wrangler 2.1.4 → 2.1.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.
Files changed (46) hide show
  1. package/miniflare-dist/index.mjs +4 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/api-dev.test.ts +3 -3
  4. package/src/__tests__/api-devregistry.test.js +56 -0
  5. package/src/__tests__/configuration.test.ts +3 -0
  6. package/src/__tests__/dev.test.tsx +39 -1
  7. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  8. package/src/__tests__/helpers/{hello-world-worker.js → worker-scripts/hello-world-worker.js} +0 -0
  9. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  10. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  11. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  12. package/src/__tests__/init.test.ts +72 -0
  13. package/src/__tests__/middleware.scheduled.test.ts +135 -0
  14. package/src/__tests__/middleware.test.ts +703 -745
  15. package/src/__tests__/pages.test.ts +35 -40
  16. package/src/__tests__/publish.test.ts +57 -1
  17. package/src/api/dev.ts +2 -0
  18. package/src/bundle.ts +14 -16
  19. package/src/config/config.ts +12 -0
  20. package/src/config/validation.ts +9 -0
  21. package/src/create-worker-upload-form.ts +3 -2
  22. package/src/dev/dev.tsx +12 -21
  23. package/src/dev/local.tsx +1 -0
  24. package/src/dev/remote.tsx +38 -50
  25. package/src/dev/start-server.ts +43 -8
  26. package/src/dev/use-esbuild.ts +4 -0
  27. package/src/dev-registry.tsx +30 -0
  28. package/src/dev.tsx +21 -3
  29. package/src/index.tsx +8 -1
  30. package/src/init.ts +3 -1
  31. package/src/inspect.ts +26 -26
  32. package/src/miniflare-cli/assets.ts +8 -1
  33. package/src/pages/constants.ts +2 -1
  34. package/src/pages/dev.tsx +133 -10
  35. package/src/pages/errors.ts +48 -4
  36. package/src/pages/functions/routes-transformation.ts +1 -14
  37. package/src/pages/functions/routes-validation.test.ts +403 -0
  38. package/src/pages/functions/routes-validation.ts +202 -0
  39. package/src/pages/functions.tsx +4 -18
  40. package/src/pages/publish.tsx +6 -22
  41. package/src/publish.ts +3 -1
  42. package/src/worker.ts +1 -1
  43. package/templates/middleware/middleware-scheduled.ts +2 -1
  44. package/templates/pages-dev-pipeline.ts +35 -0
  45. package/wrangler-dist/cli.d.ts +2 -0
  46. package/wrangler-dist/cli.js +611 -353
@@ -2,10 +2,16 @@ import { fork } from "node:child_process";
2
2
  import { realpathSync } from "node:fs";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  import * as path from "node:path";
5
+ import * as util from "node:util";
5
6
  import onExit from "signal-exit";
6
7
  import tmp from "tmp-promise";
7
8
  import { bundleWorker } from "../bundle";
8
- import { registerWorker } from "../dev-registry";
9
+ import {
10
+ getBoundRegisteredWorkers,
11
+ registerWorker,
12
+ startWorkerRegistry,
13
+ stopWorkerRegistry,
14
+ } from "../dev-registry";
9
15
  import { runCustomBuild } from "../entry";
10
16
  import { logger } from "../logger";
11
17
  import { waitForPortToBeAvailable } from "../proxy";
