wrangler 2.0.24 → 2.0.27

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 (63) hide show
  1. package/miniflare-dist/index.mjs +142 -16
  2. package/package.json +3 -3
  3. package/src/__tests__/configuration.test.ts +7 -3
  4. package/src/__tests__/dev.test.tsx +26 -4
  5. package/src/__tests__/generate.test.ts +2 -4
  6. package/src/__tests__/helpers/mock-cfetch.ts +35 -2
  7. package/src/__tests__/init.test.ts +537 -359
  8. package/src/__tests__/jest.setup.ts +7 -0
  9. package/src/__tests__/metrics.test.ts +1 -1
  10. package/src/__tests__/pages.test.ts +14 -0
  11. package/src/__tests__/r2.test.ts +22 -3
  12. package/src/__tests__/tail.test.ts +112 -42
  13. package/src/__tests__/user.test.ts +11 -0
  14. package/src/api/dev.ts +7 -0
  15. package/src/bundle.ts +3 -2
  16. package/src/cfetch/internal.ts +56 -0
  17. package/src/config/config.ts +1 -1
  18. package/src/config/validation-helpers.ts +19 -6
  19. package/src/config/validation.ts +9 -3
  20. package/src/config-cache.ts +2 -1
  21. package/src/dev/dev.tsx +16 -2
  22. package/src/dev/local.tsx +69 -5
  23. package/src/dev/use-esbuild.ts +3 -0
  24. package/src/dev-registry.tsx +3 -0
  25. package/src/dev.tsx +28 -19
  26. package/src/generate.ts +1 -1
  27. package/src/index.tsx +51 -21
  28. package/src/init.ts +111 -38
  29. package/src/inspect.ts +1 -4
  30. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  31. package/src/metrics/metrics-config.ts +1 -1
  32. package/src/miniflare-cli/assets.ts +27 -16
  33. package/src/miniflare-cli/index.ts +124 -2
  34. package/src/pages/build.tsx +75 -41
  35. package/src/pages/constants.ts +4 -0
  36. package/src/pages/deployments.tsx +9 -9
  37. package/src/pages/dev.tsx +178 -64
  38. package/src/pages/errors.ts +22 -0
  39. package/src/pages/functions/buildPlugin.ts +4 -0
  40. package/src/pages/functions/buildWorker.ts +4 -0
  41. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  42. package/src/pages/functions/routes-consolidation.ts +73 -0
  43. package/src/pages/functions/routes-transformation.test.ts +271 -0
  44. package/src/pages/functions/routes-transformation.ts +122 -0
  45. package/src/pages/functions.tsx +96 -0
  46. package/src/pages/index.tsx +65 -55
  47. package/src/pages/projects.tsx +9 -3
  48. package/src/pages/publish.tsx +75 -22
  49. package/src/pages/types.ts +9 -0
  50. package/src/pages/upload.tsx +6 -8
  51. package/src/proxy.ts +10 -0
  52. package/src/r2.ts +17 -4
  53. package/src/tail/filters.ts +3 -1
  54. package/src/tail/index.ts +15 -2
  55. package/src/tail/printing.ts +43 -3
  56. package/src/user/user.tsx +6 -4
  57. package/src/whoami.tsx +5 -5
  58. package/templates/pages-template-plugin.ts +16 -4
  59. package/templates/pages-template-worker.ts +16 -5
  60. package/templates/service-bindings-module-facade.js +10 -7
  61. package/templates/service-bindings-sw-facade.js +10 -7
  62. package/wrangler-dist/cli.d.ts +7 -0
  63. package/wrangler-dist/cli.js +1681 -1091
@@ -6,3 +6,7 @@ export const MAX_UPLOAD_ATTEMPTS = 5;
6
6
  export const MAX_CHECK_MISSING_ATTEMPTS = 5;
7
7
  export const SECONDS_TO_WAIT_FOR_PROXY = 5;
8
8
  export const isInPagesCI = !!process.env.CF_PAGES;
9
+ /** The max number of rules in _routes.json */
10
+ export const MAX_FUNCTIONS_ROUTES_RULES = 100;
11
+ export const MAX_FUNCTIONS_ROUTES_RULE_LENGTH = 100;
12
+ export const ROUTES_SPEC_VERSION = 1;
@@ -11,14 +11,16 @@ import { requireAuth } from "../user";
11
11
  import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
