wrangler 2.0.26 → 2.0.29

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 (62) hide show
  1. package/bin/wrangler.js +1 -1
  2. package/miniflare-dist/index.mjs +54 -46
  3. package/package.json +6 -4
  4. package/src/__tests__/api-dev.test.ts +19 -0
  5. package/src/__tests__/configuration.test.ts +33 -29
  6. package/src/__tests__/dev.test.tsx +8 -6
  7. package/src/__tests__/generate.test.ts +2 -4
  8. package/src/__tests__/helpers/hello-world-worker.js +5 -0
  9. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  10. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  11. package/src/__tests__/index.test.ts +30 -30
  12. package/src/__tests__/jest.setup.ts +17 -0
  13. package/src/__tests__/metrics.test.ts +1 -1
  14. package/src/__tests__/pages.test.ts +829 -103
  15. package/src/__tests__/paths.test.ts +17 -0
  16. package/src/__tests__/publish.test.ts +59 -18
  17. package/src/__tests__/r2.test.ts +4 -3
  18. package/src/__tests__/tail.test.ts +34 -0
  19. package/src/__tests__/test-old-node-version.js +3 -3
  20. package/src/__tests__/user.test.ts +11 -0
  21. package/src/__tests__/worker-namespace.test.ts +37 -35
  22. package/src/api/dev.ts +74 -28
  23. package/src/bundle.ts +14 -11
  24. package/src/cfetch/internal.ts +81 -3
  25. package/src/cli.ts +1 -1
  26. package/src/config/environment.ts +1 -1
  27. package/src/config/index.ts +4 -4
  28. package/src/config/validation-helpers.ts +19 -6
  29. package/src/config/validation.ts +11 -5
  30. package/src/config-cache.ts +2 -1
  31. package/src/create-worker-upload-form.ts +29 -26
  32. package/src/dev/local.tsx +317 -169
  33. package/src/dev/remote.tsx +10 -1
  34. package/src/dev/start-server.ts +412 -0
  35. package/src/dev.tsx +341 -157
  36. package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
  37. package/src/entry.ts +2 -1
  38. package/src/generate.ts +1 -1
  39. package/src/index.tsx +54 -8
  40. package/src/init.ts +5 -5
  41. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  42. package/src/metrics/metrics-config.ts +1 -1
  43. package/src/metrics/send-event.ts +6 -5
  44. package/src/miniflare-cli/assets.ts +4 -65
  45. package/src/miniflare-cli/index.ts +36 -32
  46. package/src/pages/constants.ts +3 -0
  47. package/src/pages/dev.tsx +10 -15
  48. package/src/pages/functions/buildPlugin.ts +2 -1
  49. package/src/pages/functions/buildWorker.ts +2 -1
  50. package/src/pages/functions/routes-transformation.test.ts +12 -1
  51. package/src/pages/functions/routes-transformation.ts +7 -1
  52. package/src/pages/publish.tsx +82 -38
  53. package/src/paths.ts +20 -1
  54. package/src/proxy.ts +10 -0
  55. package/src/publish.ts +19 -4
  56. package/src/r2.ts +4 -4
  57. package/src/user/user.tsx +6 -4
  58. package/src/whoami.tsx +5 -5
  59. package/src/worker.ts +10 -9
  60. package/src/zones.ts +91 -0
  61. package/wrangler-dist/cli.d.ts +22 -8
  62. package/wrangler-dist/cli.js +7757 -2315
package/src/bundle.ts CHANGED
@@ -7,6 +7,7 @@ import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
7
7
  import * as esbuild from "esbuild";
8
8
  import tmp from "tmp-promise";
9
9
  import createModuleCollector from "./module-collection";
10
+ import { getBasePath } from "./paths";
10
11
  import type { Config } from "./config";
11
12
  import type { WorkerRegistry } from "./dev-registry";
12
13
  import type { Entry } from "./entry";