@@ -17,6 +23,7 @@ import {
17
23
  import { validateDevProps } from "./validate-dev-props";
18
24
 
19
25
  import type { Config } from "../config";
26
+ import type { WorkerRegistry } from "../dev-registry";
20
27
  import type { Entry } from "../entry";
21
28
  import type { DevProps, DirectorySyncResult } from "./dev";
22
29
  import type { LocalProps } from "./local";
@@ -30,6 +37,7 @@ export async function startDevServer(
30
37
  }
31
38
  ) {
32
39
  try {
40
+ let workerDefinitions: WorkerRegistry = {};
33
41
  validateDevProps(props);
34
42
 
35
43
  if (props.build.command) {
@@ -48,6 +56,21 @@ export async function startDevServer(
48
56
  throw new Error("Failed to create temporary directory.");
49
57
  }
50
58
 
59
+ //start the worker registry
60
+ startWorkerRegistry().catch((err) => {
61
+ logger.error("failed to start worker registry", err);
62
+ });
63
+ if (props.local) {
64
+ const boundRegisteredWorkers = await getBoundRegisteredWorkers({
65
+ services: props.bindings.services,
66
+ durableObjects: props.bindings.durable_objects,
67
+ });
68
+
69
+ if (!util.isDeepStrictEqual(boundRegisteredWorkers, workerDefinitions)) {
70
+ workerDefinitions = boundRegisteredWorkers || {};
71
+ }
72
+ }
73
+
51
74
  //implement a react-free version of useEsbuild
52
75
  const bundle = await runEsbuild({
53
76
  entry: props.entry,
@@ -64,7 +87,10 @@ export async function startDevServer(
64
87
  define: props.define,
65
88
  noBundle: props.noBundle,
66
89
  assets: props.assetsConfig,
90
+ workerDefinitions,
67
91
  services: props.bindings.services,
92
+ firstPartyWorkerDevFacade: props.firstPartyWorker,
93
+ testScheduled: props.testScheduled,
68
94
  });
69
95
 
70
96
  //run local now
@@ -90,13 +116,14 @@ export async function startDevServer(
90
116
  inspect: props.inspect,
91
117
  onReady: props.onReady,
92
118
  enablePagesAssetsServiceBinding: props.enablePagesAssetsServiceBinding,
93
- usageModel: undefined,
94
- workerDefinitions: undefined,
119
+ usageModel: props.usageModel,
120
+ workerDefinitions,
95
121
  });
96
122
 
97
123
  return {
98
124
  stop: async () => {
99
125
  stop();
126
+ await stopWorkerRegistry();
100
127
  },
101
128
  inspectorUrl,
102
129
  };
@@ -129,6 +156,10 @@ async function runEsbuild({
129
156
  nodeCompat,
130
157
  define,
131
158
  noBundle,
159
+ workerDefinitions,
160
+ services,
161
+ firstPartyWorkerDevFacade,
162
+ testScheduled,
132
163
  }: {
133
164
  entry: Entry;
134
165
  destination: string | undefined;
@@ -143,6 +174,9 @@ async function runEsbuild({
143
174
  minify: boolean | undefined;
144
175
  nodeCompat: boolean | undefined;
145
176
  noBundle: boolean;
177
+ workerDefinitions: WorkerRegistry;
178
+ firstPartyWorkerDevFacade: boolean | undefined;
179
+ testScheduled?: boolean;
146
180
  }): Promise<EsbuildBundle | undefined> {
147
181
  if (!destination) return;
148
182
 
@@ -174,10 +208,11 @@ async function runEsbuild({
174
208
  // disable the cache in dev
175
209
  bypassCache: true,
176
210
  },
177
- services: undefined,
178
- workerDefinitions: undefined,
179
- firstPartyWorkerDevFacade: undefined,
211
+ workerDefinitions,
212
+ services,
213
+ firstPartyWorkerDevFacade,
180
214
  targetConsumer: "dev", // We are starting a dev server
215
+ testScheduled,
181
216
  });
182
217
 
183
218
  return {
@@ -237,8 +272,8 @@ export async function startLocalServer({
237
272
  }
238
273
 
239
274
  if (bindings.services && bindings.services.length > 0) {
240
- throw new Error(
241
- "⎔ Service bindings are not yet supported in local mode."
275
+ logger.warn(
276
+ "⎔ Support for service bindings in local mode is experimental and may change."
242
277
  );
243
278
  }
244
279
 
@@ -37,6 +37,7 @@ export function useEsbuild({
37
37
  durableObjects,
38
38
  firstPartyWorkerDevFacade,
39
39
  targetConsumer,
40
+ testScheduled,
40
41
  }: {
41
42
  entry: Entry;
42
43
  destination: string | undefined;
@@ -55,6 +56,7 @@ export function useEsbuild({
55
56
  durableObjects: Config["durable_objects"];
56
57
  firstPartyWorkerDevFacade: boolean | undefined;
57
58
  targetConsumer: "dev" | "publish";
59
+ testScheduled: boolean;
58
60
  }): EsbuildBundle | undefined {
59
61
  const [bundle, setBundle] = useState<EsbuildBundle>();
60
62
  const { exit } = useApp();
@@ -119,6 +121,7 @@ export function useEsbuild({
119
121
  services,
120
122
  firstPartyWorkerDevFacade,
121
123
  targetConsumer,
124
+ testScheduled,
122
125
  });
123
126
 
124
127
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -177,6 +180,7 @@ export function useEsbuild({
177
180
  workerDefinitions,
178
181
  firstPartyWorkerDevFacade,
179
182
  targetConsumer,
183
+ testScheduled,
180
184
  ]);
181
185
  return bundle;
182
186
  }
@@ -5,6 +5,8 @@ import express from "express";
5
5
  import { createHttpTerminator } from "http-terminator";
6
6
  import { fetch } from "undici";
7
7
  import { logger } from "./logger";
8
+
9
+ import type { Config } from "./config";
8
10
  import type { Server } from "http";
9
11
  import type { HttpTerminator } from "http-terminator";
10
12
 
@@ -158,3 +160,31 @@ export async function getRegisteredWorkers(): Promise<
158
160
  }
159
161
  }
160
162
  }
163
+
164
+ /**
165
+ * a function that takes your serviceNames and durableObjectNames and returns a
166
+ * list of the running workers that we're bound to
167
+ */
168
+ export async function getBoundRegisteredWorkers({
169
+ services,
170
+ durableObjects,
171
+ }: {
172
+ services: Config["services"] | undefined;
173
+ durableObjects: Config["durable_objects"] | undefined;
174
+ }) {
175
+ const serviceNames = (services || []).map(
176
+ (serviceBinding) => serviceBinding.service
177
+ );
178
+ const durableObjectServices = (
179
+ durableObjects || { bindings: [] }
180
+ ).bindings.map((durableObjectBinding) => durableObjectBinding.script_name);
181
+
182
+ const workerDefinitions = await getRegisteredWorkers();
183
+ const filteredWorkers = Object.fromEntries(
184
+ Object.entries(workerDefinitions || {}).filter(
185
+ ([key, _value]) =>
186
+ serviceNames.includes(key) || durableObjectServices.includes(key)
187
+ )
188
+ );
189
+ return filteredWorkers;
190
+ }
package/src/dev.tsx CHANGED
@@ -72,6 +72,7 @@ interface DevArgs {
72
72
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
73
73
  logPrefix?: string;
74
74
  showInteractiveDevSession?: boolean;
75
+ "test-scheduled"?: boolean;
75
76
  }
76
77
 
77
78
  export function devOptions(yargs: Argv): Argv<DevArgs> {
@@ -273,6 +274,17 @@ export function devOptions(yargs: Argv): Argv<DevArgs> {
273
274
  describe: "Use legacy environments",
274
275
  hidden: true,
275
276
  })
277
+ .option("test-scheduled", {
278
+ describe: "Test scheduled events by visiting /__scheduled in browser",
279
+ type: "boolean",
280
+ default: false,
281
+ })
282
+ .option("log-level", {
283
+ // "none" will currently default to "error" for Wrangler Logger
284
+ choices: ["debug", "info", "log", "warn", "error", "none"] as const,
285
+ describe: "Specify logging level",
286
+ default: "log",
287
+ })
276
288
  );
277
289
  }
278
290
 
@@ -309,6 +321,7 @@ export type AdditionalDevProps = {
309
321
  preview_bucket_name?: string;
310
322
  }[];
311
323
  };
324
+
312
325
  type StartDevOptions = ArgumentsCamelCase<DevArgs> &
313
326
  // These options can be passed in directly when called with the `wrangler.dev()` API.
314
327
  // They aren't exposed as CLI arguments.
@@ -430,6 +443,7 @@ export async function startDev(args: StartDevOptions) {
430
443
  enablePagesAssetsServiceBinding={args.enablePagesAssetsServiceBinding}
431
444
  firstPartyWorker={configParam.first_party_worker}
432
445
  sendMetrics={configParam.send_metrics}
446
+ testScheduled={args["test-scheduled"]}
433
447
  />
434
448
  );
435
449
  }
@@ -541,8 +555,9 @@ export async function startApiDev(args: StartDevOptions) {
541
555
  forceLocal: args.forceLocal,
542
556
  enablePagesAssetsServiceBinding: args.enablePagesAssetsServiceBinding,
543
557
  local: true,
544
- firstPartyWorker: undefined,
545
- sendMetrics: undefined,
558
+ firstPartyWorker: configParam.first_party_worker,
559
+ sendMetrics: configParam.send_metrics,
560
+ testScheduled: args.testScheduled,
546
561
  });
547
562
  }
548
563
 
@@ -699,7 +714,10 @@ async function validateDevServerSettings(
699
714
  : args.persist
700
715
  ? // If just flagged on, treat it as relative to wrangler.toml,
701
716
  // if one can be found, otherwise cwd()
702
- path.resolve(config.configPath || process.cwd(), ".wrangler/state")
717
+ path.resolve(
718
+ config.configPath ? path.dirname(config.configPath) : process.cwd(),
719
+ ".wrangler/state"
720
+ )
703
721
  : null;
704
722
 
705
723
  const cliDefines =
package/src/index.tsx CHANGED
@@ -527,6 +527,12 @@ function createCLIParser(argv: string[]) {
527
527
  describe: "Don't actually publish",
528
528
  type: "boolean",
529
529
  })
530
+ .option("keep-vars", {
531
+ describe:
532
+ "Stop wrangler from deleting vars that are not present in the wrangler.toml\nBy default Wrangler will remove all vars and replace them with those found in the wrangler.toml configuration.\nIf your development approach is to modify vars after deployment via the dashboard you may wish to set this flag.",
533
+ default: false,
534
+ type: "boolean",
535
+ })
530
536
  .option("legacy-env", {
531
537
  type: "boolean",
532
538
  describe: "Use legacy environments",
@@ -631,6 +637,7 @@ function createCLIParser(argv: string[]) {
631
637
  outDir: args.outdir,
632
638
  dryRun: args.dryRun,
633
639
  noBundle: !(args.bundle ?? !config.no_bundle),
640
+ keepVars: args.keepVars,
634
641
  });
635
642
  }
636
643
  );
