wrangler 2.12.3 → 2.14.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 (49) hide show
  1. package/package.json +11 -7
  2. package/src/__tests__/api-devregistry.test.ts +121 -0
  3. package/src/__tests__/api.test.ts +27 -5
  4. package/src/__tests__/configuration.test.ts +59 -12
  5. package/src/__tests__/deployments.test.ts +335 -95
  6. package/src/__tests__/helpers/msw/handlers/deployments.ts +70 -3
  7. package/src/__tests__/helpers/msw/index.ts +4 -2
  8. package/src/__tests__/index.test.ts +10 -4
  9. package/src/__tests__/jest.setup.ts +4 -0
  10. package/src/__tests__/mtls-certificates.test.ts +5 -2
  11. package/src/__tests__/pages/publish.test.ts +67 -23
  12. package/src/__tests__/publish.test.ts +138 -59
  13. package/src/__tests__/queues.test.ts +5 -2
  14. package/src/__tests__/traverse-module-graph.test.ts +220 -0
  15. package/src/api/dev.ts +7 -18
  16. package/src/api/pages/create-worker-bundle-contents.ts +1 -0
  17. package/src/bundle.ts +19 -5
  18. package/src/config/environment.ts +27 -0
  19. package/src/config/index.ts +18 -0
  20. package/src/config/validation.ts +91 -1
  21. package/src/create-worker-upload-form.ts +18 -1
  22. package/src/d1/execute.tsx +1 -1
  23. package/src/d1/migrations/apply.tsx +2 -1
  24. package/src/deployments.ts +260 -8
  25. package/src/dev/start-server.ts +2 -8
  26. package/src/dev/use-esbuild.ts +2 -8
  27. package/src/dev-registry.ts +2 -1
  28. package/src/dev.tsx +1 -0
  29. package/src/entry.ts +18 -8
  30. package/src/index.ts +75 -22
  31. package/src/init.ts +144 -135
  32. package/src/metrics/send-event.ts +2 -1
  33. package/src/module-collection.ts +91 -25
  34. package/src/pages/functions/buildPlugin.ts +1 -0
  35. package/src/pages/functions/buildWorker.ts +2 -8
  36. package/src/publish/publish.ts +10 -26
  37. package/src/queues/cli/commands/consumer/add.ts +6 -0
  38. package/src/queues/client.ts +1 -0
  39. package/src/secret/index.ts +1 -0
  40. package/src/traverse-module-graph.ts +53 -0
  41. package/src/worker.ts +10 -0
  42. package/wrangler-dist/cli.d.ts +24 -0
  43. package/wrangler-dist/cli.js +19083 -18680
  44. package/src/__tests__/api-devregistry.test.js +0 -64
  45. package/src/__tests__/tsconfig.tsbuildinfo +0 -1
  46. package/src/miniflare-cli/tsconfig.tsbuildinfo +0 -1
  47. package/src/pages/functions/tsconfig.tsbuildinfo +0 -1
  48. package/templates/__tests__/tsconfig.tsbuildinfo +0 -1
  49. package/templates/tsconfig.tsbuildinfo +0 -1
