wrangler 2.0.25 → 2.0.26

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.
@@ -3,6 +3,7 @@
3
3
  import * as Build from "./build";
4
4
  import * as Deployments from "./deployments";
5
5
  import * as Dev from "./dev";
6
+ import * as Functions from "./functions";
6
7
  import * as Projects from "./projects";
7
8
  import * as Publish from "./publish";
8
9
  import * as Upload from "./upload";
@@ -19,66 +20,75 @@ process.on("SIGTERM", () => {
19
20
  });
20
21
 
21
22
  export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
22
- return yargs
23
- .command(
24
- "dev [directory] [-- command..]",
25
- "🧑‍💻 Develop your full-stack Pages application locally",
26
- Dev.Options,
27
- Dev.Handler
28
- )
29
- .command("functions", false, (yargs) =>
30
- // we hide this command from help output because
31
- // it's not meant to be used directly right now
32
- {
33
- return yargs.command(
34
- "build [directory]",
35
- "Compile a folder of Cloudflare Pages Functions into a single Worker",
36
- Build.Options,
37
- Build.Handler
38
- );
39
- }
40
- )
41
- .command("project", "⚡️ Interact with your Pages projects", (yargs) =>
42
- yargs
43
- .command(
44
- "list",
45
- "List your Cloudflare Pages projects",
46
- Projects.ListOptions,
47
- Projects.ListHandler
48
- )
49
- .command(
50
- "create [project-name]",
51
- "Create a new Cloudflare Pages project",
52
- Projects.CreateOptions,
53
- Projects.CreateHandler
54
- )
55
- .command("upload [directory]", false, Upload.Options, Upload.Handler)
56
- .epilogue(pagesBetaWarning)
57
- )
58
- .command(
59
- "deployment",
60
- "🚀 Interact with the deployments of a project",
61
- (yargs) =>
23
+ return (
24
+ yargs
25
+ .command(
26
+ "dev [directory] [-- command..]",
27
+ "🧑‍💻 Develop your full-stack Pages application locally",
28
+ Dev.Options,
29
+ Dev.Handler
30
+ )
31
+ /**
32
+ * `wrangler pages functions` is meant for internal use only for now,
33
+ * so let's hide this command from the help output
34
+ */
35
+ .command("functions", false, (yargs) =>
36
+ yargs
37
+ .command(
38
+ "build [directory]",
39
+ "Compile a folder of Cloudflare Pages Functions into a single Worker",
40
+ Build.Options,
41
+ Build.Handler
42
+ )
43
+ .command(
44
+ "optimize-routes [routesPath] [outputRoutesPath]",
45
+ "Consolidate and optimize the route paths declared in _routes.json",
46
+ Functions.OptimizeRoutesOptions,
47
+ Functions.OptimizeRoutesHandler
48
+ )
49
+ )
50
+ .command("project", "⚡️ Interact with your Pages projects", (yargs) =>
62
51
  yargs
63
52
  .command(
64
53
  "list",
65
- "List deployments in your Cloudflare Pages project",
66
- Deployments.ListOptions,
67
- Deployments.ListHandler
54
+ "List your Cloudflare Pages projects",
55
+ Projects.ListOptions,
56
+ Projects.ListHandler
68
57
  )
69
58
  .command(
70
- "create [directory]",
71
- "🆙 Publish a directory of static assets as a Pages deployment",
72
- Publish.Options,
73
- Publish.Handler
59
+ "create [project-name]",
60
+ "Create a new Cloudflare Pages project",
61
+ Projects.CreateOptions,
62
+ Projects.CreateHandler
74
63
  )
64
+ .command("upload [directory]", false, Upload.Options, Upload.Handler)
75
65
  .epilogue(pagesBetaWarning)
76
- )
77
- .command(
78
- "publish [directory]",
79
- "🆙 Publish a directory of static assets as a Pages deployment",
80
- Publish.Options,
81
- Publish.Handler
82
- )
83
- .epilogue(pagesBetaWarning);
66
+ )
67
+ .command(
68
+ "deployment",
69
+ "🚀 Interact with the deployments of a project",
70
+ (yargs) =>
71
+ yargs
72
+ .command(
73
+ "list",
74
+ "List deployments in your Cloudflare Pages project",
75
+ Deployments.ListOptions,
76
+ Deployments.ListHandler
77
+ )
78
+ .command(
79
+ "create [directory]",
80
+ "🆙 Publish a directory of static assets as a Pages deployment",
81
+ Publish.Options,
82
+ Publish.Handler
83
+ )
84
+ .epilogue(pagesBetaWarning)
85
+ )
86
+ .command(
87
+ "publish [directory]",
88
+ "🆙 Publish a directory of static assets as a Pages deployment",
89
+ Publish.Options,
90
+ Publish.Handler
91
+ )
92
+ .epilogue(pagesBetaWarning)
93
+ );
84
94
  };