@@ -1021,7 +1028,7 @@ function createCLIParser(argv: string[]) {
1021
1028
  compatibility_date: undefined,
1022
1029
  compatibility_flags: undefined,
1023
1030
  usage_model: undefined,
1024
- keep_bindings: false, // this doesn't matter since it's a new script anyway
1031
+ keepVars: false, // this doesn't matter since it's a new script anyway
1025
1032
  }),
1026
1033
  }
1027
1034
  );
package/src/init.ts CHANGED
@@ -941,7 +941,9 @@ async function getWorkerConfig(
941
941
  : { route: routeOrRoutes[0] };
942
942
 
943
943
  return {
944
- compatibility_date: serviceEnvMetadata.script.compatibility_date,
944
+ compatibility_date:
945
+ serviceEnvMetadata.script.compatibility_date ??
946
+ new Date().toISOString().substring(0, 10),
945
947
  ...routeOrRoutesToConfig,
946
948
  usage_model: serviceEnvMetadata.script.usage_model,
947
949
  migrations: [
package/src/inspect.ts CHANGED
@@ -610,36 +610,36 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
610
610
  break;
611
611
  case "weakmap":
612
612
  case "map":
613
- args.push(
614
- "{\n" +
615
- // Maps always have entries
616
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
617
- ro.preview
618
- .entries!.map(({ key, value }) => {
619
- return ` ${key?.description ?? "<unknown>"} => ${
620
- value.description
621
- }`;
622
- })
623
- .join(",\n") +
624
- (ro.preview.overflow ? "\n ..." : "") +
625
- "\n}"
626
- );
613
+ ro.preview.entries === undefined
614
+ ? args.push("{}")
615
+ : args.push(
616
+ "{\n" +
617
+ ro.preview.entries
618
+ .map(({ key, value }) => {
619
+ return ` ${key?.description ?? "<unknown>"} => ${
620
+ value.description
621
+ }`;
622
+ })
623
+ .join(",\n") +
624
+ (ro.preview.overflow ? "\n ..." : "") +
625
+ "\n}"
626
+ );
627
627
 
628
628
  break;
629
629
  case "weakset":
630
630
  case "set":
631
- args.push(
632
- "{ " +
633
- // Sets always have entries
634
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
635
- ro.preview
636
- .entries!.map(({ value }) => {
637
- return `${value.description}`;
638
- })
639
- .join(", ") +
640
- (ro.preview.overflow ? ", ..." : "") +
641
- " }"
642
- );
631
+ ro.preview.entries === undefined
632
+ ? args.push("{}")
633
+ : args.push(
634
+ "{ " +
635
+ ro.preview.entries
636
+ .map(({ value }) => {
637
+ return `${value.description}`;
638
+ })
639
+ .join(", ") +
640
+ (ro.preview.overflow ? ", ..." : "") +
641
+ " }"
642
+ );
643
643
  break;
644
644
  case "regexp":
645
645
  break;
@@ -189,7 +189,14 @@ async function generateAssetsFetch(
189
189
  }
190
190
  const body = readFileSync(filepath) as unknown as ReadableStream;
191
191
 
192
- const contentType = getType(filepath) || "application/octet-stream";
192
+ let contentType = getType(filepath) || "application/octet-stream";
193
+ if (
194
+ contentType.startsWith("text/") &&
195
+ !contentType.includes("charset")
196
+ ) {
197
+ contentType = `${contentType}; charset=utf-8`;
198
+ }
199
+
193
200
  return { body, contentType };
194
201
  },
195
202
  });