package/src/api/dev.ts CHANGED
@@ -143,7 +143,6 @@ export async function unstable_dev(
143
143
  enablePagesAssetsServiceBinding,
144
144
  liveReload,
145
145
  showInteractiveDevSession,
146
- forceLocal,
147
146
  onReady: (address, port) => {
148
147
  readyPort = port;
149
148
  readyAddress = address;
@@ -232,6 +231,7 @@ export async function unstable_dev(
232
231
  experimentalLocal: experimentalLocal ?? false,
233
232
  experimentalLocalRemoteKv: experimentalLocalRemoteKv ?? false,
234
233
  enablePagesAssetsServiceBinding,
234
+ forceLocal,
235
235
  liveReload,
236
236
  onReady: (address, port) => {
237
237
  readyPort = port;
@@ -301,27 +301,16 @@ export async function unstable_dev(
301
301
  export function parseRequestInput(
302
302
  readyAddress: string,
303
303
  readyPort: number,
304
- input?: RequestInfo,
304
+ input: RequestInfo = "/",
305
305
  init?: RequestInit,
306
306
  protocol: "http" | "https" = "http"
307
307
  ): [RequestInfo, RequestInit | undefined] {
308
308
  if (input instanceof Request) {
309
309
  return [input, undefined];
310
- } else if (input instanceof URL) {
311
- input = `${protocol}://${readyAddress}:${readyPort}${input.pathname}`;
312
- } else if (typeof input === "string") {
313
- try {
314
- // Want to strip the URL to only get the pathname, but the user could pass in only the pathname
315
- // Will error if we try and pass "/something" into new URL("/something")
316
- input = `${protocol}://${readyAddress}:${readyPort}${
317
- new URL(input).pathname
318
- }`;
319
- } catch {
320
- input = `${protocol}://${readyAddress}:${readyPort}${input}`;
321
- }
322
- } else {
323
- input = `${protocol}://${readyAddress}:${readyPort}`;
324
310
  }
325
-
326
- return [input, init];
311
+ const url = new URL(`${input}`, `${protocol}://${readyAddress}:${readyPort}`);
312
+ url.protocol = protocol;
313
+ url.hostname = readyAddress;
314
+ url.port = readyPort.toString();
315
+ return [url, init];
327
316
  }
@@ -49,6 +49,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData {
49
49
  bindings: {
50
50
  vars: undefined,
51
51
  kv_namespaces: undefined,
52
+ send_email: undefined,
52
53
  wasm_modules: undefined,
53
54
  text_blobs: undefined,
54
55
  data_blobs: undefined,
package/src/bundle.ts CHANGED
@@ -13,6 +13,13 @@ import type { DurableObjectBindings } from "./config/environment";
13
13
  import type { WorkerRegistry } from "./dev-registry";
14
14
  import type { Entry } from "./entry";
15
15
  import type { CfModule } from "./worker";
16
+
17
+ export const COMMON_ESBUILD_OPTIONS = {
18
+ // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/
19
+ target: "es2022",
20
+ loader: { ".js": "jsx", ".mjs": "jsx", ".cjs": "jsx" },
21
+ } as const;
22
+
16
23
  export type BundleResult = {
17
24
  modules: CfModule[];
18
25
  dependencies: esbuild.Metafile["outputs"][string]["inputs"];
@@ -94,6 +101,15 @@ const nodejsCompatPlugin: esbuild.Plugin = {
94
101
  },
95
102
  };
96
103
 
104
+ const cloudflareJsPlugin: esbuild.Plugin = {
105
+ name: "cloudflare javascript Plugin",
106
+ setup(pluginBuild) {
107
+ pluginBuild.onResolve({ filter: /^cloudflare:.*/ }, () => {
108
+ return { external: true };
109
+ });
110
+ },
111
+ };
112
+
97
113
  /**
98
114
  * Generate a bundle for the worker identified by the arguments passed in.
99
115
  */
@@ -348,8 +364,7 @@ export async function bundleWorker(
348
364
  inject,
349
365
  external: ["__STATIC_CONTENT_MANIFEST"],
350
366
  format: entry.format === "modules" ? "esm" : "iife",
351
- // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/
352
- target: "es2022",
367
+ target: COMMON_ESBUILD_OPTIONS.target,
353
368
  sourcemap: sourcemap ?? true, // this needs to use ?? to accept false
354
369
  // Include a reference to the output folder in the sourcemap.
355
370
  // This is omitted by default, but we need it to properly resolve source paths in error output.
@@ -368,9 +383,7 @@ export async function bundleWorker(
368
383
  },
369
384
  }),
370
385
  loader: {
371
- ".js": "jsx",
372
- ".mjs": "jsx",
373
- ".cjs": "jsx",
386
+ ...COMMON_ESBUILD_OPTIONS.loader,
374
387
  ...(loader || {}),
375
388
  },
376
389
  plugins: [
@@ -379,6 +392,7 @@ export async function bundleWorker(
379
392
  ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
380
393
  : []),
381
394
  ...(nodejsCompat ? [nodejsCompatPlugin] : []),
395
+ ...[cloudflareJsPlugin],
382
396
  ...(plugins || []),
383
397
  ],
384
398
  ...(jsxFactory && { jsxFactory }),
@@ -74,6 +74,12 @@ interface EnvironmentInheritable {
74
74
  */
75
75
  main: string | undefined;
76
76
 
77
+ /**
78
+ * The directory in which module rules should be evaluated in a `--no-bundle` worker
79
+ * This defaults to dirname(main) when left undefined
80
+ */
81
+ base_dir: string | undefined;
82
+
77
83
  /**
78
84
  * Whether we use <name>.<subdomain>.workers.dev to
79
85
  * test and deploy your worker.
@@ -339,6 +345,24 @@ interface EnvironmentNonInheritable {
339
345
  preview_id?: string;
340
346
  }[];
341
347
 
348
+ /**
349
+ * These specify bindings to send email from inside your Worker.
350
+ *
351
+ * NOTE: This field is not automatically inherited from the top level environment,
352
+ * and so must be specified in every named environment.
353
+ *
354
+ * @default `[]`
355
+ * @nonInheritable
356
+ */
357
+ send_email: {
358
+ /** The binding name used to refer to the this binding */
359
+ name: string;
360
+ /** If this binding should be restricted to a specific verified address */
361
+ destination_address?: string;
362
+ /** If this binding should be restricted to a set of verified addresses */
363
+ allowed_destination_addresses?: string[];
364
+ }[];
365
+
342
366
  /**
343
367
  * Specifies Queues that are bound to this Worker environment.
344
368
  *
@@ -374,6 +398,9 @@ interface EnvironmentNonInheritable {
374
398
 
375
399
  /** The queue to send messages that failed to be consumed. */
376
400
  dead_letter_queue?: string;
401
+
402
+ /** The maximum number of concurrent consumer Worker invocations. Leaving this unset will allow your consumer to scale to the maximum concurrency needed to keep up with the message backlog. */
403
+ max_concurrency?: number | null;
377
404
  }[];
378
405
  };
379
406
 
@@ -97,6 +97,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
97
97
  data_blobs,
98
98
  durable_objects,
99
99
  kv_namespaces,
100
+ send_email,
100
101
  queues,
101
102
  d1_databases,
102
103
  r2_buckets,
@@ -155,6 +156,23 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
155
156
  });
156
157
  }
157
158
 
159
+ if (send_email !== undefined && send_email.length > 0) {
160
+ output.push({
161
+ type: "Send Email",
162
+ entries: send_email.map(
163
+ ({ name, destination_address, allowed_destination_addresses }) => {
164
+ return {
165
+ key: name,
166
+ value:
167
+ destination_address ||
168
+ allowed_destination_addresses?.join(", ") ||
169
+ "unrestricted",
170
+ };
171
+ }
172
+ ),
173
+ });
174
+ }
175
+
158
176
  if (queues !== undefined && queues.length > 0) {
159
177
  output.push({
160
178
  type: "Queues",
@@ -358,6 +358,26 @@ function normalizeAndValidateMainField(
358
358
  }
359
359
  }
360
360
 
361
+ /**
362
+ * Validate the `base_dir` field and return the normalized values.
363
+ */
364
+ function normalizeAndValidateBaseDirField(
365
+ configPath: string | undefined,
366
+ rawDir: string | undefined
367
+ ): string | undefined {
368
+ const configDir = path.dirname(configPath ?? "wrangler.toml");
369
+ if (rawDir !== undefined) {
370
+ if (typeof rawDir === "string") {
371
+ const directory = path.resolve(configDir);
372
+ return path.resolve(directory, rawDir);
373
+ } else {
374
+ return rawDir;
375
+ }
376
+ } else {
377
+ return;
378
+ }
379
+ }
380
+
361
381
  /**
362
382
  * Validate the `dev` configuration and return the normalized values.
363
383
  */
@@ -1011,6 +1031,17 @@ function normalizeAndValidateEnvironment(
1011
1031
  ),
1012
1032
  deprecatedUpload
1013
1033
  ),
1034
+ base_dir: normalizeAndValidateBaseDirField(
1035
+ configPath,
1036
+ inheritable(
1037
+ diagnostics,
1038
+ topLevelEnv,
1039
+ rawEnv,
1040
+ "base_dir",
1041
+ isString,
1042
+ undefined
1043
+ )
1044
+ ),
1014
1045
  route,
1015
1046
  routes,
1016
1047
  triggers: inheritable(
@@ -1074,6 +1105,16 @@ function normalizeAndValidateEnvironment(
1074
1105
  validateBindingArray(envName, validateKVBinding),
1075
1106
  []
1076
1107
  ),
1108
+ send_email: notInheritable(
1109
+ diagnostics,
1110
+ topLevelEnv,
1111
+ rawConfig,
1112
+ rawEnv,
1113
+ envName,
1114
+ "send_email",
1115
+ validateBindingArray(envName, validateSendEmailBinding),
1116
+ []
1117
+ ),
1077
1118
  queues: notInheritable(
1078
1119
  diagnostics,
1079
1120
  topLevelEnv,
@@ -1765,6 +1806,53 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1765
1806
  return isValid;
1766
1807
  };
1767
1808
 
1809
+ const validateSendEmailBinding: ValidatorFn = (diagnostics, field, value) => {
1810
+ if (typeof value !== "object" || value === null) {
1811
+ diagnostics.errors.push(
1812
+ `"send_email" bindings should be objects, but got ${JSON.stringify(
1813
+ value
1814
+ )}`
1815
+ );
1816
+ return false;
1817
+ }
1818
+ let isValid = true;
1819
+ // send email bindings must have a name.
1820
+ if (!isRequiredProperty(value, "name", "string")) {
1821
+ diagnostics.errors.push(
1822
+ `"${field}" bindings should have a string "name" field but got ${JSON.stringify(
1823
+ value
1824
+ )}.`
1825
+ );
1826
+ isValid = false;
1827
+ }
1828
+ if (!isOptionalProperty(value, "destination_address", "string")) {
1829
+ diagnostics.errors.push(
1830
+ `"${field}" bindings should, optionally, have a string "destination_address" field but got ${JSON.stringify(
1831
+ value
1832
+ )}.`
1833
+ );
1834
+ isValid = false;
1835
+ }
1836
+ if (!isOptionalProperty(value, "allowed_destination_addresses", "object")) {
1837
+ diagnostics.errors.push(
1838
+ `"${field}" bindings should, optionally, have a []string "allowed_destination_addresses" field but got ${JSON.stringify(
1839
+ value
1840
+ )}.`
1841
+ );
1842
+ isValid = false;
1843
+ }
1844
+ if (
1845
+ "destination_address" in value &&
1846
+ "allowed_destination_addresses" in value
1847
+ ) {
1848
+ diagnostics.errors.push(
1849
+ `"${field}" bindings should have either a "destination_address" or "allowed_destination_addresses" field, but not both.`
1850
+ );
1851
+ isValid = false;
1852
+ }
1853
+ return isValid;
1854
+ };
1855
+
1768
1856
  const validateQueueBinding: ValidatorFn = (diagnostics, field, value) => {
1769
1857
  if (typeof value !== "object" || value === null) {
1770
1858
  diagnostics.errors.push(
@@ -2215,6 +2303,7 @@ const validateConsumer: ValidatorFn = (diagnostics, field, value, _config) => {
2215
2303
  "max_batch_timeout",
2216
2304
  "max_retries",
2217
2305
  "dead_letter_queue",
2306
+ "max_concurrency",
2218
2307
  ])
2219
2308
  ) {
2220
2309
  isValid = false;
@@ -2230,12 +2319,13 @@ const validateConsumer: ValidatorFn = (diagnostics, field, value, _config) => {
2230
2319
 
2231
2320
  const options: {
2232
2321
  key: string;
2233
- type: "number" | "string";
2322
+ type: "number" | "string" | "boolean";
2234
2323
  }[] = [
2235
2324
  { key: "max_batch_size", type: "number" },
2236
2325
  { key: "max_batch_timeout", type: "number" },
2237
2326
  { key: "max_retries", type: "number" },
2238
2327
  { key: "dead_letter_queue", type: "string" },
2328
+ { key: "max_concurrency", type: "number" },
2239
2329
  ];
2240
2330
  for (const optionalOpt of options) {
2241
2331
  if (!isOptionalProperty(value, optionalOpt.key, optionalOpt.type)) {
@@ -23,7 +23,7 @@ export function toMimeType(type: CfModuleType): string {
23
23
  }
24
24
  }
25
25
 
26
- type WorkerMetadataBinding =
26
+ export type WorkerMetadataBinding =
27
27
  // If you add any new binding types here, also add it to safeBindings
28
28
  // under validateUnsafeBinding in config/validation.ts
29
29
  | { type: "plain_text"; name: string; text: string }
@@ -32,6 +32,12 @@ type WorkerMetadataBinding =
32
32
  | { type: "text_blob"; name: string; part: string }
33
33
  | { type: "data_blob"; name: string; part: string }
34
34
  | { type: "kv_namespace"; name: string; namespace_id: string }
35
+ | {
36
+ type: "send_email";
37
+ name: string;
38
+ destination_address?: string;
39
+ allowed_destination_addresses?: string[];
40
+ }
35
41
  | {
36
42
  type: "durable_object_namespace";
37
43
  name: string;
@@ -105,6 +111,17 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
105
111
  });
106
112
  });
107
113
 
114
+ bindings.send_email?.forEach(
115
+ ({ name, destination_address, allowed_destination_addresses }) => {
116
+ metadataBindings.push({
117
+ name: name,
118
+ type: "send_email",
119
+ destination_address,
120
+ allowed_destination_addresses,
121
+ });
122
+ }
123
+ );
124
+
108
125
  bindings.durable_objects?.bindings.forEach(
109
126
  ({ name, class_name, script_name, environment }) => {
110
127
  metadataBindings.push({
@@ -37,7 +37,7 @@ export type QueryResult = {
37
37
  };
38
38
  query?: string;
39
39
  };
40
- // Max number of bytes to send in a single /execute call
40
+ // Max number of statements to send in a single /execute call
41
41
  const QUERY_LIMIT = 10_000;
42
42
 
43
43
  export function Options(yargs: CommonYargsArgv) {
@@ -172,10 +172,11 @@ Your database may not be available to serve requests during the migration, conti
172
172
  }
173
173
  } catch (e) {
174
174
  const err = e as ParseError;
175
+ const maybeCause = (err.cause ?? err) as Error;
175
176
 
176
177
  success = false;
177
178
  errorNotes = err.notes?.map((msg) => msg.text) ?? [
178
- err.message ?? err.toString(),
179
+ maybeCause?.message ?? maybeCause.toString(),
179
180
  ];
180
181
  }
181
182