wrangler 2.1.6 → 2.1.8

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.
Files changed (52) hide show
  1. package/miniflare-dist/index.mjs +5 -20
  2. package/package.json +14 -3
  3. package/src/__tests__/api-dev.test.ts +20 -0
  4. package/src/__tests__/configuration.test.ts +125 -22
  5. package/src/__tests__/dev.test.tsx +0 -2
  6. package/src/__tests__/helpers/mock-oauth-flow.ts +4 -2
  7. package/src/__tests__/index.test.ts +2 -0
  8. package/src/__tests__/paths.test.ts +23 -1
  9. package/src/__tests__/publish.test.ts +8 -10
  10. package/src/__tests__/user.test.ts +4 -4
  11. package/src/__tests__/whoami.test.tsx +0 -1
  12. package/src/__tests__/worker-namespace.test.ts +102 -112
  13. package/src/api/dev.ts +12 -12
  14. package/src/bundle.ts +59 -1
  15. package/src/cfetch/internal.ts +37 -21
  16. package/src/config/environment.ts +20 -0
  17. package/src/config/index.ts +32 -0
  18. package/src/config/validation.ts +59 -0
  19. package/src/config-cache.ts +1 -1
  20. package/src/create-worker-upload-form.ts +9 -0
  21. package/src/d1/backups.tsx +212 -0
  22. package/src/d1/create.tsx +54 -0
  23. package/src/d1/delete.tsx +56 -0
  24. package/src/d1/execute.tsx +294 -0
  25. package/src/d1/formatTimeAgo.ts +14 -0
  26. package/src/d1/index.ts +75 -0
  27. package/src/d1/list.tsx +48 -0
  28. package/src/d1/options.ts +12 -0
  29. package/src/d1/types.tsx +14 -0
  30. package/src/d1/utils.ts +39 -0
  31. package/src/dev/dev.tsx +30 -3
  32. package/src/dev/get-local-persistence-path.tsx +31 -0
  33. package/src/dev/local.tsx +73 -11
  34. package/src/dev/start-server.ts +6 -3
  35. package/src/dev/use-esbuild.ts +12 -1
  36. package/src/dev.tsx +48 -29
  37. package/src/dialogs.tsx +4 -0
  38. package/src/environment-variables.ts +17 -2
  39. package/src/index.tsx +18 -16
  40. package/src/logger.ts +11 -4
  41. package/src/miniflare-cli/index.ts +11 -16
  42. package/src/pages/dev.tsx +13 -9
  43. package/src/paths.ts +30 -4
  44. package/src/proxy.ts +21 -1
  45. package/src/publish.ts +7 -0
  46. package/src/user/user.tsx +1 -0
  47. package/src/worker.ts +30 -0
  48. package/templates/d1-beta-facade.js +174 -0
  49. package/templates/experimental-local-cache-stubs.js +27 -0
  50. package/wrangler-dist/cli.d.ts +438 -7
  51. package/wrangler-dist/cli.js +11679 -3911
  52. package/src/miniflare-cli/enum-keys.ts +0 -17
@@ -1,6 +1,7 @@
1
+ import { rest } from "msw";
1
2
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
2
- import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
3
3
  import { mockConsoleMethods } from "./helpers/mock-console";
4
+ import { msw } from "./helpers/msw";
4
5
  import { runInTempDir } from "./helpers/run-in-tmp";
5
6
  import { runWrangler } from "./helpers/run-wrangler";
6
7
 