@@ -16,6 +16,7 @@ import * as metrics from "../metrics";
16
16
  import { requireAuth } from "../user";
17
17
  import { buildFunctions } from "./build";
18
18
  import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
19
+ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
19
20
  import {
20
21
  isRoutesJSONSpec,
21
22
  optimizeRoutesJSONSpec,
@@ -256,18 +257,24 @@ export const Handler = async ({
256
257
  const routesOutputPath = join(tmpdir(), `_routes-${Math.random()}.json`);
257
258
  if (existsSync(functionsDirectory)) {
258
259
  const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
259
-
260
- await new Promise((resolve) =>
261
- buildFunctions({
260
+ try {
261
+ await buildFunctions({
262
262
  outfile,
263
263
  functionsDirectory,
264
- onEnd: () => resolve(null),
264
+ onEnd: () => {},
265
265
  buildOutputDirectory: dirname(outfile),
266
266
  routesOutputPath,
267
- })
268
- );
269
-
270
- builtFunctions = readFileSync(outfile, "utf-8");
267
+ });
268
+ builtFunctions = readFileSync(outfile, "utf-8");
269
+ } catch (e) {
270
+ if (e instanceof FunctionsNoRoutesError) {
271
+ logger.warn(
272
+ getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
273
+ );
274
+ } else {
275
+ throw e;
276
+ }
277
+ }
271
278
  }
272
279
 
273
280
  const manifest = await upload({ directory, accountId, projectName });
@@ -305,32 +312,34 @@ export const Handler = async ({
305
312
  _redirects = readFileSync(join(directory, "_redirects"), "utf-8");
306
313
  } catch {}
307
314
 
308
- try {
309
- _routes = readFileSync(routesOutputPath, "utf-8");
310
- } catch {}
311
-
312
315
  try {
313
316
  _workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
314
317
  } catch {}
315
318
 
316
319
  if (_headers) {
317
320
  formData.append("_headers", new File([_headers], "_headers"));
321
+ logger.log(`✨ Uploading _headers`);
318
322
  }
319
323
 
320
324
  if (_redirects) {
321
325
  formData.append("_redirects", new File([_redirects], "_redirects"));
322
- }
323
-
324
- if (_routes) {
325
- formData.append("_routes.json", new File([_routes], "_routes.json"));
326
+ logger.log(`✨ Uploading _redirects`);
326
327
  }
327
328
 
328
329
  if (builtFunctions) {
329
330
  formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
331
+ logger.log(`✨ Uploading Functions`);
332
+ try {
333
+ _routes = readFileSync(routesOutputPath, "utf-8");
334
+ if (_routes) {
335
+ formData.append("_routes.json", new File([_routes], "_routes.json"));
336
+ }
337
+ } catch {}
330
338
  } else if (_workerJS) {
331
339
  // Advanced Mode
332
340
  // https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
333
341
  formData.append("_worker.js", new File([_workerJS], "_worker.js"));
342
+ logger.log(`✨ Uploading _worker.js`);
334
343
 
335
344
  try {
336
345
  // In advanced mode, developers can specify a custom _routes.json
@@ -348,6 +357,8 @@ export const Handler = async ({
348
357
  }
349
358
 
350
359
  _routes = JSON.stringify(optimizeRoutesJSONSpec(advancedModeRoutes));
360
+ formData.append("_routes.json", new File([_routes], "_routes.json"));
361
+ logger.log(`✨ Uploading _routes.json`);
351
362
 
352
363
  logger.warn(
353
364
  `🚨 _routes.json is an experimental feature and is subject to change. Don't use unless you really must!`
@@ -58,13 +58,14 @@ type OutcomeFilter = {
58
58
 
59
59
  /**
60
60
  * There are five possible outcomes we can get, three of which
61
- * (exception, exceededCpu, and unknown) are considered errors
61
+ * (exception, exceededCpu, exceededMemory, and unknown) are considered errors
62
62
  */
63
63
  export type Outcome =
64
64
  | "ok"
65
65
  | "canceled"
66
66
  | "exception"
67
67
  | "exceededCpu"
68
+ | "exceededMemory"
68
69
  | "unknown";
69
70
 
70
71
  /**
@@ -210,6 +211,7 @@ function parseOutcome(
210
211
  case "error":
211
212
  outcomes.add("exception");
212
213
  outcomes.add("exceededCpu");
214
+ outcomes.add("exceededMemory");
213
215
  outcomes.add("unknown");
214
216
  break;
215
217
 
@@ -96,6 +96,8 @@ function prettifyOutcome(outcome: Outcome): string {
96
96
  return "Canceled";
97
97
  case "exceededCpu":
98
98
  return "Exceeded CPU Limit";
99
+ case "exceededMemory":
100
+ return "Exceeded Memory Limit";
99
101
  case "exception":
100
102
  return "Exception Thrown";
101
103
  case "unknown":
@@ -1,5 +1,8 @@
1
1
  import { match } from "path-to-regexp";
2
2
 
3
+ //note: this explicitly does not include the * character, as pages requires this
4
+ const escapeRegex = /[.+?^${}()|[\]\\]/g;
5
+
3
6
  type HTTPMethod =
4
7
  | "HEAD"
5
8
  | "OPTIONS"
@@ -67,8 +70,13 @@ function* executeRequest(request: Request, relativePathname: string) {
67
70
  continue;
68
71
  }
69
72
 
70
- const routeMatcher = match(route.routePath, { end: false });
71
- const mountMatcher = match(route.mountPath, { end: false });
73
+ // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
74
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
75
+ end: false,
76
+ });
77
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
78
+ end: false,
79
+ });
72
80
  const matchResult = routeMatcher(relativePathname);
73
81
  const mountMatchResult = mountMatcher(relativePathname);
74
82
  if (matchResult && mountMatchResult) {
@@ -88,8 +96,12 @@ function* executeRequest(request: Request, relativePathname: string) {
88
96
  continue;
89
97
  }
90
98
 
91
- const routeMatcher = match(route.routePath, { end: true });
92
- const mountMatcher = match(route.mountPath, { end: false });
99
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
100
+ end: true,
101
+ });
102
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
103
+ end: false,
104
+ });
93
105
  const matchResult = routeMatcher(relativePathname);
94
106
  const mountMatchResult = mountMatcher(relativePathname);
95
107
  if (matchResult && mountMatchResult && route.modules.length) {
@@ -1,5 +1,8 @@
1
1
  import { match } from "path-to-regexp";
2
2
 
3
+ //note: this explicitly does not include the * character, as pages requires this
4
+ const escapeRegex = /[.+?^${}()|[\]\\]/g;
5
+
3
6
  type HTTPMethod =
4
7
  | "HEAD"
5
8
  | "OPTIONS"
@@ -61,8 +64,13 @@ function* executeRequest(request: Request) {
61
64
  continue;
62
65
  }
63
66
 
64
- const routeMatcher = match(route.routePath, { end: false });
65
- const mountMatcher = match(route.mountPath, { end: false });
67
+ // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
68
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
69
+ end: false,
70
+ });
71
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
72
+ end: false,
73
+ });
66
74
  const matchResult = routeMatcher(requestPath);
67
75
  const mountMatchResult = mountMatcher(requestPath);
68
76
  if (matchResult && mountMatchResult) {
@@ -81,9 +89,12 @@ function* executeRequest(request: Request) {
81
89
  if (route.method && route.method !== request.method) {
82
90
  continue;
83
91
  }
84
-
85
- const routeMatcher = match(route.routePath, { end: true });
86
- const mountMatcher = match(route.mountPath, { end: false });
92
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
93
+ end: true,
94
+ });
95
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
96
+ end: false,
97
+ });
87
98
  const matchResult = routeMatcher(requestPath);
88
99
  const mountMatchResult = mountMatcher(requestPath);
89
100
  if (matchResult && mountMatchResult && route.modules.length) {
@@ -16,20 +16,23 @@ export default {
16
16
  if (details) {
17
17
  facadeEnv[name] = {
18
18
  async fetch(...reqArgs) {
19
+ const reqFromArgs = new Request(...reqArgs);
19
20
  if (details.headers) {
20
- const req = new Request(...reqArgs);
21
21
  for (const [key, value] of Object.entries(details.headers)) {
22
22
  // In remote mode, you need to add a couple of headers
23
23
  // to make sure it's talking to the 'dev' preview session
24
24
  // (much like wrangler dev already does via proxy.ts)
25
- req.headers.set(key, value);
25
+ reqFromArgs.headers.set(key, value);
26
26
  }
27
- return env[name].fetch(req);
27
+ return env[name].fetch(reqFromArgs);
28
28
  }
29
- const url = `${details.protocol}://${details.host}${
30
- details.port ? `:${details.port}` : ""
31
- }`;
32
- const request = new Request(url, ...reqArgs);
29
+
30
+ const url = new URL(reqFromArgs.url);
31
+ url.protocol = details.protocol;
32
+ url.host = details.host;
33
+ if (details.port !== undefined) url.port = details.port;
34
+
35
+ const request = new Request(url.toString(), reqFromArgs);
33
36
  return fetch(request);
34
37
  },
35
38
  };
@@ -7,20 +7,23 @@ for (const [name, details] of Object.entries(Workers)) {
7
7
  if (details) {
8
8
  globalThis[name] = {
9
9
  async fetch(...reqArgs) {
10
+ const reqFromArgs = new Request(...reqArgs);
10
11
  if (details.headers) {
11
- const req = new Request(...reqArgs);
12
12
  for (const [key, value] of Object.entries(details.headers)) {
13
13
  // In remote mode, you need to add a couple of headers
14
14
  // to make sure it's talking to the 'dev' preview session
15
15
  // (much like wrangler dev already does via proxy.ts)
16
- req.headers.set(key, value);
16
+ reqFromArgs.headers.set(key, value);
17
17
  }
18
- return env[name].fetch(req);
18
+ return env[name].fetch(reqFromArgs);
19
19
  }
20
- const url = `${details.protocol}://${details.host}${
21
- details.port ? `:${details.port}` : ""
22
- }`;
23
- const request = new Request(url, ...reqArgs);
20
+
21
+ const url = new URL(reqFromArgs.url);
22
+ url.protocol = details.protocol;
23
+ url.host = details.host;
24
+ if (details.port !== undefined) url.port = details.port;
25
+
26
+ const request = new Request(url.toString(), reqFromArgs);
24
27
  return fetch(request);
25
28
  },
26
29
  };
@@ -41,6 +41,7 @@ declare interface DevOptions {
41
41
  siteExclude?: string[];
42
42
  nodeCompat?: boolean;
43
43
  compatibilityDate?: string;
44
+ compatibilityFlags?: string[];
44
45
  experimentalEnableLocalPersistence?: boolean;
45
46
  liveReload?: boolean;
46
47
  watch?: boolean;