12
12
  import { listProjects } from "./projects";
13
13
  import { pagesBetaWarning } from "./utils";
14
- import type { Deployment, PagesConfigCache } from "./types";
15
- import type { ArgumentsCamelCase, Argv } from "yargs";
14
+ import type {
15
+ Deployment,
16
+ PagesConfigCache,
17
+ YargsOptionsToInterface,
18
+ } from "./types";
19
+ import type { Argv } from "yargs";
16
20
 
17
- type ListArgs = {
18
- "project-name"?: string;
19
- };
21
+ type ListArgs = YargsOptionsToInterface<typeof ListOptions>;
20
22
 
21
- export function ListOptions(yargs: Argv): Argv<ListArgs> {
23
+ export function ListOptions(yargs: Argv) {
22
24
  return yargs
23
25
  .options({
24
26
  "project-name": {
@@ -30,9 +32,7 @@ export function ListOptions(yargs: Argv): Argv<ListArgs> {
30
32
  .epilogue(pagesBetaWarning);
31
33
  }
32
34
 
33
- export async function ListHandler({
34
- projectName,
35
- }: ArgumentsCamelCase<ListArgs>) {
35
+ export async function ListHandler({ projectName }: ListArgs) {
36
36
  const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
37
37
  const accountId = await requireAuth(config);
38
38
 
package/src/pages/dev.tsx CHANGED
@@ -1,34 +1,29 @@
1
1
  import { execSync, spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { tmpdir } from "node:os";
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
7
  import { unstable_dev } from "../api";
7
8
  import { FatalError } from "../errors";
8
9
  import { logger } from "../logger";
9
10
  import * as metrics from "../metrics";
10
11
  import { buildFunctions } from "./build";
11
12
  import { SECONDS_TO_WAIT_FOR_PROXY } from "./constants";
13
+ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
12
14
  import { CLEANUP, CLEANUP_CALLBACKS, pagesBetaWarning } from "./utils";
13
- import type { ArgumentsCamelCase, Argv } from "yargs";
14
-
15
- type PagesDevArgs = {
16
- directory?: string;
17
- command?: string;
18
- local: boolean;
19
- port: number;
20
- proxy?: number;
21
- "script-path": string;
22
- binding?: (string | number)[];
23
- kv?: (string | number)[];
24
- do?: (string | number)[];
25
- "live-reload": boolean;
26
- "local-protocol"?: "https" | "http";
27
- "experimental-enable-local-persistence": boolean;
28
- "node-compat": boolean;
29
- };
15
+ import type { AdditionalDevProps } from "../dev";
16
+ import type { YargsOptionsToInterface } from "./types";
17
+ import type { Plugin } from "esbuild";
18
+ import type { Argv } from "yargs";
19
+
20
+ const DURABLE_OBJECTS_BINDING_REGEXP = new RegExp(
21
+ /^(?<binding>[^=]+)=(?<className>[^@\s]+)(@(?<scriptName>.*)$)?$/
22
+ );
30
23
 
31
- export function Options(yargs: Argv): Argv<PagesDevArgs> {
24
+ type PagesDevArgs = YargsOptionsToInterface<typeof Options>;
25
+
26
+ export function Options(yargs: Argv) {
32
27
  return yargs
33
28
  .positional("directory", {
34
29
  type: "string",
@@ -46,11 +41,41 @@ export function Options(yargs: Argv): Argv<PagesDevArgs> {
46
41
  default: true,
47
42
  description: "Run on my machine",
48
43
  },
44
+ "compatibility-date": {
45
+ describe: "Date to use for compatibility checks",
46
+ type: "string",
47
+ },
48
+ "compatibility-flags": {
49
+ describe: "Flags to use for compatibility checks",
50
+ alias: "compatibility-flag",
51
+ type: "string",
52
+ array: true,
53
+ },
54
+
55
+ // TODO
56
+ // For now, all Pages projects are set to 2021-11-02. We're adding compat date soon, and we can then adopt `wrangler dev`'s `default: true`.
57
+ // However, it looks like it isn't actually connected up properly in `wrangler dev` at the moment, hence commenting this out for now.
58
+
59
+ // latest: {
60
+ // describe: "Use the latest version of the worker runtime",
61
+ // type: "boolean",
62
+ // default: false,
63
+ // },
64
+
65
+ ip: {
66
+ type: "string",
67
+ default: "0.0.0.0",
68
+ description: "The IP address to listen on",
69
+ },
49
70
  port: {
50
71
  type: "number",
51
72
  default: 8788,
52
73
  description: "The port to listen on (serve from)",
53
74
  },
75
+ "inspector-port": {
76
+ type: "number",
77
+ describe: "Port for devtools to connect to",
78
+ },
54
79
  proxy: {
55
80
  type: "number",
56
81
  description: "The port to proxy (where the static assets are served)",
@@ -68,14 +93,18 @@ export function Options(yargs: Argv): Argv<PagesDevArgs> {
68
93
  },
69
94
  kv: {
70
95
  type: "array",
71
- description: "KV namespace to bind",
96
+ description: "KV namespace to bind (--kv KV_BINDING)",
72
97
  alias: "k",
73
98
  },
74
99
  do: {
75
100
  type: "array",
76
- description: "Durable Object to bind (NAME=CLASS)",
101
+ description: "Durable Object to bind (--do NAME=CLASS)",
77
102
  alias: "o",
78
103
  },
104
+ r2: {
105
+ type: "array",
106
+ description: "R2 bucket to bind (--r2 R2_BINDING)",
107
+ },
79
108
  "live-reload": {
80
109
  type: "boolean",
81
110
  default: false,
@@ -108,19 +137,24 @@ export function Options(yargs: Argv): Argv<PagesDevArgs> {
108
137
  export const Handler = async ({
109
138
  local,
110
139
  directory,
140
+ "compatibility-date": compatibilityDate = "2021-11-02",
141
+ "compatibility-flags": compatibilityFlags,
142
+ ip,
111
143
  port,
144
+ "inspector-port": inspectorPort,
112
145
  proxy: requestedProxyPort,
113
146
  "script-path": singleWorkerScriptPath,
114
147
  binding: bindings = [],
115
148
  kv: kvs = [],
116
149
  do: durableObjects = [],
150
+ r2: r2s = [],
117
151
  "live-reload": liveReload,
118
152
  "local-protocol": localProtocol,
119
153
  "experimental-enable-local-persistence": experimentalEnableLocalPersistence,
120
154
  "node-compat": nodeCompat,
121
155
  config: config,
122
156
  _: [_pages, _dev, ...remaining],
123
- }: ArgumentsCamelCase<PagesDevArgs>) => {
157
+ }: PagesDevArgs) => {
124
158
  // Beta message for `wrangler pages <commands>` usage
125
159
  logger.log(pagesBetaWarning);
126
160
 
@@ -133,7 +167,7 @@ export const Handler = async ({
133
167
  }
134
168
 
135
169
  const functionsDirectory = "./functions";
136
- const usingFunctions = existsSync(functionsDirectory);
170
+ let usingFunctions = existsSync(functionsDirectory);
137
171
 
138
172
  const command = remaining;
139
173
 
@@ -159,8 +193,9 @@ export const Handler = async ({
159
193
  (promiseResolve) => (scriptReadyResolve = promiseResolve)
160
194
  );
161
195
 
162
- let scriptPath: string;
196
+ let scriptPath = "";
163
197
 
198
+ // Try to use Functions
164
199
  if (usingFunctions) {
165
200
  const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
166
201
  scriptPath = outfile;
@@ -172,36 +207,63 @@ export const Handler = async ({
172
207
  }
173
208
 
174
209
  logger.log(`Compiling worker to "${outfile}"...`);
175
-
210
+ const onEnd = () => scriptReadyResolve();
176
211
  try {
177
212
  await buildFunctions({
178
213
  outfile,
179
214
  functionsDirectory,
180
215
  sourcemap: true,
181
216
  watch: true,
182
- onEnd: () => scriptReadyResolve(),
217
+ onEnd,
183
218
  buildOutputDirectory: directory,
184
219
  nodeCompat,
185
220
  });
186
221
  await metrics.sendMetricsEvent("build pages functions");
187
- } catch {}
188
222
 
189
- watch([functionsDirectory], {
190
- persistent: true,
191
- ignoreInitial: true,
192
- }).on("all", async () => {
193
- await buildFunctions({
194
- outfile,
195
- functionsDirectory,
196
- sourcemap: true,
197
- watch: true,
198
- onEnd: () => scriptReadyResolve(),
199
- buildOutputDirectory: directory,
200
- nodeCompat,
223
+ // If Functions found routes, continue using Functions
224
+ watch([functionsDirectory], {
225
+ persistent: true,
226
+ ignoreInitial: true,
227
+ }).on("all", async () => {
228
+ try {
229
+ await buildFunctions({
230
+ outfile,
231
+ functionsDirectory,
232
+ sourcemap: true,
233
+ watch: true,
234
+ onEnd,
235
+ buildOutputDirectory: directory,
236
+ nodeCompat,
237
+ });
238
+ await metrics.sendMetricsEvent("build pages functions");
239
+ } catch (e) {
240
+ if (e instanceof FunctionsNoRoutesError) {
241
+ logger.warn(
242
+ getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
243
+ );
244
+ } else {
245
+ throw e;
246
+ }
247
+ }
201
248
  });
202
- await metrics.sendMetricsEvent("build pages functions");
203
- });
204
- } else {
249
+ } catch (e) {
250
+ // If there are no Functions, then Pages will only serve assets.
251
+ if (e instanceof FunctionsNoRoutesError) {
252
+ logger.warn(
253
+ getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
254
+ );
255
+ // Resolve anyway and run without Functions
256
+ onEnd();
257
+ // Turn off Functions
258
+ usingFunctions = false;
259
+ } else {
260
+ throw e;
261
+ }
262
+ }
263
+ }
264
+ // Depending on the result of building Functions, we may not actually be using
265
+ // Functions even if the directory exists.
266
+ if (!usingFunctions) {
205
267
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
206
268
  scriptReadyResolve!();
207
269
 
@@ -213,20 +275,48 @@ export const Handler = async ({
213
275
  if (!existsSync(scriptPath)) {
214
276
  logger.log("No functions. Shimming...");
215
277
  scriptPath = resolve(__dirname, "../templates/pages-shim.ts");
278
+ } else {
279
+ const runBuild = async () => {
280
+ try {
281
+ await workerJsBuild({
282
+ entryPoints: [scriptPath],
283
+ write: false,
284
+ plugins: [blockWorkerJsImports],
285
+ });
286
+ } catch {}
287
+ };
288
+ await runBuild();
289
+ watch([scriptPath], {
290
+ persistent: true,
291
+ ignoreInitial: true,
292
+ }).on("all", async () => {
293
+ await runBuild();
294
+ });
216
295
  }
217
296
  }
218
297
 
219
298
  await scriptReadyPromise;
220
299
 
300
+ if (scriptPath === "") {
301
+ // Failed to get a script with or without Functions,
302
+ // something really bad must have happend.
303
+ throw new FatalError(
304
+ "Failed to start wrangler pages dev due to an unknown error",
305
+ 1
306
+ );
307
+ }
308
+
221
309
  const { stop, waitUntilExit } = await unstable_dev(
222
310
  scriptPath,
223
311
  {
312
+ ip,
224
313
  port,
314
+ inspectorPort,
225
315
  watch: true,
226
316
  localProtocol,
227
317
  liveReload,
228
-
229
- compatibilityDate: "2021-11-02",
318
+ compatibilityDate,
319
+ compatibilityFlags,
230
320
  nodeCompat,
231
321
  vars: Object.fromEntries(
232
322
  bindings
@@ -237,12 +327,29 @@ export const Handler = async ({
237
327
  binding: val.toString(),
238
328
  id: "",
239
329
  })),
240
- durableObjects: durableObjects.map((durableObject) => {
241
- const [name, class_name] = durableObject.toString().split("=");
242
- return {
243
- name,
244
- class_name,
245
- };
330
+ durableObjects: durableObjects
331
+ .map((durableObject) => {
332
+ const { binding, className, scriptName } =
333
+ DURABLE_OBJECTS_BINDING_REGEXP.exec(durableObject.toString())
334
+ ?.groups || {};
335
+
336
+ if (!binding || !className) {
337
+ logger.warn(
338
+ "Could not parse Durable Object binding:",
339
+ durableObject.toString()
340
+ );
341
+ return;
342
+ }
343
+
344
+ return {
345
+ name: binding,
346
+ class_name: className,
347
+ script_name: scriptName,
348
+ };
349
+ })
350
+ .filter(Boolean) as AdditionalDevProps["durableObjects"],
351
+ r2: r2s.map((binding) => {
352
+ return { binding: binding.toString(), bucket_name: "" };
246
353
  }),
247
354
 
248
355
  enablePagesAssetsServiceBinding: {
@@ -260,24 +367,16 @@ export const Handler = async ({
260
367
  );
261
368
  await metrics.sendMetricsEvent("run pages dev");
262
369
 
370
+ CLEANUP_CALLBACKS.push(stop);
371
+
263
372
  waitUntilExit().then(() => {
264
373
  CLEANUP();
265
- stop();
266
374
  process.exit(0);
267
375
  });
268
376
 
269
- process.on("exit", () => {
270
- CLEANUP();
271
- stop();
272
- });
273
- process.on("SIGINT", () => {
274
- CLEANUP();
275
- stop();
276
- });
277
- process.on("SIGTERM", () => {
278
- CLEANUP();
279
- stop();
280
- });
377
+ process.on("exit", CLEANUP);
378
+ process.on("SIGINT", CLEANUP);
379
+ process.on("SIGTERM", CLEANUP);
281
380
  };
282
381
 
283
382
  function isWindows() {
@@ -319,7 +418,8 @@ function getPort(pid: number) {
319
418
  let command: string, regExp: RegExp;
320
419
 
321
420
  if (isWindows()) {
322
- command = "\\windows\\system32\\netstat.exe -nao";
421
+ const drive = homedir().split(":\\")[0];
422
+ command = drive + ":\\windows\\system32\\netstat.exe -nao";
323
423
  regExp = new RegExp(`TCP\\s+.*:(\\d+)\\s+.*:\\d+\\s+LISTENING\\s+${pid}`);
324
424
  } else {
325
425
  command = "lsof -nPi";
@@ -383,6 +483,8 @@ async function spawnProxyProcess({
383
483
 
384
484
  proxy.on("close", (code) => {
385
485
  logger.error(`Proxy exited with status ${code}.`);
486
+ CLEANUP();
487
+ process.exitCode = code ?? 0;
386
488
  });
387
489
 
388
490
  // Wait for proxy process to start...
@@ -412,3 +514,15 @@ async function spawnProxyProcess({
412
514
 
413
515
  return port;
414
516
  }
517
+
518
+ const blockWorkerJsImports: Plugin = {
519
+ name: "block-worker-js-imports",
520
+ setup(build) {
521
+ build.onResolve({ filter: /.*/g }, (_args) => {
522
+ logger.error(
523
+ `_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.`
524
+ );
525
+ return null;
526
+ });
527
+ },
528
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pages error when no routes are found in the functions directory
3
+ */
4
+ export class FunctionsNoRoutesError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ }
8
+ }
9
+ /**
10
+ * Exit code for `pages functions build` when no routes are found.
11
+ */
12
+ export const EXIT_CODE_FUNCTIONS_NO_ROUTES_ERROR = 156;
13
+
14
+ /** Warning message for when buildFunctions throws FunctionsNoRoutesError */
15
+ export function getFunctionsNoRoutesWarning(
16
+ functionsDirectory: string,
17
+ suffix?: string
18
+ ) {
19
+ return `No routes found when building Functions directory: ${functionsDirectory}${
20
+ suffix ? " - " + suffix : ""
21
+ }`;
22
+ }
@@ -29,6 +29,10 @@ export function buildPlugin({
29
29
  bundle: true,
30
30
  format: "esm",
31
31
  target: "esnext",
32
+ loader: {
33
+ ".html": "text",
34
+ ".txt": "text",
35
+ },
32
36
  outfile,
33
37
  minify,
34
38
  sourcemap,
@@ -34,6 +34,10 @@ export function buildWorker({
34
34
  bundle: true,
35
35
  format: "esm",
36
36
  target: "esnext",
37
+ loader: {
38
+ ".html": "text",
39
+ ".txt": "text",
40
+ },
37
41
  outfile,
38
42
  minify,
39
43
  sourcemap,