@@ -10,10 +11,6 @@ describe("dispatch-namespace", () => {
10
11
  mockAccountId();
11
12
  mockApiToken();
12
13
 
13
- afterEach(() => {
14
- unsetAllMocks();
15
- });
16
-
17
14
  it("should should display a list of available subcommands, for dispatch-namespace with no subcommand", async () => {
18
15
  await runWrangler("dispatch-namespace");
19
16
 
@@ -45,31 +42,30 @@ describe("dispatch-namespace", () => {
45
42
  });
46
43
 
47
44
  describe("create namespace", () => {
48
- function mockCreateRequest(expectedName: string) {
49
- const requests = { count: 0 };
50
- setMockResponse(
51
- "/accounts/:accountId/workers/dispatch/namespaces",
52
- ([_url], init) => {
53
- requests.count += 1;
54
-
55
- const incomingText = init.body?.toString() || "";
56
- const { name: namespace_name } = JSON.parse(incomingText);
57
-
58
- expect(init.method).toBe("POST");
59
- expect(namespace_name).toEqual(expectedName);
60
-
61
- return {
62
- namespace_id: "some-namespace-id",
63
- namespace_name: "namespace-name",
64
- created_on: "2022-06-29T14:30:08.16152Z",
65
- created_by: "1fc1df98cc4420fe00367c3ab68c1639",
66
- modified_on: "2022-06-29T14:30:08.16152Z",
67
- modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
68
- };
45
+ const namespaceName = "my-namespace";
46
+ let counter = 0;
47
+ msw.use(
48
+ rest.post(
49
+ "/accounts/:accountId/workers/dispatch/namespaces/:namespaceNameParam",
50
+ (req, res, cxt) => {
51
+ counter++;
52
+ const { namespaceNameParam } = req.params;
53
+ expect(counter).toBe(1);
54
+ expect(namespaceNameParam).toBe(namespaceName);
55
+ return res.once(
56
+ cxt.status(200),
57
+ cxt.json({
58
+ namespace_id: "some-namespace-id",
59
+ namespace_name: "namespace-name",
60
+ created_on: "2022-06-29T14:30:08.16152Z",
61
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
62
+ modified_on: "2022-06-29T14:30:08.16152Z",
63
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
64
+ })
65
+ );
69
66
  }
70
- );
71
- return requests;
72
- }
67
+ )
68
+ );
73
69
 
74
70
  it("should display help for create", async () => {
75
71
  await expect(
@@ -95,10 +91,7 @@ describe("dispatch-namespace", () => {
95
91
  });
96
92
 
97
93
  it("should attempt to create the given namespace", async () => {
98
- const namespaceName = "my-namespace";
99
- const requests = mockCreateRequest(namespaceName);
100
94
  await runWrangler(`dispatch-namespace create ${namespaceName}`);
101
- expect(requests.count).toEqual(1);
102
95
 
103
96
  expect(std.out).toMatchInlineSnapshot(
104
97
  `"Created dispatch namespace \\"my-namespace\\" with ID \\"some-namespace-id\\""`
@@ -107,21 +100,20 @@ describe("dispatch-namespace", () => {
107
100
  });
108
101
 
109
102
  describe("delete namespace", () => {
110
- function mockDeleteRequest(expectedName: string) {
111
- const requests = { count: 0 };
112
- setMockResponse(
113
- "/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
114
- ([_url, _, namespaceName], init) => {
115
- requests.count += 1;
116
-
117
- expect(init.method).toBe("DELETE");
118
- expect(namespaceName).toEqual(expectedName);
119
-
120
- return null;
103
+ const namespaceName = "my-namespace";
104
+ let counter = 0;
105
+ msw.use(
106
+ rest.delete(
107
+ "/accounts/:accountId/workers/dispatch/namespaces/:namespaceNameParam",
108
+ (req, res, cxt) => {
109
+ counter++;
110
+ const { namespaceNameParam } = req.params;
111
+ expect(counter).toBe(1);
112
+ expect(namespaceNameParam).toBe(namespaceName);
113
+ return res.once(cxt.status(200), cxt.json(null));
121
114
  }
122
- );
123
- return requests;
124
- }
115
+ )
116
+ );
125
117
 
126
118
  it("should display help for delete", async () => {
127
119
  await expect(
@@ -147,10 +139,7 @@ describe("dispatch-namespace", () => {
147
139
  });
148
140
 
149
141
  it("should try to delete the given namespace", async () => {
150
- const namespaceName = "my-namespace";
151
- const requests = mockDeleteRequest(namespaceName);
152
142
  await runWrangler(`dispatch-namespace delete ${namespaceName}`);
153
- expect(requests.count).toBe(1);
154
143
 
155
144
  expect(std.out).toMatchInlineSnapshot(
156
145
  `"Deleted dispatch namespace \\"my-namespace\\""`
@@ -159,27 +148,30 @@ describe("dispatch-namespace", () => {
159
148
  });
160
149
 
161
150
  describe("get namespace", () => {
162
- function mockInfoRequest(expectedName: string) {
163
- const requests = { count: 0 };
164
- setMockResponse(
165
- "/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
166
- ([_url, _, namespaceName], _init) => {
167
- requests.count += 1;
168
-
169
- expect(namespaceName).toEqual(expectedName);
170
-
171
- return {
172
- namespace_id: "some-namespace-id",
173
- namespace_name: "namespace-name",
174
- created_on: "2022-06-29T14:30:08.16152Z",
175
- created_by: "1fc1df98cc4420fe00367c3ab68c1639",
176
- modified_on: "2022-06-29T14:30:08.16152Z",
177
- modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
178
- };
151
+ const namespaceName = "my-namespace";
152
+ let counter = 0;
153
+ msw.use(
154
+ rest.get(
155
+ "/accounts/:accountId/workers/dispatch/namespaces/:namespaceNameParam",
156
+ (req, res, cxt) => {
157
+ counter++;
158
+ const { namespaceNameParam } = req.params;
159
+ expect(counter).toBe(1);
160
+ expect(namespaceNameParam).toBe(namespaceName);
161
+ return res.once(
162
+ cxt.status(200),
163
+ cxt.json({
164
+ namespace_id: "some-namespace-id",
165
+ namespace_name: "namespace-name",
166
+ created_on: "2022-06-29T14:30:08.16152Z",
167
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
168
+ modified_on: "2022-06-29T14:30:08.16152Z",
169
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
170
+ })
171
+ );
179
172
  }
180
- );
181
- return requests;
182
- }
173
+ )
174
+ );
183
175
 
184
176
  it("should display help for get", async () => {
185
177
  await expect(
@@ -205,10 +197,7 @@ describe("dispatch-namespace", () => {
205
197
  });
206
198
 
207
199
  it("should attempt to get info for the given namespace", async () => {
208
- const namespaceName = "my-namespace";
209
- const requests = mockInfoRequest(namespaceName);
210
200
  await runWrangler(`dispatch-namespace get ${namespaceName}`);
211
- expect(requests.count).toBe(1);
212
201
 
213
202
  expect(std.out).toMatchInlineSnapshot(`
214
203
  "{
@@ -224,32 +213,33 @@ describe("dispatch-namespace", () => {
224
213
  });
225
214
 
226
215
  describe("list namespaces", () => {
227
- function mockListRequest() {
228
- const requests = { count: 0 };
229
- setMockResponse(
230
- "/accounts/:accountId/workers/dispatch/namespaces",
231
- ([_url, _, _page, _perPage], _init) => {
232
- requests.count += 1;
233
-
234
- return [
235
- {
216
+ const namespaceName = "my-namespace";
217
+ let counter = 0;
218
+ msw.use(
219
+ rest.get(
220
+ "/accounts/:accountId/workers/dispatch/namespaces/:namespaceNameParam",
221
+ (req, res, cxt) => {
222
+ counter++;
223
+ const { namespaceNameParam } = req.params;
224
+ expect(counter).toBe(1);
225
+ expect(namespaceNameParam).toBe(namespaceName);
226
+ return res.once(
227
+ cxt.status(200),
228
+ cxt.json({
236
229
  namespace_id: "some-namespace-id",
237
230
  namespace_name: "namespace-name",
238
231
  created_on: "2022-06-29T14:30:08.16152Z",
239
232
  created_by: "1fc1df98cc4420fe00367c3ab68c1639",
240
233
  modified_on: "2022-06-29T14:30:08.16152Z",
241
234
  modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
242
- },
243
- ];
235
+ })
236
+ );
244
237
  }
245
- );
246
- return requests;
247
- }
238
+ )
239
+ );
248
240
 
249
241
  it("should list all namespaces", async () => {
250
- const requests = mockListRequest();
251
242
  await runWrangler("dispatch-namespace list");
252
- expect(requests.count).toBe(1);
253
243
  expect(std.out).toMatchInlineSnapshot(`
254
244
  "[
255
245
  {
@@ -266,28 +256,30 @@ describe("dispatch-namespace", () => {
266
256
  });
267
257
 
268
258
  describe("rename namespace", () => {
269
- function mockRenameRequest(expectedName: string) {
270
- const requests = { count: 0 };
271
- setMockResponse(
272
- "/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
273
- ([_url, _, namespaceName], init) => {
274
- requests.count += 1;
275
-
276
- expect(init.method).toEqual("PUT");
277
- expect(namespaceName).toEqual(expectedName);
278
-
279
- return {
280
- namespace_id: "some-namespace-id",
281
- namespace_name: "namespace-name",
282
- created_on: "2022-06-29T14:30:08.16152Z",
283
- created_by: "1fc1df98cc4420fe00367c3ab68c1639",
284
- modified_on: "2022-06-29T14:30:08.16152Z",
285
- modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
286
- };
259
+ const namespaceName = "my-namespace";
260
+ let counter = 0;
261
+ msw.use(
262
+ rest.put(
263
+ "/accounts/:accountId/workers/dispatch/namespaces/:namespaceNameParam",
264
+ (req, res, cxt) => {
265
+ counter++;
266
+ const { namespaceNameParam } = req.params;
267
+ expect(counter).toBe(1);
268
+ expect(namespaceNameParam).toBe(namespaceName);
269
+ return res.once(
270
+ cxt.status(200),
271
+ cxt.json({
272
+ namespace_id: "some-namespace-id",
273
+ namespace_name: "namespace-name",
274
+ created_on: "2022-06-29T14:30:08.16152Z",
275
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
276
+ modified_on: "2022-06-29T14:30:08.16152Z",
277
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
278
+ })
279
+ );
287
280
  }
288
- );
289
- return requests;
290
- }
281
+ )
282
+ );
291
283
 
292
284
  it("should display help for rename", async () => {
293
285
  await expect(
@@ -314,13 +306,11 @@ describe("dispatch-namespace", () => {
314
306
  });
315
307
 
316
308
  it("should attempt to rename the given namespace", async () => {
317
- const namespaceName = "my-namespace";
318
309
  const newName = "new-namespace";
319
- const requests = mockRenameRequest(namespaceName);
320
310
  await runWrangler(
321
311
  `dispatch-namespace rename ${namespaceName} ${newName}`
322
312
  );
323
- expect(requests.count).toBe(1);
313
+
324
314
  expect(std.out).toMatchInlineSnapshot(
325
315
  `"Renamed dispatch namespace \\"my-namespace\\" to \\"new-namespace\\""`
326
316
  );
package/src/api/dev.ts CHANGED
@@ -2,6 +2,7 @@ import { fetch, Request } from "undici";
2
2
  import { startApiDev, startDev } from "../dev";
3
3
  import { logger } from "../logger";
4
4
 
5
+ import type { Environment } from "../config";
5
6
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
6
7
  import type { RequestInit, Response, RequestInfo } from "undici";
7
8
 
@@ -10,7 +11,7 @@ interface DevOptions {
10
11
  env?: string;
11
12
  ip?: string;
12
13
  port?: number;
13
- noBundle?: boolean;
14
+ bundle?: boolean;
14
15
  inspectorPort?: number;
15
16
  localProtocol?: "http" | "https";
16
17
  assets?: string;
@@ -43,8 +44,9 @@ interface DevOptions {
43
44
  bucket_name: string;
44
45
  preview_bucket_name?: string;
45
46
  }[];
47
+ d1Databases?: Environment["d1_databases"];
46
48
  showInteractiveDevSession?: boolean;
47
- logLevel?: "none" | "error" | "log" | "warn" | "debug";
49
+ logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug";
48
50
  logPrefix?: string;
49
51
  inspect?: boolean;
50
52
  forceLocal?: boolean;
@@ -60,25 +62,20 @@ interface DevApiOptions {
60
62
  }
61
63
 
62
64
  export interface UnstableDevWorker {
65
+ port: number;
66
+ address: string;
63
67
  stop: () => Promise<void>;
64
- fetch: (
65
- input?: RequestInfo,
66
- init?: RequestInit
67
- ) => Promise<Response | undefined>;
68
+ fetch: (input?: RequestInfo, init?: RequestInit) => Promise<Response>;
68
69
  waitUntilExit: () => Promise<void>;
69
70
  }
70
71
  /**
71
72
  * unstable_dev starts a wrangler dev server, and returns a promise that resolves with utility functions to interact with it.
72
- * @param {string} script
73
- * @param {DevOptions} options
74
- * @param {DevApiOptions} apiOptions
75
- * @returns {Promise<UnstableDev>}
76
73
  */
77
74
  export async function unstable_dev(
78
75
  script: string,
79
76
  options?: DevOptions,
80
77
  apiOptions?: DevApiOptions
81
- ) {
78
+ ): Promise<UnstableDevWorker> {
82
79
  const { testMode = true, disableExperimentalWarning = false } =
83
80
  apiOptions || {};
84
81
  if (!disableExperimentalWarning) {
@@ -116,6 +113,8 @@ export async function unstable_dev(
116
113
  // now that the inner promise has resolved, we can resolve the outer promise
117
114
  // with an object that lets you fetch and stop the dev server
118
115
  resolve({
116
+ port: readyPort,
117
+ address: readyAddress,
119
118
  stop: devServer.stop,
120
119
  fetch: async (input?: RequestInfo, init?: RequestInit) => {
121
120
  return await fetch(
@@ -144,7 +143,6 @@ export async function unstable_dev(
144
143
  const devServer = startDev({
145
144
  script: script,
146
145
  inspect: false,
147
- logLevel: "none",
148
146
  showInteractiveDevSession: false,
149
147
  _: [],
150
148
  $0: "",
@@ -158,6 +156,8 @@ export async function unstable_dev(
158
156
  });
159
157
  }).then((devServer) => {
160
158
  resolve({
159
+ port: readyPort,
160
+ address: readyAddress,
161
161
  stop: devServer.stop,
162
162
  fetch: async (input?: RequestInfo, init?: RequestInit) => {
163
163
  return await fetch(
package/src/bundle.ts CHANGED
@@ -63,6 +63,7 @@ export async function bundleWorker(
63
63
  options: {
64
64
  serveAssetsFromWorker: boolean;
65
65
  assets: StaticAssetsConfig;
66
+ betaD1Shims?: string[];
66
67
  jsxFactory: string | undefined;
67
68
  jsxFragment: string | undefined;
68
69
  rules: Config["rules"];
@@ -76,11 +77,14 @@ export async function bundleWorker(
76
77
  workerDefinitions: WorkerRegistry | undefined;
77
78
  firstPartyWorkerDevFacade: boolean | undefined;
78
79
  targetConsumer: "dev" | "publish";
80
+ local: boolean;
79
81
  testScheduled?: boolean | undefined;
82
+ experimentalLocalStubCache: boolean | undefined;
80
83
  }
81
84
  ): Promise<BundleResult> {
82
85
  const {
83
86
  serveAssetsFromWorker,
87
+ betaD1Shims,
84
88
  jsxFactory,
85
89
  jsxFragment,
86
90
  rules,
@@ -89,12 +93,14 @@ export async function bundleWorker(
89
93
  minify,
90
94
  nodeCompat,
91
95
  checkFetch,
96
+ local,
92
97
  assets,
93
98
  workerDefinitions,
94
99
  services,
95
100
  firstPartyWorkerDevFacade,
96
101
  targetConsumer,
97
102
  testScheduled,
103
+ experimentalLocalStubCache,
98
104
  } = options;
99
105
 
100
106
  // We create a temporary directory for any oneoff files we
@@ -188,6 +194,12 @@ export async function bundleWorker(
188
194
  return applyFirstPartyWorkerDevFacade(currentEntry, tmpDir.path);
189
195
  }),
190
196
 
197
+ Array.isArray(betaD1Shims) &&
198
+ betaD1Shims.length > 0 &&
199
+ ((currentEntry: Entry) => {
200
+ return applyD1BetaFacade(currentEntry, tmpDir.path, betaD1Shims, local);
201
+ }),
202
+
191
203
  // Middleware loader: to add middleware, we add the path to the middleware
192
204
  // Currently for demonstration purposes we have two example middlewares
193
205
  // Middlewares are togglable by changing the `publish` (default=false) and `dev` (default=true) options
@@ -223,12 +235,20 @@ export async function bundleWorker(
223
235
 
224
236
  // At this point, inputEntry points to the entry point we want to build.
225
237
 
238
+ const inject: string[] = [];
239
+ if (checkFetch) inject.push(checkedFetchFileToInject);
240
+ if (experimentalLocalStubCache) {
241
+ inject.push(
242
+ path.resolve(getBasePath(), "templates/experimental-local-cache-stubs.js")
243
+ );
244
+ }
245
+
226
246
  const result = await esbuild.build({
227
247
  entryPoints: [inputEntry.file],
228
248
  bundle: true,
229
249
  absWorkingDir: entry.directory,
230
250
  outdir: destination,
231
- inject: checkFetch ? [checkedFetchFileToInject] : [],
251
+ inject,
232
252
  external: ["__STATIC_CONTENT_MANIFEST"],
233
253
  format: entry.format === "modules" ? "esm" : "iife",
234
254
  target: "es2020",
@@ -686,6 +706,44 @@ async function applyFirstPartyWorkerDevFacade(
686
706
  };
687
707
  }
688
708
 
709
+ /**
710
+ * A middleware that injects the beta D1 API in JS.
711
+ *
712
+ * This code be removed from here when the API is in Workers core,
713
+ * but moved inside Miniflare for simulating D1.
714
+ */
715
+
716
+ async function applyD1BetaFacade(
717
+ entry: Entry,
718
+ tmpDirPath: string,
719
+ betaD1Shims: string[],
720
+ local: boolean
721
+ ): Promise<Entry> {
722
+ const targetPath = path.join(tmpDirPath, "d1-beta-facade.entry.js");
723
+
724
+ await esbuild.build({
725
+ entryPoints: [path.resolve(getBasePath(), "templates/d1-beta-facade.js")],
726
+ bundle: true,
727
+ format: "esm",
728
+ sourcemap: true,
729
+ plugins: [
730
+ esbuildAliasExternalPlugin({
731
+ __ENTRY_POINT__: entry.file,
732
+ }),
733
+ ],
734
+ define: {
735
+ __D1_IMPORTS__: JSON.stringify(betaD1Shims),
736
+ __LOCAL_MODE__: JSON.stringify(local),
737
+ },
738
+ outfile: targetPath,
739
+ });
740
+
741
+ return {
742
+ ...entry,
743
+ file: targetPath,
744
+ };
745
+ }
746
+
689
747
  /**
690
748
  * Generate a string that describes the entry-points that were identified by esbuild.
691
749
  */
@@ -19,21 +19,18 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
19
19
  defaultValue: "https://api.cloudflare.com/client/v4",
20
20
  });
21
21
 
22
- /**
23
- * Make a fetch request to the Cloudflare API.
24
- *
25
- * This function handles acquiring the API token and logging the caller in, as necessary.
26
- *
27
- * Check out https://api.cloudflare.com/ for API docs.
28
- *
29
- * This function should not be used directly, instead use the functions in `cfetch/index.ts`.
30
- */
31
- export async function fetchInternal<ResponseType>(
22
+ /*
23
+ * performApiFetch does everything required to make a CF API request,
24
+ * but doesn't parse the response as JSON. For normal V4 API responses,
25
+ * use `fetchInternal`
26
+ * */
27
+ export async function performApiFetch(
32
28
  resource: string,
33
29
  init: RequestInit = {},
34
30
  queryParams?: URLSearchParams,
35
31
  abortSignal?: AbortSignal
36
- ): Promise<ResponseType> {
32
+ ) {
33
+ const method = init.method ?? "GET";
37
34
  assert(
38
35
  resource.startsWith("/"),
39
36
  `CF API fetch - resource path must start with a "/" but got "${resource}"`
@@ -45,22 +42,41 @@ export async function fetchInternal<ResponseType>(
45
42
  addUserAgent(headers);
46
43
 
47
44
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
48
- const method = init.method ?? "GET";
49
-
50
45
  logger.debug(
51
46
  `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}`
52
47
  );
53
48
  logger.debug("HEADERS:", JSON.stringify(headers, null, 2));
54
49
  logger.debug("INIT:", JSON.stringify(init, null, 2));
55
50
  logger.debug("-- END CF API REQUEST");
56
- const response = await fetch(
57
- `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
58
- {
59
- method,
60
- ...init,
61
- headers,
62
- signal: abortSignal,
63
- }
51
+ return await fetch(`${getCloudflareAPIBaseURL()}${resource}${queryString}`, {
52
+ method,
53
+ ...init,
54
+ headers,
55
+ signal: abortSignal,
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Make a fetch request to the Cloudflare API.
61
+ *
62
+ * This function handles acquiring the API token and logging the caller in, as necessary.
63
+ *
64
+ * Check out https://api.cloudflare.com/ for API docs.
65
+ *
66
+ * This function should not be used directly, instead use the functions in `cfetch/index.ts`.
67
+ */
68
+ export async function fetchInternal<ResponseType>(
69
+ resource: string,
70
+ init: RequestInit = {},
71
+ queryParams?: URLSearchParams,
72
+ abortSignal?: AbortSignal
73
+ ): Promise<ResponseType> {
74
+ const method = init.method ?? "GET";
75
+ const response = await performApiFetch(
76
+ resource,
77
+ init,
78
+ queryParams,
79
+ abortSignal
64
80
  );
65
81
  const jsonText = await response.text();
66
82
  logger.debug(
@@ -343,6 +343,26 @@ interface EnvironmentNonInheritable {
343
343
  preview_bucket_name?: string;
344
344
  }[];
345
345
 
346
+ /**
347
+ * Specifies D1 databases that are bound to this Worker environment.
348
+ *
349
+ * NOTE: This field is not automatically inherited from the top level environment,
350
+ * and so must be specified in every named environment.
351
+ *
352
+ * @default `[]`
353
+ * @nonInheritable
354
+ */
355
+ d1_databases: {
356
+ /** The binding name used to refer to the D1 database in the worker. */
357
+ binding: string;
358
+ /** The name of this D1 database. */
359
+ database_name: string;
360
+ /** The UUID of this D1 database (not required). */
361
+ database_id: string;
362
+ /** The UUID of this D1 database for Wrangler Dev (if specified). */
363
+ preview_database_id?: string;
364
+ }[];
365
+
346
366
  /**
347
367
  * Specifies service bindings (worker-to-worker) that are bound to this Worker environment.
348
368
  *
@@ -1,9 +1,11 @@
1
1
  import { findUpSync } from "find-up";
2
2
  import { logger } from "../logger";
3
3
  import { parseTOML, readFileSync } from "../parse";
4
+ import { removeD1BetaPrefix } from "../worker";
4
5
  import { normalizeAndValidateConfig } from "./validation";
5
6
  import type { CfWorkerInit } from "../worker";
6
7
  import type { Config, RawConfig } from "./config";
8
+ import type { CamelCaseKey } from "yargs";
7
9
 
8
10
  export type {
9
11
  Config,
@@ -84,6 +86,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
84
86
  data_blobs,
85
87
  durable_objects,
86
88
  kv_namespaces,
89
+ d1_databases,
87
90
  r2_buckets,
88
91
  logfwdr,
89
92
  services,
@@ -138,6 +141,20 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
138
141
  });
139
142
  }
140
143
 
144
+ if (d1_databases !== undefined && d1_databases.length > 0) {
145
+ output.push({
146
+ type: "D1 Databases",
147
+ entries: d1_databases.map(({ binding, database_name, database_id }) => {
148
+ return {
149
+ key: removeD1BetaPrefix(binding),
150
+ value: database_name
151
+ ? `${database_name} (${database_id})`
152
+ : database_id,
153
+ };
154
+ }),
155
+ });
156
+ }
157
+
141
158
  if (r2_buckets !== undefined && r2_buckets.length > 0) {
142
159
  output.push({
143
160
  type: "R2 Buckets",
@@ -249,3 +266,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
249
266
 
250
267
  logger.log(message);
251
268
  }
269
+
270
+ type CamelCase<T> = {
271
+ [key in keyof T as key | CamelCaseKey<key>]: T[key];
272
+ };
273
+
274
+ export function withConfig<T extends { config?: string }>(
275
+ handler: (
276
+ t: Omit<CamelCase<T>, "config"> & { config: Config }
277
+ ) => Promise<void>
278
+ ) {
279
+ return (t: CamelCase<T>) => {
280
+ const { config: configPath, ...rest } = t;
281
+ return handler({ ...rest, config: readConfig(configPath, rest) });
282
+ };
283
+ }