@@ -71,7 +72,7 @@ export async function bundleWorker(
71
72
  nodeCompat: boolean | undefined;
72
73
  define: Config["define"];
73
74
  checkFetch: boolean;
74
- services: Config["services"];
75
+ services: Config["services"] | undefined;
75
76
  workerDefinitions: WorkerRegistry | undefined;
76
77
  firstPartyWorkerDevFacade: boolean | undefined;
77
78
  }
@@ -129,7 +130,7 @@ export async function bundleWorker(
129
130
  });
130
131
  fs.writeFileSync(
131
132
  checkedFetchFileToInject,
132
- fs.readFileSync(path.resolve(__dirname, "../templates/checked-fetch.js"))
133
+ fs.readFileSync(path.resolve(getBasePath(), "templates/checked-fetch.js"))
133
134
  );
134
135
  }
135
136
 
@@ -302,7 +303,9 @@ async function applyFormatDevErrorsFacade(
302
303
  ): Promise<Entry> {
303
304
  const targetPath = path.join(tmpDirPath, "format-dev-errors.entry.js");
304
305
  await esbuild.build({
305
- entryPoints: [path.resolve(__dirname, "../templates/format-dev-errors.ts")],
306
+ entryPoints: [
307
+ path.resolve(getBasePath(), "templates/format-dev-errors.ts"),
308
+ ],
306
309
  bundle: true,
307
310
  sourcemap: true,
308
311
  format: "esm",
@@ -334,7 +337,7 @@ async function applyStaticAssetFacade(
334
337
 
335
338
  await esbuild.build({
336
339
  entryPoints: [
337
- path.resolve(__dirname, "../templates/serve-static-assets.ts"),
340
+ path.resolve(getBasePath(), "templates/serve-static-assets.ts"),
338
341
  ],
339
342
  bundle: true,
340
343
  format: "esm",
@@ -342,7 +345,7 @@ async function applyStaticAssetFacade(
342
345
  plugins: [
343
346
  esbuildAliasExternalPlugin({
344
347
  __ENTRY_POINT__: entry.file,
345
- __KV_ASSET_HANDLER__: path.join(__dirname, "../kv-asset-handler.js"),
348
+ __KV_ASSET_HANDLER__: path.join(getBasePath(), "kv-asset-handler.js"),
346
349
  __STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
347
350
  }),
348
351
  ],
@@ -380,7 +383,7 @@ async function applyMultiWorkerDevFacade(
380
383
  services: Config["services"],
381
384
  workerDefinitions: WorkerRegistry
382
385
  ) {
383
- const targetPath = path.join(tmpDirPath, "serve-static-assets.entry.js");
386
+ const targetPath = path.join(tmpDirPath, "multiworker-dev-facade.entry.js");
384
387
  const serviceMap = Object.fromEntries(
385
388
  (services || []).map((serviceBinding) => [
386
389
  serviceBinding.binding,
@@ -391,10 +394,10 @@ async function applyMultiWorkerDevFacade(
391
394
  await esbuild.build({
392
395
  entryPoints: [
393
396
  path.join(
394
- __dirname,
397
+ getBasePath(),
395
398
  entry.format === "modules"
396
- ? "../templates/service-bindings-module-facade.js"
397
- : "../templates/service-bindings-sw-facade.js"
399
+ ? "templates/service-bindings-module-facade.js"
400
+ : "templates/service-bindings-sw-facade.js"
398
401
  ),
399
402
  ],
400
403
  bundle: true,
@@ -440,8 +443,8 @@ async function applyFirstPartyWorkerDevFacade(
440
443
  await esbuild.build({
441
444
  entryPoints: [
442
445
  path.resolve(
443
- __dirname,
444
- "../templates/first-party-worker-module-facade.ts"
446
+ getBasePath(),
447
+ "templates/first-party-worker-module-facade.ts"
445
448
  ),
446
449
  ],
447
450
  bundle: true,
@@ -1,7 +1,9 @@
1
1
  import assert from "node:assert";
2
- import { fetch, Headers } from "undici";
2
+ import Busboy from "busboy";
3
+ import { fetch, File, FormData, Headers } from "undici";
3
4
  import { version as wranglerVersion } from "../../package.json";
4
5
  import { getEnvironmentVariableFactory } from "../environment-variables";
6
+ import { logger } from "../logger";
5
7
  import { ParseError, parseJSON } from "../parse";
6
8
  import { loginOrRefreshIfRequired, requireApiToken } from "../user";
7
9
  import type { ApiCredentials } from "../user";
@@ -44,6 +46,13 @@ export async function fetchInternal<ResponseType>(
44
46
 
45
47
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
46
48
  const method = init.method ?? "GET";
49
+
50
+ logger.debug(
51
+ `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}`
52
+ );
53
+ logger.debug("HEADERS:", JSON.stringify(headers, null, 2));
54
+ logger.debug("INIT:", JSON.stringify(init, null, 2));
55
+ logger.debug("-- END CF API REQUEST");
47
56
  const response = await fetch(
48
57
  `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
49
58
  {
@@ -54,6 +63,15 @@ export async function fetchInternal<ResponseType>(
54
63
  }
55
64
  );
56
65
  const jsonText = await response.text();
66
+ logger.debug(
67
+ "-- START CF API RESPONSE:",
68
+ response.statusText,
69
+ response.status
70
+ );
71
+ logger.debug("HEADERS:", JSON.stringify(response.headers, null, 2));
72
+ logger.debug("RESPONSE:", jsonText);
73
+ logger.debug("-- END CF API RESPONSE");
74
+
57
75
  try {
58
76
  return parseJSON<ResponseType>(jsonText);
59
77
  } catch (err) {
@@ -209,12 +227,72 @@ export async function fetchDashboardScript(
209
227
  ?.startsWith("multipart");
210
228
 
211
229
  if (usesModules) {
212
- const file = await response.text();
230
+ // Response from edge contains generic "name = worker.js" for dashboard created scripts
231
+ const form = await formData(response);
232
+ const entries = Array.from(form.entries());
233
+ if (entries.length > 1)
234
+ throw new RangeError("Expected only one entry in multipart response");
235
+ const [_, file] = entries[0];
236
+
237
+ if (file instanceof File) {
238
+ return await file.text();
239
+ }
240
+
241
+ return file ?? "";
213
242
 
214
243
  // Follow up on issue in Undici about multipart/form-data support & replace the workaround: https://github.com/nodejs/undici/issues/974
215
244
  // This should be using a builtin formData() parser pattern.
216
- return file.split("\n").slice(4, -4).join("\n");
217
245
  } else {
218
246
  return response.text();
219
247
  }
220
248
  }
249
+
250
+ async function formData({ headers, body }: Response): Promise<FormData> {
251
+ // undici doesn't include a multipart/form-data parser yet, so we parse
252
+ // form data with busboy instead
253
+ const contentType = headers.get("Content-Type") ?? "";
254
+ if (!/multipart\/form-data/.test(contentType))
255
+ throw Error("Need Content-Type for multipart/form-data");
256
+
257
+ const responseFormData = new FormData();
258
+
259
+ let busboy: Busboy.Busboy;
260
+
261
+ const parsedHeaders = Object.fromEntries(
262
+ Array.from(headers).map(([header, value]) => [header.toLowerCase(), value])
263
+ );
264
+ try {
265
+ busboy = Busboy({ headers: parsedHeaders });
266
+ } catch (err) {
267
+ // Error due to headers:
268
+ throw Object.assign(new TypeError(), { cause: err });
269
+ }
270
+
271
+ busboy.on("field", (name, value) => {
272
+ responseFormData.append(name, value);
273
+ });
274
+ busboy.on("file", (name, value, info) => {
275
+ const { filename, encoding, mimeType } = info;
276
+ const base64 = encoding.toLowerCase() === "base64";
277
+ const chunks: Buffer[] = [];
278
+ value.on("data", (chunk) => {
279
+ if (base64) chunk = Buffer.from(chunk.toString(), "base64");
280
+ chunks.push(chunk);
281
+ });
282
+ value.on("end", () => {
283
+ const file = new File(chunks, filename, { type: mimeType });
284
+ responseFormData.append(name, file);
285
+ });
286
+ });
287
+
288
+ const busboyResolve = new Promise((resolve, reject) => {
289
+ busboy.on("finish", resolve);
290
+ busboy.on("error", (err) => reject(err));
291
+ });
292
+
293
+ if (body !== null) for await (const chunk of body) busboy.write(chunk);
294
+ busboy.end();
295
+ await busboyResolve;
296
+
297
+ return responseFormData;
298
+ }
package/src/cli.ts CHANGED
@@ -24,4 +24,4 @@ if (typeof jest === "undefined" && require.main) {
24
24
  * It makes it possible to import wrangler from 'wrangler',
25
25
  * and call wrangler.unstable_dev().
26
26
  */
27
- export default { unstable_dev };
27
+ export { unstable_dev };
@@ -210,7 +210,7 @@ interface EnvironmentInheritable {
210
210
  * @default `[]`
211
211
  * @nonInheritable
212
212
  */
213
- worker_namespaces: {
213
+ dispatch_namespaces: {
214
214
  /** The binding name used to refer to the bound service. */
215
215
  binding: string;
216
216
  /** The namespace to bind to. */
@@ -91,7 +91,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
91
91
  unsafe,
92
92
  vars,
93
93
  wasm_modules,
94
- worker_namespaces,
94
+ dispatch_namespaces,
95
95
  } = bindings;
96
96
 
97
97
  if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) {
@@ -219,10 +219,10 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
219
219
  });
220
220
  }
221
221
 
222
- if (worker_namespaces !== undefined && worker_namespaces.length > 0) {
222
+ if (dispatch_namespaces !== undefined && dispatch_namespaces.length > 0) {
223
223
  output.push({
224
- type: "Worker Namespaces",
225
- entries: worker_namespaces.map(({ binding, namespace }) => {
224
+ type: "dispatch namespaces",
225
+ entries: dispatch_namespaces.map(({ binding, namespace }) => {
226
226
  return {
227
227
  key: binding,
228
228
  value: namespace,
@@ -383,7 +383,7 @@ export const validateRequiredProperty = (
383
383
  container: string,
384
384
  key: string,
385
385
  value: unknown,
386
- type: string,
386
+ type: TypeofType,
387
387
  choices?: unknown[]
388
388
  ): boolean => {
389
389
  if (container) {
@@ -417,7 +417,7 @@ export const validateOptionalProperty = (
417
417
  container: string,
418
418
  key: string,
419
419
  value: unknown,
420
- type: string,
420
+ type: TypeofType,
421
421
  choices?: unknown[]
422
422
  ): boolean => {
423
423
  if (value !== undefined) {
@@ -440,7 +440,7 @@ export const validateTypedArray = (
440
440
  diagnostics: Diagnostics,
441
441
  container: string,
442
442
  value: unknown,
443
- type: string
443
+ type: TypeofType
444
444
  ): boolean => {
445
445
  let isValid = true;
446
446
  if (!Array.isArray(value)) {
@@ -472,7 +472,7 @@ export const validateOptionalTypedArray = (
472
472
  diagnostics: Diagnostics,
473
473
  container: string,
474
474
  value: unknown,
475
- type: string
475
+ type: TypeofType
476
476
  ) => {
477
477
  if (value !== undefined) {
478
478
  return validateTypedArray(diagnostics, container, value, type);
@@ -486,7 +486,7 @@ export const validateOptionalTypedArray = (
486
486
  export const isRequiredProperty = <T extends object>(
487
487
  obj: object,
488
488
  prop: keyof T,
489
- type: string,
489
+ type: TypeofType,
490
490
  choices?: unknown[]
491
491
  ): obj is T =>
492
492
  hasProperty<T>(obj, prop) &&
@@ -499,7 +499,7 @@ export const isRequiredProperty = <T extends object>(
499
499
  export const isOptionalProperty = <T extends object>(
500
500
  obj: object,
501
501
  prop: keyof T,
502
- type: string
502
+ type: TypeofType
503
503
  ): obj is T => !hasProperty<T>(obj, prop) || typeof obj[prop] === type;
504
504
 
505
505
  /**
@@ -582,3 +582,16 @@ const isRecord = (
582
582
  value: unknown
583
583
  ): value is Record<string | number | symbol, unknown> =>
584
584
  typeof value === "object" && value !== null && !Array.isArray(value);
585
+
586
+ /**
587
+ * JavaScript `typeof` operator return values.
588
+ */
589
+ type TypeofType =
590
+ | "string"
591
+ | "number"
592
+ | "bigint"
593
+ | "boolean"
594
+ | "symbol"
595
+ | "undefined"
596
+ | "object"
597
+ | "function";
@@ -899,7 +899,7 @@ function normalizeAndValidateEnvironment(
899
899
 
900
900
  experimental(diagnostics, rawEnv, "unsafe");
901
901
  experimental(diagnostics, rawEnv, "services");
902
- experimental(diagnostics, rawEnv, "worker_namespaces");
902
+ experimental(diagnostics, rawEnv, "dispatch_namespaces");
903
903
 
904
904
  const route = normalizeAndValidateRoute(diagnostics, topLevelEnv, rawEnv);
905
905
 
@@ -1085,13 +1085,13 @@ function normalizeAndValidateEnvironment(
1085
1085
  validateBindingArray(envName, validateServiceBinding),
1086
1086
  []
1087
1087
  ),
1088
- worker_namespaces: notInheritable(
1088
+ dispatch_namespaces: notInheritable(
1089
1089
  diagnostics,
1090
1090
  topLevelEnv,
1091
1091
  rawConfig,
1092
1092
  rawEnv,
1093
1093
  envName,
1094
- "worker_namespaces",
1094
+ "dispatch_namespaces",
1095
1095
  validateBindingArray(envName, validateWorkerNamespaceBinding),
1096
1096
  []
1097
1097
  ),
@@ -1630,7 +1630,10 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1630
1630
  );
1631
1631
  isValid = false;
1632
1632
  }
1633
- if (!isRequiredProperty(value, "id", "string")) {
1633
+ if (
1634
+ !isRequiredProperty(value, "id", "string") ||
1635
+ (value as { id: string }).id.length === 0
1636
+ ) {
1634
1637
  diagnostics.errors.push(
1635
1638
  `"${field}" bindings should have a string "id" field but got ${JSON.stringify(
1636
1639
  value
@@ -1668,7 +1671,10 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1668
1671
  );
1669
1672
  isValid = false;
1670
1673
  }
1671
- if (!isRequiredProperty(value, "bucket_name", "string")) {
1674
+ if (
1675
+ !isRequiredProperty(value, "bucket_name", "string") ||
1676
+ (value as { bucket_name: string }).bucket_name.length === 0
1677
+ ) {
1672
1678
  diagnostics.errors.push(
1673
1679
  `"${field}" bindings should have a string "bucket_name" field but got ${JSON.stringify(
1674
1680
  value
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import * as path from "path";
3
3
  import { findUpSync } from "find-up";
4
+ import { CI } from "./is-ci";
4
5
  import isInteractive from "./is-interactive";
5
6
  import { logger } from "./logger";
6
7
 
@@ -29,7 +30,7 @@ const arrayFormatter = new Intl.ListFormat("en", {
29
30
  });
30
31
 
31
32
  function showCacheMessage(fields: string[], folder: string) {
32
- if (!cacheMessageShown && isInteractive()) {
33
+ if (!cacheMessageShown && isInteractive() && !CI.isCI()) {
33
34
  if (fields.length > 0) {
34
35
  logger.log(
35
36
  `Retrieving cached values for ${arrayFormatter.format(
@@ -23,6 +23,31 @@ export function toMimeType(type: CfModuleType): string {
23
23
  }
24
24
  }
25
25
 
26
+ type WorkerMetadataBinding =
27
+ // If you add any new binding types here, also add it to safeBindings
28
+ // under validateUnsafeBinding in config/validation.ts
29
+ | { type: "plain_text"; name: string; text: string }
30
+ | { type: "json"; name: string; json: unknown }
31
+ | { type: "wasm_module"; name: string; part: string }
32
+ | { type: "text_blob"; name: string; part: string }
33
+ | { type: "data_blob"; name: string; part: string }
34
+ | { type: "kv_namespace"; name: string; namespace_id: string }
35
+ | {
36
+ type: "durable_object_namespace";
37
+ name: string;
38
+ class_name: string;
39
+ script_name?: string;
40
+ environment?: string;
41
+ }
42
+ | { type: "r2_bucket"; name: string; bucket_name: string }
43
+ | { type: "service"; name: string; service: string; environment?: string }
44
+ | { type: "namespace"; name: string; namespace: string }
45
+ | {
46
+ type: "logfwdr";
47
+ name: string;
48
+ destination: string;
49
+ };
50
+
26
51
  export interface WorkerMetadata {
27
52
  /** The name of the entry point module. Only exists when the worker is in the ES module format */
28
53
  main_module?: string;
@@ -33,31 +58,8 @@ export interface WorkerMetadata {
33
58
  usage_model?: "bundled" | "unbound";
34
59
  migrations?: CfDurableObjectMigrations;
35
60
  capnp_schema?: string;
36
- // If you add any new binding types here, also add it to safeBindings
37
- // under validateUnsafeBinding in config/validation.ts
38
- bindings: (
39
- | { type: "plain_text"; name: string; text: string }
40
- | { type: "json"; name: string; json: unknown }
41
- | { type: "wasm_module"; name: string; part: string }
42
- | { type: "text_blob"; name: string; part: string }
43
- | { type: "data_blob"; name: string; part: string }
44
- | { type: "kv_namespace"; name: string; namespace_id: string }
45
- | {
46
- type: "durable_object_namespace";
47
- name: string;
48
- class_name: string;
49
- script_name?: string;
50
- environment?: string;
51
- }
52
- | { type: "r2_bucket"; name: string; bucket_name: string }
53
- | { type: "service"; name: string; service: string; environment?: string }
54
- | { type: "namespace"; name: string; namespace: string }
55
- | {
56
- type: "logfwdr";
57
- name: string;
58
- destination: string;
59
- }
60
- )[];
61
+ bindings: WorkerMetadataBinding[];
62
+ keep_bindings: WorkerMetadataBinding["type"][];
61
63
  }
62
64
 
63
65
  /**
@@ -123,7 +125,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
123
125
  });
124
126
  });
125
127
 
126
- bindings.worker_namespaces?.forEach(({ binding, namespace }) => {
128
+ bindings.dispatch_namespaces?.forEach(({ binding, namespace }) => {
127
129
  metadataBindings.push({
128
130
  name: binding,
129
131
  type: "namespace",
@@ -255,6 +257,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
255
257
  ...(usage_model && { usage_model }),
256
258
  ...(migrations && { migrations }),
257
259
  capnp_schema: bindings.logfwdr?.schema,
260
+ keep_bindings: ["plain_text", "json"],
258
261
  };
259
262
 
260
263
  formData.set("metadata", JSON.stringify(metadata));