@@ -8,8 +8,9 @@ export const MAX_UPLOAD_ATTEMPTS = 5;
8
8
  export const MAX_CHECK_MISSING_ATTEMPTS = 5;
9
9
  export const SECONDS_TO_WAIT_FOR_PROXY = 5;
10
10
  export const isInPagesCI = !!process.env.CF_PAGES;
11
- /** The max number of rules in _routes.json */
11
+ /** Max number of rules in _routes.json */
12
12
  export const MAX_FUNCTIONS_ROUTES_RULES = 100;
13
+ /** Max char length of each rule in _routes.json */
13
14
  export const MAX_FUNCTIONS_ROUTES_RULE_LENGTH = 100;
14
15
  export const ROUTES_SPEC_VERSION = 1;
15
16
  export const ROUTES_SPEC_DESCRIPTION = `Generated by wrangler@${wranglerVersion}`;
package/src/pages/dev.tsx CHANGED
@@ -1,21 +1,23 @@
1
1
  import { execSync, spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { watch } from "chokidar";
6
- import { build as workerJsBuild } from "esbuild";
6
+ import * as esbuild from "esbuild";
7
7
  import { unstable_dev } from "../api";
8
+ import { esbuildAliasExternalPlugin } from "../bundle";
8
9
  import { FatalError } from "../errors";
9
10
  import { logger } from "../logger";
10
11
  import * as metrics from "../metrics";
11
12
  import { getBasePath } from "../paths";
12
13
  import { buildFunctions } from "./build";
13
- import { SECONDS_TO_WAIT_FOR_PROXY } from "./constants";
14
+ import { ROUTES_SPEC_VERSION, SECONDS_TO_WAIT_FOR_PROXY } from "./constants";
14
15
  import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
16
+ import { validateRoutes } from "./functions/routes-validation";
15
17
  import { CLEANUP, CLEANUP_CALLBACKS, pagesBetaWarning } from "./utils";
16
18
  import type { AdditionalDevProps } from "../dev";
19
+ import type { RoutesJSONSpec } from "./functions/routes-transformation";
17
20
  import type { YargsOptionsToInterface } from "./types";
18
- import type { Plugin } from "esbuild";
19
21
  import type { Argv } from "yargs";
20
22
 
21
23
  const DURABLE_OBJECTS_BINDING_REGEXP = new RegExp(
@@ -144,6 +146,13 @@ export function Options(yargs: Argv) {
144
146
  type: "string",
145
147
  hidden: true,
146
148
  },
149
+
150
+ "log-level": {
151
+ // "none" will currently default to "error" for Wrangler Logger
152
+ choices: ["debug", "info", "log", "warn", "error", "none"] as const,
153
+ describe: "Specify logging level",
154
+ default: "log",
155
+ },
147
156
  })
