wrangler 2.5.0 → 2.6.0

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/src/dev/local.tsx CHANGED
@@ -4,6 +4,7 @@ import { realpathSync } from "node:fs";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import chalk from "chalk";
7
+ import getPort from "get-port";
7
8
  import { npxImport } from "npx-import";
8
9
  import { useState, useEffect, useRef } from "react";
9
10
  import onExit from "signal-exit";
@@ -11,6 +12,7 @@ import { performApiFetch } from "../cfetch/internal";
11
12
  import { registerWorker } from "../dev-registry";
12
13
  import useInspector from "../inspect";
13
14
  import { logger } from "../logger";
15
+ import generateASSETSBinding from "../miniflare-cli/assets";
14
16
  import {
15
17
  DEFAULT_MODULE_RULES,
16
18
  ModuleTypeToRuleType,
@@ -110,7 +112,6 @@ function useLocalWorker({
110
112
  workerDefinitions,
111
113
  assetPaths,
112
114
  initialPort,
113
- inspectorPort,
114
115
  rules,
115
116
  localPersistencePath,
116
117
  liveReload,
@@ -134,6 +135,11 @@ function useLocalWorker({
134
135
  const removeExperimentalLocalSignalExitListener = useRef<() => void>();
135
136
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
136
137
 
138
+ // Our inspector proxy server will be binding to `LocalProps`'s `inspectorPort`.
139
+ // If we attempted to bind Node.js/workerd to the same inspector port, we'd get a port already in use error.
140
+ // Therefore, generate a new random port for our runtime's to bind their inspector service to.
141
+ const runtimeInspectorPortRef = useRef<number>();
142
+
137
143
  useEffect(() => {
138
144
  if (bindings.services && bindings.services.length > 0) {
139
145
  logger.warn(
@@ -159,13 +165,6 @@ function useLocalWorker({
159
165
  async function startLocalWorker() {
160
166
  if (!bundle || !format) return;
161
167
 
162
- // port for the worker
163
- await waitForPortToBeAvailable(initialPort, {
164
- retryPeriod: 200,
165
- timeout: 2000,
166
- abortSignal: abortController.signal,
167
- });
168
-
169
168
  // In local mode, we want to copy all referenced modules into
170
169
  // the output bundle directory before starting up
171
170
  for (const module of bundle.modules) {
@@ -197,6 +196,9 @@ function useLocalWorker({
197
196
  bundle,
198
197
  });
199
198
 
199
+ runtimeInspectorPortRef.current ??= await getPort();
200
+ const runtimeInspectorPort = runtimeInspectorPortRef.current;
201
+
200
202
  const { forkOptions, miniflareCLIPath, options } = setupMiniflareOptions({
201
203
  workerName,
202
204
  port: initialPort,
@@ -236,11 +238,12 @@ function useLocalWorker({
236
238
  format,
237
239
  bundle,
238
240
  log,
241
+ enablePagesAssetsServiceBinding,
239
242
  kvNamespaces: bindings?.kv_namespaces,
240
243
  r2Buckets: bindings?.r2_buckets,
241
244
  authenticatedAccountId: accountId,
242
245
  kvRemote: experimentalLocalRemoteKv,
243
- inspectorPort,
246
+ inspectorPort: runtimeInspectorPort,
244
247
  });
245
248
 
246
249
  const current = experimentalLocalRef.current;
@@ -268,7 +271,7 @@ function useLocalWorker({
268
271
  try {
269
272
  // fetch the inspector JSON response from the DevTools Inspector protocol
270
273
  const inspectorJSONArr = (await (
271
- await fetch(`http://127.0.0.1:${inspectorPort}/json`)
274
+ await fetch(`http://127.0.0.1:${runtimeInspectorPort}/json`)
272
275
  ).json()) as InspectorJSON;
273
276
 
274
277
  const foundInspectorURL = inspectorJSONArr?.find((inspectorJSON) =>
@@ -295,10 +298,18 @@ function useLocalWorker({
295
298
  return;
296
299
  }
297
300
 
301
+ // Wait for the Worker port to be available. We don't want to do this in experimental local
302
+ // mode, as we only `dispose()` the Miniflare 3 instance, and shutdown the server when
303
+ // unmounting the component, not when props change. If we did, we'd just timeout every time.
304
+ await waitForPortToBeAvailable(initialPort, {
305
+ retryPeriod: 200,
306
+ timeout: 2000,
307
+ abortSignal: abortController.signal,
308
+ });
309
+
298
310
  const nodeOptions = setupNodeOptions({
299
311
  inspect,
300
- ip: initialIp,
301
- inspectorPort,
312
+ inspectorPort: runtimeInspectorPort,
302
313
  });
303
314
  logger.log("⎔ Starting a local server...");
304
315
 
@@ -386,7 +397,13 @@ function useLocalWorker({
386
397
  }
387
398
 
388
399
  startLocalWorker().catch((err) => {
389
- logger.error("local worker:", err);
400
+ if (err.code === "ERR_RUNTIME_FAILURE") {
401
+ // Don't log a full verbose stack-trace when Miniflare 3's workerd instance fails to start.
402
+ // workerd will log its own errors, and our stack trace won't have any useful information.
403
+ logger.error(err.message);
404
+ } else {
405
+ logger.error("local worker:", err);
406
+ }
390
407
  });
391
408
 
392
409
  return () => {
@@ -404,7 +421,6 @@ function useLocalWorker({
404
421
  workerName,
405
422
  format,
406
423
  initialPort,
407
- inspectorPort,
408
424
  initialIp,
409
425
  queueConsumers,
410
426
  bindings.queues,
@@ -733,11 +749,9 @@ export function setupMiniflareOptions({
733
749
 
734
750
  export function setupNodeOptions({
735
751
  inspect,
736
- ip,
737
752
  inspectorPort,
738
753
  }: {
739
754
  inspect: boolean;
740
- ip: string;
741
755
  inspectorPort: number;
742
756
  }) {
743
757
  const nodeOptions = [
@@ -746,7 +760,7 @@ export function setupNodeOptions({
746
760
  // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
747
761
  ];
748
762
  if (inspect) {
749
- nodeOptions.push("--inspect=" + `${ip}:${inspectorPort}`); // start Miniflare listening for a debugger to attach
763
+ nodeOptions.push("--inspect=" + `127.0.0.1:${inspectorPort}`); // start Miniflare listening for a debugger to attach
750
764
  }
751
765
  return nodeOptions;
752
766
  }
@@ -761,6 +775,8 @@ export interface SetupMiniflare3Options {
761
775
  // Miniflare's logger
762
776
  log: Miniflare3LogType;
763
777
 
778
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
779
+
764
780
  // Miniflare 3 accepts namespace/bucket names in addition to binding names.
765
781
  // This means multiple workers persisting to the same location can have
766
782
  // different binding names for the same namespace/bucket. Therefore, we need
@@ -798,6 +814,7 @@ export async function transformMf2OptionsToMf3Options({
798
814
  format,
799
815
  bundle,
800
816
  log,
817
+ enablePagesAssetsServiceBinding,
801
818
  kvNamespaces,
802
819
  r2Buckets,
803
820
  authenticatedAccountId,
@@ -833,6 +850,19 @@ export async function transformMf2OptionsToMf3Options({
833
850
  log,
834
851
  };
835
852
 
853
+ if (enablePagesAssetsServiceBinding !== undefined) {
854
+ options.serviceBindings = {
855
+ ...options.serviceBindings,
856
+ ASSETS: (await generateASSETSBinding({
857
+ log,
858
+ ...enablePagesAssetsServiceBinding,
859
+ tre: true,
860
+ // We can get rid of this `any` easily once we do experimental-local/tre by default
861
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
862
+ })) as any,
863
+ };
864
+ }
865
+
836
866
  if (format === "modules") {
837
867
  // Manually specify all modules from the bundle. If we didn't do this,
838
868
  // Miniflare 3 would try collect them automatically again itself.
@@ -886,5 +916,5 @@ export async function getMiniflare3(): Promise<
886
916
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
887
917
  typeof import("@miniflare/tre")
888
918
  > {
889
- return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.7"));
919
+ return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.8"));
890
920
  }
@@ -426,11 +426,7 @@ export async function startLocalServer({
426
426
  return;
427
427
  }
428
428
 
429
- const nodeOptions = setupNodeOptions({
430
- inspect,
431
- ip: initialIp,
432
- inspectorPort,
433
- });
429
+ const nodeOptions = setupNodeOptions({ inspect, inspectorPort });
434
430
  logger.log("⎔ Starting a local server...");
435
431
 
436
432
  const child = (local = fork(miniflareCLIPath, forkOptions, {
package/src/dev.tsx CHANGED
@@ -14,7 +14,7 @@ import { getEntry } from "./entry";
14
14
  import { logger } from "./logger";
15
15
  import * as metrics from "./metrics";
16
16
  import { getAssetPaths, getSiteAssetPaths } from "./sites";
17
- import { getAccountFromCache } from "./user";
17
+ import { getAccountFromCache, loginOrRefreshIfRequired } from "./user";
18
18
  import { collectKeyValues } from "./utils/collectKeyValues";
19
19
  import { identifyD1BindingsAsBeta } from "./worker";
20
20
  import { getHostFromRoute, getZoneForRoute, getZoneIdFromHost } from "./zones";
@@ -328,6 +328,15 @@ export function devOptions(yargs: Argv<CommonYargsOptions>): Argv<DevArgs> {
328
328
  }
329
329
 
330
330
  export async function devHandler(args: ArgumentsCamelCase<DevArgs>) {
331
+ if (!args.local) {
332
+ const isLoggedIn = await loginOrRefreshIfRequired();
333
+ if (!isLoggedIn) {
334
+ throw new Error(
335
+ "You must be logged in to use wrangler dev in remote mode. Try logging in, or run wrangler dev --local."
336
+ );
337
+ }
338
+ }
339
+
331
340
  let watcher;
332
341
  try {
333
342
  const devInstance = await startDev(args);
@@ -892,10 +901,22 @@ async function getBindings(
892
901
  ],
893
902
  dispatch_namespaces: configParam.dispatch_namespaces,
894
903
  services: configParam.services,
904
+ analytics_engine_datasets: configParam.analytics_engine_datasets,
895
905
  unsafe: configParam.unsafe?.bindings,
896
906
  logfwdr: configParam.logfwdr,
897
907
  d1_databases: identifyD1BindingsAsBeta([
898
- ...configParam.d1_databases,
908
+ ...(configParam.d1_databases ?? []).map((d1Db) => {
909
+ if (!d1Db.preview_database_id) {
910
+ throw new Error(
911
+ `In development, you should use a separate D1 database than the one you'd use in production. Please create a new D1 database with "wrangler d1 create <name>" and add its id as preview_database_id to the d1_database "${d1Db.binding}" in your wrangler.toml`
912
+ );
913
+ }
914
+
915
+ return {
916
+ ...d1Db,
917
+ database_id: d1Db.preview_database_id,
918
+ };
919
+ }),
899
920
  ...(args.d1Databases || []),
900
921
  ]),
901
922
  };
package/src/git-client.ts CHANGED
@@ -42,10 +42,21 @@ export async function getGitVersioon(): Promise<string | null> {
42
42
  */
43
43
  export async function initializeGit(cwd: string) {
44
44
  try {
45
- // Try to create the repository with the HEAD branch of `main`.
46
- await execa("git", ["init", "--initial-branch", "main"], {
47
- cwd,
48
- });
45
+ // Get the default init branch name
46
+ const { stdout: defaultBranchName } = await execa("git", [
47
+ "config",
48
+ "--get",
49
+ "init.defaultBranch",
50
+ ]);
51
+
52
+ // Try to create the repository with the HEAD branch of defaultBranchName ?? `main`.
53
+ await execa(
54
+ "git",
55
+ ["init", "--initial-branch", defaultBranchName.trim() ?? "main"],
56
+ {
57
+ cwd,
58
+ }
59
+ );
49
60
  } catch {
50
61
  // Unable to create the repo with a HEAD branch name, so just fall back to the default.
51
62
  await execa("git", ["init"], {
package/src/index.tsx CHANGED
@@ -543,6 +543,7 @@ export function createCLIParser(argv: string[]) {
543
543
  r2_buckets: config.r2_buckets,
544
544
  d1_databases: config.d1_databases,
545
545
  services: config.services,
546
+ analytics_engine_datasets: config.analytics_engine_datasets,
546
547
  dispatch_namespaces: config.dispatch_namespaces,
547
548
  logfwdr: config.logfwdr,
548
549
  unsafe: { bindings: config.unsafe?.bindings },
package/src/init.ts CHANGED
@@ -862,6 +862,14 @@ async function getWorkerConfig(
862
862
  ];
863
863
  }
864
864
  break;
865
+ case "analytics_engine":
866
+ {
867
+ configObj.analytics_engine_datasets = [
868
+ ...(configObj.analytics_engine_datasets ?? []),
869
+ { binding: binding.name, dataset: binding.dataset },
870
+ ];
871
+ }
872
+ break;
865
873
  case "namespace":
866
874
  {
867
875
  configObj.dispatch_namespaces = [
@@ -3,59 +3,69 @@ import { join } from "node:path";
3
3
  import { createMetadataObject } from "@cloudflare/pages-shared/metadata-generator/createMetadataObject";
4
4
  import { parseHeaders } from "@cloudflare/pages-shared/metadata-generator/parseHeaders";
5
5
  import { parseRedirects } from "@cloudflare/pages-shared/metadata-generator/parseRedirects";
6
- import {
7
- Response as MiniflareResponse,
8
- Request as MiniflareRequest,
9
- } from "@miniflare/core";
10
- import { upgradingFetch as miniflareFetch } from "@miniflare/web-sockets";
11
6
  import { watch } from "chokidar";
12
7
  import { getType } from "mime";
13
8
  import { hashFile } from "../pages/hash";
14
9
  import type { Metadata } from "@cloudflare/pages-shared/asset-server/metadata";
15
- import type {
16
- fetch,
17
- Request,
18
- } from "@cloudflare/pages-shared/environment-polyfills/types";
19
10
  import type {
20
11
  ParsedRedirects,
21
12
  ParsedHeaders,
22
13
  } from "@cloudflare/pages-shared/metadata-generator/types";
23
- import type { RequestInfo, RequestInit, FetcherFetch } from "@miniflare/core";
24
- import type { Log } from "miniflare";
14
+ import type {
15
+ RequestInfo as TreRequestInfo,
16
+ RequestInit as TreRequestInit,
17
+ } from "@miniflare/tre";
18
+
19
+ interface Logger {
20
+ log: (message: string) => void;
21
+ warn: (message: string) => void;
22
+ error: (error: Error) => void;
23
+ }
25
24
 
26
25
  export interface Options {
27
- log: Log;
26
+ log: Logger;
28
27
  proxyPort?: number;
29
28
  directory?: string;
29
+ tre: boolean;
30
30
  }
31
31
 
32
32
  export default async function generateASSETSBinding(options: Options) {
33
33
  const assetsFetch =
34
34
  options.directory !== undefined
35
- ? await generateAssetsFetch(options.directory, options.log)
35
+ ? await generateAssetsFetch(options.directory, options.log, options.tre)
36
36
  : invalidAssetsFetch;
37
37
 
38
- return async function (miniflareRequest: MiniflareRequest) {
38
+ const miniflare = options.tre
39
+ ? await import("@miniflare/tre")
40
+ : await import("@miniflare/core");
41
+
42
+ const Request = miniflare.Request;
43
+ const Response = miniflare.Response;
44
+ // WebSockets won't work with `--experimental-local` until we expose something like `upgradingFetch` from `@miniflare/tre`.
45
+ const fetch = (
46
+ options.tre
47
+ ? miniflare.fetch
48
+ : (await import("@miniflare/web-sockets")).upgradingFetch
49
+ ) as (request: Request) => Promise<Response>;
50
+
51
+ return async function (miniflareRequest: Request) {
39
52
  if (options.proxyPort) {
40
53
  try {
41
54
  const url = new URL(miniflareRequest.url);
42
55
  url.host = `localhost:${options.proxyPort}`;
43
- const proxyRequest = new MiniflareRequest(url, miniflareRequest);
56
+ const proxyRequest = new Request(url, miniflareRequest);
44
57
  if (proxyRequest.headers.get("Upgrade") === "websocket") {
45
58
  proxyRequest.headers.delete("Sec-WebSocket-Accept");
46
59
  proxyRequest.headers.delete("Sec-WebSocket-Key");
47
60
  }
48
- return await miniflareFetch(proxyRequest);
61
+ return await fetch(proxyRequest as Request);
49
62
  } catch (thrown) {
50
63
  options.log.error(new Error(`Could not proxy request: ${thrown}`));
51
64
 
52
65
  // TODO: Pretty error page
53
- return new MiniflareResponse(
54
- `[wrangler] Could not proxy request: ${thrown}`,
55
- {
56
- status: 502,
57
- }
58
- );
66
+ return new Response(`[wrangler] Could not proxy request: ${thrown}`, {
67
+ status: 502,
68
+ });
59
69
  }
60
70
  } else {
61
71
  try {
@@ -64,21 +74,33 @@ export default async function generateASSETSBinding(options: Options) {
64
74
  options.log.error(new Error(`Could not serve static asset: ${thrown}`));
65
75
 
66
76
  // TODO: Pretty error page
67
- return new MiniflareResponse(
77
+ return new Response(
68
78
  `[wrangler] Could not serve static asset: ${thrown}`,
69
79
  { status: 502 }
70
80
  );
71
81
  }
72
82
  }
73
- } as FetcherFetch;
83
+ };
74
84
  }
75
85
 
76
86
  async function generateAssetsFetch(
77
87
  directory: string,
78
- log: Log
88
+ log: Logger,
89
+ tre: boolean
79
90
  ): Promise<typeof fetch> {
80
91
  // Defer importing miniflare until we really need it
81
- await import("@cloudflare/pages-shared/environment-polyfills/miniflare");
92
+ if (tre) {
93
+ await import(
94
+ "@cloudflare/pages-shared/environment-polyfills/miniflare-tre"
95
+ );
96
+ } else {
97
+ await import("@cloudflare/pages-shared/environment-polyfills/miniflare");
98
+ }
99
+
100
+ const miniflare = tre
101
+ ? await import("@miniflare/tre")
102
+ : await import("@miniflare/core");
103
+ const Request = miniflare.Request;
82
104
 
83
105
  const { generateHandler, parseQualityWeightedList } = await import(
84
106
  "@cloudflare/pages-shared/asset-server/handler"
@@ -207,10 +229,10 @@ async function generateAssetsFetch(
207
229
  });
208
230
  };
209
231
 
210
- return async (input: RequestInfo, init?: RequestInit) => {
211
- const request = new MiniflareRequest(input, init);
232
+ return (async (input: TreRequestInfo, init?: TreRequestInit) => {
233
+ const request = new Request(input, init);
212
234
  return await generateResponse(request as unknown as Request);
213
- };
235
+ }) as typeof fetch;
214
236
  }
215
237
 
216
238
  const invalidAssetsFetch: typeof fetch = () => {
@@ -120,6 +120,7 @@ async function main() {
120
120
  log: config.log,
121
121
  proxyPort: opts.proxyPort,
122
122
  directory: opts.directory,
123
+ tre: false,
123
124
  };
124
125
 
125
126
  config.serviceBindings = {
package/src/pages/dev.tsx CHANGED
@@ -134,6 +134,11 @@ export function Options(yargs: Argv) {
134
134
  type: "boolean",
135
135
  hidden: true,
136
136
  },
137
+ "experimental-local": {
138
+ describe: "Run on my machine using the Cloudflare Workers runtime",
139
+ type: "boolean",
140
+ default: false,
141
+ },
137
142
  config: {
138
143
  describe: "Pages does not support wrangler.toml",
139
144
  type: "string",
@@ -168,6 +173,7 @@ export const Handler = async ({
168
173
  persist,
169
174
  persistTo,
170
175
  "node-compat": nodeCompat,
176
+ "experimental-local": experimentalLocal,
171
177
  config: config,
172
178
  _: [_pages, _dev, ...remaining],
173
179
  logLevel,
@@ -476,6 +482,7 @@ export const Handler = async ({
476
482
  compatibilityDate,
477
483
  compatibilityFlags,
478
484
  nodeCompat,
485
+ experimentalLocal,
479
486
  vars: Object.fromEntries(
480
487
  bindings
481
488
  .map((binding) => binding.toString().split("="))
@@ -540,6 +540,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
540
540
  r2_buckets: config.r2_buckets,
541
541
  d1_databases: identifyD1BindingsAsBeta(config.d1_databases),
542
542
  services: config.services,
543
+ analytics_engine_datasets: config.analytics_engine_datasets,
543
544
  dispatch_namespaces: config.dispatch_namespaces,
544
545
  logfwdr: config.logfwdr,
545
546
  unsafe: config.unsafe?.bindings,
@@ -108,6 +108,7 @@ export const secret = (secretYargs: Argv<CommonYargsOptions>) => {
108
108
  r2_buckets: [],
109
109
  d1_databases: [],
110
110
  services: [],
111
+ analytics_engine_datasets: [],
111
112
  wasm_modules: {},
112
113
  text_blobs: {},
113
114
  data_blobs: {},
@@ -59,6 +59,14 @@ export async function generateTypes(
59
59
  }
60
60
  }
61
61
 
62
+ if (configToDTS.analytics_engine_datasets) {
63
+ for (const analyticsEngine of configToDTS.analytics_engine_datasets) {
64
+ envTypeStructure.push(
65
+ `${analyticsEngine.binding}: AnalyticsEngineDataset;`
66
+ );
67
+ }
68
+ }
69
+
62
70
  if (configToDTS.dispatch_namespaces) {
63
71
  for (const namespace of configToDTS.dispatch_namespaces) {
64
72
  envTypeStructure.push(`${namespace.binding}: any;`);
@@ -156,11 +164,11 @@ function writeDTSFile({
156
164
  if (formatType === "modules") {
157
165
  combinedTypeStrings += `interface Env {\n${envTypeStructure
158
166
  .map((value) => `\t${value}`)
159
- .join("\n")} \n}\n${modulesTypeStructure.join("\n")}`;
167
+ .join("\n")}\n}\n${modulesTypeStructure.join("\n")}`;
160
168
  } else {
161
169
  combinedTypeStrings += `export {};\ndeclare global {\n${envTypeStructure
162
170
  .map((value) => `\tconst ${value}`)
163
- .join("\n")} \n}\n${modulesTypeStructure.join("\n")}`;
171
+ .join("\n")}\n}\n${modulesTypeStructure.join("\n")}`;
164
172
  }
165
173
 
166
174
  if (envTypeStructure.length || modulesTypeStructure.length) {
package/src/worker.ts CHANGED
@@ -144,6 +144,11 @@ interface CfService {
144
144
  environment?: string;
145
145
  }
146
146
 
147
+ interface CfAnalyticsEngineDataset {
148
+ binding: string;
149
+ dataset?: string;
150
+ }
151
+
147
152
  interface CfDispatchNamespace {
148
153
  binding: string;
149
154
  namespace: string;
@@ -207,6 +212,7 @@ export interface CfWorkerInit {
207
212
  r2_buckets: CfR2Bucket[] | undefined;
208
213
  d1_databases: CfD1Database[] | undefined;
209
214
  services: CfService[] | undefined;
215
+ analytics_engine_datasets: CfAnalyticsEngineDataset[] | undefined;
210
216
  dispatch_namespaces: CfDispatchNamespace[] | undefined;
211
217
  logfwdr: CfLogfwdr | undefined;
212
218
  unsafe: CfUnsafeBinding[] | undefined;
@@ -527,6 +527,21 @@ declare interface EnvironmentNonInheritable {
527
527
  /** The environment of the service (e.g. production, staging, etc). */
528
528
  environment?: string;
529
529
  }[] | undefined;
530
+ /**
531
+ * Specifies analytics engine datasets that are bound to this Worker environment.
532
+ *
533
+ * NOTE: This field is not automatically inherited from the top level environment,
534
+ * and so must be specified in every named environment.
535
+ *
536
+ * @default `[]`
537
+ * @nonInheritable
538
+ */
539
+ analytics_engine_datasets: {
540
+ /** The binding name used to refer to the dataset in the worker. */
541
+ binding: string;
542
+ /** The name of this dataset to write to. */
543
+ dataset?: string;
544
+ }[];
530
545
  /**
531
546
  * "Unsafe" tables for features that aren't directly supported by wrangler.
532
547
  *