wrangler 2.4.4 → 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.
Files changed (46) hide show
  1. package/bin/wrangler.js +20 -8
  2. package/miniflare-dist/index.mjs +90 -41
  3. package/package.json +3 -3
  4. package/src/__tests__/configuration.test.ts +211 -0
  5. package/src/__tests__/delete.test.ts +81 -48
  6. package/src/__tests__/dev.test.tsx +25 -8
  7. package/src/__tests__/helpers/mock-oauth-flow.ts +5 -1
  8. package/src/__tests__/helpers/msw/handlers/oauth.ts +13 -18
  9. package/src/__tests__/init.test.ts +18 -3
  10. package/src/__tests__/logout.test.ts +47 -0
  11. package/src/__tests__/metrics.test.ts +88 -43
  12. package/src/__tests__/pages-deployment-tail.test.ts +165 -101
  13. package/src/__tests__/publish.test.ts +94 -7
  14. package/src/__tests__/pubsub.test.ts +208 -88
  15. package/src/__tests__/queues.test.ts +155 -67
  16. package/src/__tests__/tail.test.ts +207 -108
  17. package/src/__tests__/type-generation.test.ts +7 -0
  18. package/src/__tests__/user.test.ts +43 -69
  19. package/src/config/environment.ts +16 -0
  20. package/src/config/index.ts +31 -8
  21. package/src/config/validation.ts +49 -0
  22. package/src/create-worker-upload-form.ts +9 -0
  23. package/src/d1/backups.tsx +7 -2
  24. package/src/d1/delete.tsx +4 -4
  25. package/src/d1/index.ts +2 -0
  26. package/src/d1/migrations/apply.tsx +6 -5
  27. package/src/d1/migrations/helpers.ts +4 -2
  28. package/src/d1/migrations/list.tsx +2 -2
  29. package/src/d1/migrations/options.ts +18 -0
  30. package/src/dev/dev.tsx +46 -22
  31. package/src/dev/local.tsx +63 -29
  32. package/src/dev/start-server.ts +18 -21
  33. package/src/dev.tsx +33 -13
  34. package/src/git-client.ts +15 -4
  35. package/src/index.tsx +1 -0
  36. package/src/init.ts +8 -0
  37. package/src/miniflare-cli/assets.ts +55 -28
  38. package/src/miniflare-cli/index.ts +2 -1
  39. package/src/pages/dev.tsx +7 -0
  40. package/src/proxy.ts +5 -0
  41. package/src/publish/publish.ts +1 -0
  42. package/src/secret/index.ts +1 -0
  43. package/src/type-generation.ts +10 -2
  44. package/src/worker.ts +6 -0
  45. package/wrangler-dist/cli.d.ts +15 -0
  46. package/wrangler-dist/cli.js +2045 -636
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,
@@ -57,8 +59,8 @@ export interface LocalProps {
57
59
  bindings: CfWorkerInit["bindings"];
58
60
  workerDefinitions: WorkerRegistry | undefined;
59
61
  assetPaths: AssetPaths | undefined;
60
- port: number;
61
- ip: string;
62
+ initialPort: number;
63
+ initialIp: string;
62
64
  rules: Config["rules"];
63
65
  inspectorPort: number;
64
66
  localPersistencePath: string | null;
@@ -109,12 +111,11 @@ function useLocalWorker({
109
111
  bindings,
110
112
  workerDefinitions,
111
113
  assetPaths,
112
- port,
113
- inspectorPort,
114
+ initialPort,
114
115
  rules,
115
116
  localPersistencePath,
116
117
  liveReload,
117
- ip,
118
+ initialIp,
118
119
  crons,
119
120
  queueConsumers,
120
121
  localProtocol,
@@ -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(port, {
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,12 +196,15 @@ 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
- port,
204
+ port: initialPort,
203
205
  scriptPath,
204
206
  localProtocol,
205
- ip,
207
+ ip: initialIp,
206
208
  format,
207
209
  rules,
208
210
  compatibilityDate,
@@ -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,7 +298,19 @@ function useLocalWorker({
295
298
  return;
296
299
  }
297
300
 
298
- const nodeOptions = setupNodeOptions({ inspect, ip, inspectorPort });
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
+
310
+ const nodeOptions = setupNodeOptions({
311
+ inspect,
312
+ inspectorPort: runtimeInspectorPort,
313
+ });
299
314
  logger.log("⎔ Starting a local server...");
300
315
 
301
316
  const child = (local.current = fork(miniflareCLIPath, forkOptions, {
@@ -316,21 +331,21 @@ function useLocalWorker({
316
331
  await registerWorker(workerName, {
317
332
  protocol: localProtocol,
318
333
  mode: "local",
319
- port,
320
- host: ip,
334
+ port: message.port,
335
+ host: initialIp,
321
336
  durableObjects: internalDurableObjects.map((binding) => ({
322
337
  name: binding.name,
323
338
  className: binding.class_name,
324
339
  })),
325
340
  ...(message.durableObjectsPort
326
341
  ? {
327
- durableObjectsHost: ip,
342
+ durableObjectsHost: initialIp,
328
343
  durableObjectsPort: message.durableObjectsPort,
329
344
  }
330
345
  : {}),
331
346
  });
332
347
  }
333
- onReady?.(ip, message.mfPort);
348
+ onReady?.(initialIp, message.port);
334
349
  }
335
350
  });
336
351
 
@@ -382,7 +397,13 @@ function useLocalWorker({
382
397
  }
383
398
 
384
399
  startLocalWorker().catch((err) => {
385
- 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
+ }
386
407
  });
387
408
 
388
409
  return () => {
@@ -399,9 +420,8 @@ function useLocalWorker({
399
420
  bundle,
400
421
  workerName,
401
422
  format,
402
- port,
403
- inspectorPort,
404
- ip,
423
+ initialPort,
424
+ initialIp,
405
425
  queueConsumers,
406
426
  bindings.queues,
407
427
  bindings.durable_objects,
@@ -729,11 +749,9 @@ export function setupMiniflareOptions({
729
749
 
730
750
  export function setupNodeOptions({
731
751
  inspect,
732
- ip,
733
752
  inspectorPort,
734
753
  }: {
735
754
  inspect: boolean;
736
- ip: string;
737
755
  inspectorPort: number;
738
756
  }) {
739
757
  const nodeOptions = [
@@ -742,7 +760,7 @@ export function setupNodeOptions({
742
760
  // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
743
761
  ];
744
762
  if (inspect) {
745
- 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
746
764
  }
747
765
  return nodeOptions;
748
766
  }
@@ -757,6 +775,8 @@ export interface SetupMiniflare3Options {
757
775
  // Miniflare's logger
758
776
  log: Miniflare3LogType;
759
777
 
778
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
779
+
760
780
  // Miniflare 3 accepts namespace/bucket names in addition to binding names.
761
781
  // This means multiple workers persisting to the same location can have
762
782
  // different binding names for the same namespace/bucket. Therefore, we need
@@ -794,6 +814,7 @@ export async function transformMf2OptionsToMf3Options({
794
814
  format,
795
815
  bundle,
796
816
  log,
817
+ enablePagesAssetsServiceBinding,
797
818
  kvNamespaces,
798
819
  r2Buckets,
799
820
  authenticatedAccountId,
@@ -829,6 +850,19 @@ export async function transformMf2OptionsToMf3Options({
829
850
  log,
830
851
  };
831
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
+
832
866
  if (format === "modules") {
833
867
  // Manually specify all modules from the bundle. If we didn't do this,
834
868
  // Miniflare 3 would try collect them automatically again itself.
@@ -882,5 +916,5 @@ export async function getMiniflare3(): Promise<
882
916
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
883
917
  typeof import("@miniflare/tre")
884
918
  > {
885
- return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.7"));
919
+ return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.8"));
886
920
  }
@@ -112,8 +112,8 @@ export async function startDevServer(
112
112
  compatibilityFlags: props.compatibilityFlags,
113
113
  bindings: props.bindings,
114
114
  assetPaths: props.assetPaths,
115
- port: props.port,
116
- ip: props.ip,
115
+ initialPort: props.initialPort,
116
+ initialIp: props.initialIp,
117
117
  rules: props.rules,
118
118
  inspectorPort: props.inspectorPort,
119
119
  localPersistencePath: props.localPersistencePath,
@@ -149,8 +149,8 @@ export async function startDevServer(
149
149
  bindings: props.bindings,
150
150
  assetPaths: props.assetPaths,
151
151
  isWorkersSite: props.isWorkersSite,
152
- port: props.port,
153
- ip: props.ip,
152
+ port: props.initialPort,
153
+ ip: props.initialIp,
154
154
  localProtocol: props.localProtocol,
155
155
  inspectorPort: props.inspectorPort,
156
156
  inspect: props.inspect,
@@ -294,12 +294,12 @@ export async function startLocalServer({
294
294
  bindings,
295
295
  workerDefinitions,
296
296
  assetPaths,
297
- port,
297
+ initialPort,
298
298
  inspectorPort,
299
299
  rules,
300
300
  localPersistencePath,
301
301
  liveReload,
302
- ip,
302
+ initialIp,
303
303
  crons,
304
304
  queueConsumers,
305
305
  localProtocol,
@@ -325,14 +325,11 @@ export async function startLocalServer({
325
325
  if (!bundle || !format) return;
326
326
 
327
327
  // port for the worker
328
-
329
- if (port !== 0) {
330
- await waitForPortToBeAvailable(port, {
331
- retryPeriod: 200,
332
- timeout: 2000,
333
- abortSignal: abortController.signal,
334
- });
335
- }
328
+ await waitForPortToBeAvailable(initialPort, {
329
+ retryPeriod: 200,
330
+ timeout: 2000,
331
+ abortSignal: abortController.signal,
332
+ });
336
333
 
337
334
  if (bindings.services && bindings.services.length > 0) {
338
335
  logger.warn(
@@ -373,10 +370,10 @@ export async function startLocalServer({
373
370
 
374
371
  const { forkOptions, miniflareCLIPath, options } = setupMiniflareOptions({
375
372
  workerName,
376
- port,
373
+ port: initialPort,
377
374
  scriptPath,
378
375
  localProtocol,
379
- ip,
376
+ ip: initialIp,
380
377
  format,
381
378
  rules,
382
379
  compatibilityDate,
@@ -429,7 +426,7 @@ export async function startLocalServer({
429
426
  return;
430
427
  }
431
428
 
432
- const nodeOptions = setupNodeOptions({ inspect, ip, inspectorPort });
429
+ const nodeOptions = setupNodeOptions({ inspect, inspectorPort });
433
430
  logger.log("⎔ Starting a local server...");
434
431
 
435
432
  const child = (local = fork(miniflareCLIPath, forkOptions, {
@@ -446,21 +443,21 @@ export async function startLocalServer({
446
443
  await registerWorker(workerName, {
447
444
  protocol: localProtocol,
448
445
  mode: "local",
449
- port: message.mfPort,
450
- host: ip,
446
+ port: message.port,
447
+ host: initialIp,
451
448
  durableObjects: internalDurableObjects.map((binding) => ({
452
449
  name: binding.name,
453
450
  className: binding.class_name,
454
451
  })),
455
452
  ...(message.durableObjectsPort
456
453
  ? {
457
- durableObjectsHost: ip,
454
+ durableObjectsHost: initialIp,
458
455
  durableObjectsPort: message.durableObjectsPort,
459
456
  }
460
457
  : {}),
461
458
  });
462
459
  }
463
- onReady?.(ip, message.mfPort);
460
+ onReady?.(initialIp, message.port);
464
461
  }
465
462
  });
466
463
 
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);
@@ -467,11 +476,13 @@ export async function startDev(args: StartDevOptions) {
467
476
  accountId={configParam.account_id || getAccountFromCache()?.id}
468
477
  assetPaths={assetPaths}
469
478
  assetsConfig={configParam.assets}
470
- port={args.port || configParam.dev.port || (await getLocalPort())}
471
- ip={args.ip || configParam.dev.ip}
479
+ initialPort={
480
+ args.port ?? configParam.dev.port ?? (await getLocalPort())
481
+ }
482
+ initialIp={args.ip || configParam.dev.ip}
472
483
  inspectorPort={
473
- args.inspectorPort ||
474
- configParam.dev.inspector_port ||
484
+ args.inspectorPort ??
485
+ configParam.dev.inspector_port ??
475
486
  (await getInspectorPort())
476
487
  }
477
488
  isWorkersSite={Boolean(args.site || configParam.site)}
@@ -584,14 +595,11 @@ export async function startApiDev(args: StartDevOptions) {
584
595
  assetPaths: assetPaths,
585
596
  assetsConfig: configParam.assets,
586
597
  //port can be 0, which means to use a random port
587
- port:
588
- args.port === 0
589
- ? args.port
590
- : args.port || configParam.dev.port || (await getLocalPort()),
591
- ip: args.ip || configParam.dev.ip,
598
+ initialPort: args.port ?? configParam.dev.port ?? (await getLocalPort()),
599
+ initialIp: args.ip || configParam.dev.ip,
592
600
  inspectorPort:
593
- args["inspector-port"] ||
594
- configParam.dev.inspector_port ||
601
+ args["inspector-port"] ??
602
+ configParam.dev.inspector_port ??
595
603
  (await getInspectorPort()),
596
604
  isWorkersSite: Boolean(args.site || configParam.site),
597
605
  compatibilityDate: getDevCompatibilityDate(
@@ -893,10 +901,22 @@ async function getBindings(
893
901
  ],
894
902
  dispatch_namespaces: configParam.dispatch_namespaces,
895
903
  services: configParam.services,
904
+ analytics_engine_datasets: configParam.analytics_engine_datasets,
896
905
  unsafe: configParam.unsafe?.bindings,
897
906
  logfwdr: configParam.logfwdr,
898
907
  d1_databases: identifyD1BindingsAsBeta([
899
- ...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
+ }),
900
920
  ...(args.d1Databases || []),
901
921
  ]),
902
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,54 +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 { fetch as miniflareFetch } from "@miniflare/core";
7
- import {
8
- Response as MiniflareResponse,
9
- Request as MiniflareRequest,
10
- } from "@miniflare/core";
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
- return await miniflareFetch(url, miniflareRequest);
56
+ const proxyRequest = new Request(url, miniflareRequest);
57
+ if (proxyRequest.headers.get("Upgrade") === "websocket") {
58
+ proxyRequest.headers.delete("Sec-WebSocket-Accept");
59
+ proxyRequest.headers.delete("Sec-WebSocket-Key");
60
+ }
61
+ return await fetch(proxyRequest as Request);
44
62
  } catch (thrown) {
45
63
  options.log.error(new Error(`Could not proxy request: ${thrown}`));
46
64
 
47
65
  // TODO: Pretty error page
48
- return new MiniflareResponse(
49
- `[wrangler] Could not proxy request: ${thrown}`,
50
- {
51
- status: 502,
52
- }
53
- );
66
+ return new Response(`[wrangler] Could not proxy request: ${thrown}`, {
67
+ status: 502,
68
+ });
54
69
  }
55
70
  } else {
56
71
  try {
@@ -59,21 +74,33 @@ export default async function generateASSETSBinding(options: Options) {
59
74
  options.log.error(new Error(`Could not serve static asset: ${thrown}`));
60
75
 
61
76
  // TODO: Pretty error page
62
- return new MiniflareResponse(
77
+ return new Response(
63
78
  `[wrangler] Could not serve static asset: ${thrown}`,
64
79
  { status: 502 }
65
80
  );
66
81
  }
67
82
  }
68
- } as FetcherFetch;
83
+ };
69
84
  }
70
85
 
71
86
  async function generateAssetsFetch(
72
87
  directory: string,
73
- log: Log
88
+ log: Logger,
89
+ tre: boolean
74
90
  ): Promise<typeof fetch> {
75
91
  // Defer importing miniflare until we really need it
76
- 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;
77
104
 
78
105
  const { generateHandler, parseQualityWeightedList } = await import(
79
106
  "@cloudflare/pages-shared/asset-server/handler"
@@ -202,10 +229,10 @@ async function generateAssetsFetch(
202
229
  });
203
230
  };
204
231
 
205
- return async (input: RequestInfo, init?: RequestInit) => {
206
- const request = new MiniflareRequest(input, init);
232
+ return (async (input: TreRequestInfo, init?: TreRequestInit) => {
233
+ const request = new Request(input, init);
207
234
  return await generateResponse(request as unknown as Request);
208
- };
235
+ }) as typeof fetch;
209
236
  }
210
237
 
211
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 = {
@@ -196,7 +197,7 @@ async function main() {
196
197
  process.send &&
197
198
  process.send(
198
199
  JSON.stringify({
199
- mfPort: mfPort,
200
+ port: mfPort,
200
201
  ready: true,
201
202
  durableObjectsPort: durableObjectsMfPort,
202
203
  })
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("="))
package/src/proxy.ts CHANGED
@@ -651,6 +651,11 @@ export async function waitForPortToBeAvailable(
651
651
  }
652
652
 
653
653
  function checkPort() {
654
+ if (port === 0) {
655
+ doResolve();
656
+ return;
657
+ }
658
+
654
659
  // Testing whether a port is 'available' involves simply
655
660
  // trying to make a server listen on that port, and retrying
656
661
  // until it succeeds.