148
157
  .epilogue(pagesBetaWarning);
149
158
  }
@@ -170,10 +179,21 @@ export const Handler = async ({
170
179
  "node-compat": nodeCompat,
171
180
  config: config,
172
181
  _: [_pages, _dev, ...remaining],
182
+ logLevel,
173
183
  }: PagesDevArgs) => {
174
184
  // Beta message for `wrangler pages <commands>` usage
175
185
  logger.log(pagesBetaWarning);
176
186
 
187
+ type LogLevelArg = "debug" | "info" | "log" | "warn" | "error" | "none";
188
+ if (logLevel) {
189
+ // we don't define a "none" logLevel, so "error" will do for now.
190
+ // The YargsOptionsToInterface doesn't handle the passing in of Unions from choices in Yargs
191
+ logger.loggerLevel =
192
+ (logLevel as LogLevelArg) === "none"
193
+ ? "error"
194
+ : (logLevel as Exclude<"none", LogLevelArg>);
195
+ }
196
+
177
197
  if (!local) {
178
198
  throw new FatalError("Only local mode is supported at the moment.", 1);
179
199
  }
@@ -184,6 +204,7 @@ export const Handler = async ({
184
204
 
185
205
  const functionsDirectory = "./functions";
186
206
  let usingFunctions = existsSync(functionsDirectory);
207
+ let usingWorkerScript = false;
187
208
 
188
209
  const command = remaining;
189
210
 
@@ -285,6 +306,7 @@ export const Handler = async ({
285
306
  }
286
307
  }
287
308
  }
309
+
288
310
  // Depending on the result of building Functions, we may not actually be using
289
311
  // Functions even if the directory exists.
290
312
  if (!usingFunctions) {
@@ -295,14 +317,15 @@ export const Handler = async ({
295
317
  directory !== undefined
296
318
  ? join(directory, singleWorkerScriptPath)
297
319
  : singleWorkerScriptPath;
320
+ usingWorkerScript = existsSync(scriptPath);
298
321
 
299
- if (!existsSync(scriptPath)) {
322
+ if (!usingWorkerScript) {
300
323
  logger.log("No functions. Shimming...");
301
324
  scriptPath = resolve(getBasePath(), "templates/pages-shim.ts");
302
325
  } else {
303
326
  const runBuild = async () => {
304
327
  try {
305
- await workerJsBuild({
328
+ await esbuild.build({
306
329
  entryPoints: [scriptPath],
307
330
  write: false,
308
331
  plugins: [blockWorkerJsImports],
@@ -330,8 +353,108 @@ export const Handler = async ({
330
353
  );
331
354
  }
332
355
 
356
+ let entrypoint = scriptPath;
357
+
358
+ // custom _routes.json apply only to Functions or Advanced Mode Pages projects
359
+ if (directory && (usingFunctions || usingWorkerScript)) {
360
+ const routesJSONPath = join(directory, "_routes.json");
361
+
362
+ if (existsSync(routesJSONPath)) {
363
+ let routesJSONContents: string;
364
+ const runBuild = async (
365
+ entrypointFile: string,
366
+ outfile: string,
367
+ routes: string
368
+ ) => {
369
+ await esbuild.build({
370
+ entryPoints: [
371
+ resolve(getBasePath(), "templates/pages-dev-pipeline.ts"),
372
+ ],
373
+ bundle: true,
374
+ sourcemap: true,
375
+ format: "esm",
376
+ plugins: [
377
+ esbuildAliasExternalPlugin({
378
+ __ENTRY_POINT__: entrypointFile,
379
+ }),
380
+ ],
381
+ outfile,
382
+ define: {
383
+ __ROUTES__: routes,
384
+ },
385
+ });
386
+ };
387
+
388
+ try {
389
+ // always run the routes validation first. If _routes.json is invalid we
390
+ // want to throw accordingly and exit.
391
+ routesJSONContents = readFileSync(routesJSONPath, "utf-8");
392
+ validateRoutes(JSON.parse(routesJSONContents), directory);
393
+
394
+ entrypoint = join(
395
+ tmpdir(),
396
+ `${Math.random().toString(36).slice(2)}.js`
397
+ );
398
+ await runBuild(scriptPath, entrypoint, routesJSONContents);
399
+ } catch (err) {
400
+ if (err instanceof FatalError) {
401
+ throw err;
402
+ } else {
403
+ throw new FatalError(
404
+ `Could not validate _routes.json at ${directory}: ${err}`,
405
+ 1
406
+ );
407
+ }
408
+ }
409
+
410
+ watch([routesJSONPath], {
411
+ persistent: true,
412
+ ignoreInitial: true,
413
+ }).on("all", async () => {
414
+ try {
415
+ /**
416
+ * Watch for _routes.json file changes and validate file each time.
417
+ * If file is valid proceed to running the build.
418
+ */
419
+ routesJSONContents = readFileSync(routesJSONPath, "utf-8");
420
+ validateRoutes(JSON.parse(routesJSONContents), directory as string);
421
+ await runBuild(scriptPath, entrypoint, routesJSONContents);
422
+ } catch (err) {
423
+ /**
424
+ * If _routes.json is invalid, don't exit but instead fallback to a sensible default
425
+ * and continue to serve the assets. At the same time make sure we warn users that we
426
+ * we detected an invalid file and that we'll be using a default.
427
+ * This basically equivalates to serving a Functions or _worker.js project as is,
428
+ * without applying any additional routing rules on top.
429
+ */
430
+ const error =
431
+ err instanceof FatalError
432
+ ? err
433
+ : `Could not validate _routes.json at ${directory}: ${err}`;
434
+ const defaultRoutesJSONSpec: RoutesJSONSpec = {
435
+ version: ROUTES_SPEC_VERSION,
436
+ include: ["/*"],
437
+ exclude: [],
438
+ };
439
+
440
+ logger.error(error);
441
+ logger.warn(
442
+ `Falling back to the following _routes.json default: ${JSON.stringify(
443
+ defaultRoutesJSONSpec,
444
+ null,
445
+ 2
446
+ )}`
447
+ );
448
+
449
+ routesJSONContents = JSON.stringify(defaultRoutesJSONSpec);
450
+ await runBuild(scriptPath, entrypoint, routesJSONContents);
451
+ }
452
+ });
453
+ }
454
+ }
455
+
333
456
  const { stop, waitUntilExit } = await unstable_dev(
334
- scriptPath,
457
+ entrypoint,
335
458
  {
336
459
  ip,
337
460
  port,
@@ -385,7 +508,7 @@ export const Handler = async ({
385
508
  persistTo,
386
509
  showInteractiveDevSession: undefined,
387
510
  inspect: true,
388
- logLevel: "error",
511
+ logLevel: "warn",
389
512
  logPrefix: "pages",
390
513
  },
391
514
  { testMode: false, disableExperimentalWarning: true }
@@ -544,9 +667,9 @@ async function spawnProxyProcess({
544
667
  return port;
545
668
  }
546
669
 
547
- const blockWorkerJsImports: Plugin = {
670
+ const blockWorkerJsImports: esbuild.Plugin = {
548
671
  name: "block-worker-js-imports",
549
- setup(build) {
672
+ setup(build: esbuild.PluginBuild) {
550
673
  build.onResolve({ filter: /.*/g }, (_args) => {
551
674
  logger.error(
552
675
  `_worker.js is importing from another file. This will throw an error if deployed.\nYou should bundle your Worker or remove the import if it is unused.`