wrangler 0.0.0-e7cf0caa → 0.0.0-e87ad068

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 (85) hide show
  1. package/miniflare-dist/index.mjs +167 -93
  2. package/package.json +6 -6
  3. package/src/__tests__/configuration.test.ts +218 -0
  4. package/src/__tests__/constellation.test.ts +371 -0
  5. package/src/__tests__/d1/migrate.test.ts +100 -0
  6. package/src/__tests__/index.test.ts +4 -2
  7. package/src/__tests__/init.test.ts +109 -3
  8. package/src/__tests__/mtls-certificates.test.ts +2 -1
  9. package/src/__tests__/pages/functions-build.test.ts +77 -0
  10. package/src/__tests__/pages-deployment-tail.test.ts +2 -0
  11. package/src/__tests__/publish.test.ts +646 -187
  12. package/src/__tests__/tail.test.ts +49 -7
  13. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  14. package/src/__tests__/unstableDev.test.ts +61 -0
  15. package/src/__tests__/user.test.ts +1 -1
  16. package/src/api/dev.ts +12 -0
  17. package/src/api/pages/create-worker-bundle-contents.ts +4 -0
  18. package/src/api/pages/publish.tsx +28 -12
  19. package/src/bundle.ts +58 -14
  20. package/src/cfetch/internal.ts +19 -14
  21. package/src/config/environment.ts +41 -0
  22. package/src/config/index.ts +21 -0
  23. package/src/config/validation.ts +182 -0
  24. package/src/constellation/createProject.tsx +51 -0
  25. package/src/constellation/deleteProject.ts +51 -0
  26. package/src/constellation/deleteProjectModel.ts +76 -0
  27. package/src/constellation/index.ts +75 -0
  28. package/src/constellation/listCatalog.tsx +35 -0
  29. package/src/constellation/listModel.tsx +49 -0
  30. package/src/constellation/listProject.tsx +28 -0
  31. package/src/constellation/listRuntime.tsx +28 -0
  32. package/src/constellation/options.ts +17 -0
  33. package/src/constellation/types.ts +17 -0
  34. package/src/constellation/uploadModel.tsx +84 -0
  35. package/src/constellation/utils.ts +90 -0
  36. package/src/create-worker-upload-form.ts +25 -0
  37. package/src/d1/backups.tsx +4 -4
  38. package/src/d1/constants.ts +3 -0
  39. package/src/d1/create.tsx +77 -43
  40. package/src/d1/delete.ts +1 -1
  41. package/src/d1/execute.tsx +27 -13
  42. package/src/d1/list.tsx +13 -11
  43. package/src/d1/migrations/apply.tsx +29 -6
  44. package/src/d1/migrations/helpers.ts +8 -9
  45. package/src/d1/types.ts +7 -0
  46. package/src/dev/dev.tsx +9 -2
  47. package/src/dev/local.tsx +52 -20
  48. package/src/dev/remote.tsx +4 -0
  49. package/src/dev/start-server.ts +64 -40
  50. package/src/dev/use-esbuild.ts +70 -43
  51. package/src/dev-registry.ts +11 -0
  52. package/src/dev.tsx +23 -6
  53. package/src/docs/helpers.ts +50 -0
  54. package/src/docs/index.ts +25 -68
  55. package/src/entry.ts +8 -1
  56. package/src/environment-variables/factory.ts +2 -1
  57. package/src/generate/index.ts +5 -0
  58. package/src/index.ts +12 -1
  59. package/src/init.ts +52 -10
  60. package/src/inspect.ts +107 -6
  61. package/src/kv/helpers.ts +7 -4
  62. package/src/logger.ts +5 -1
  63. package/src/miniflare-cli/index.ts +2 -2
  64. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -1
  65. package/src/module-collection.ts +1 -1
  66. package/src/pages/build.ts +30 -17
  67. package/src/pages/dev.ts +51 -8
  68. package/src/pages/functions/buildPlugin.ts +1 -0
  69. package/src/pages/functions/buildWorker.ts +92 -2
  70. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  71. package/src/publish/publish.ts +9 -1
  72. package/src/secret/index.ts +4 -0
  73. package/src/sites.ts +164 -52
  74. package/src/tail/createTail.ts +9 -0
  75. package/src/tail/printing.ts +12 -0
  76. package/src/traverse-module-graph.ts +2 -1
  77. package/src/type-generation.ts +6 -0
  78. package/src/user/user.ts +1 -0
  79. package/src/utils/render.ts +1 -1
  80. package/src/worker.ts +26 -0
  81. package/templates/__tests__/tsconfig.tsbuildinfo +1 -1
  82. package/templates/pages-dev-util.ts +4 -1
  83. package/templates/tsconfig.tsbuildinfo +1 -1
  84. package/wrangler-dist/cli.d.ts +78 -2
  85. package/wrangler-dist/cli.js +65057 -63883
@@ -0,0 +1,61 @@
1
+ import { fetch } from "undici";
2
+ import {
3
+ stopWorkerRegistry,
4
+ registerWorker,
5
+ startWorkerRegistry,
6
+ } from "../dev-registry";
7
+
8
+ jest.unmock("undici");
9
+
10
+ /**
11
+ * Sometimes the devRegistry killed by some reason, the register worker will to restart it.
12
+ */
13
+ describe("unstable devRegistry testing", () => {
14
+ afterAll(async () => {
15
+ await stopWorkerRegistry();
16
+ });
17
+
18
+ it("should start the devRegistry if the devRegistry not start", async () => {
19
+ await registerWorker("test", {
20
+ port: 6789,
21
+ protocol: "http",
22
+ host: "localhost",
23
+ mode: "local",
24
+ durableObjects: [{ name: "testing", className: "testing" }],
25
+ });
26
+ const resp = await fetch("http://localhost:6284/workers");
27
+ if (resp) {
28
+ const parsedResp = (await resp.json()) as {
29
+ test: unknown;
30
+ };
31
+ expect(parsedResp.test).toBeTruthy();
32
+ }
33
+ });
34
+
35
+ it("should not restart the devRegistry if the devRegistry already start", async () => {
36
+ await startWorkerRegistry();
37
+
38
+ await fetch("http://localhost:6284/workers/init", {
39
+ method: "POST",
40
+ body: JSON.stringify({}),
41
+ });
42
+
43
+ await registerWorker("test", {
44
+ port: 6789,
45
+ protocol: "http",
46
+ host: "localhost",
47
+ mode: "local",
48
+ durableObjects: [{ name: "testing", className: "testing" }],
49
+ });
50
+
51
+ const resp = await fetch("http://localhost:6284/workers");
52
+ if (resp) {
53
+ const parsedResp = (await resp.json()) as {
54
+ test: unknown;
55
+ init: unknown;
56
+ };
57
+ expect(parsedResp.init).toBeTruthy();
58
+ expect(parsedResp.test).toBeTruthy();
59
+ }
60
+ });
61
+ });
@@ -60,7 +60,7 @@ describe("User", () => {
60
60
  expect(counter).toBe(1);
61
61
  expect(std.out).toMatchInlineSnapshot(`
62
62
  "Attempting to login via OAuth...
63
- Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
63
+ Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
64
64
  Successfully logged in."
65
65
  `);
66
66
  expect(readAuthConfigFile()).toEqual<UserAuthConfig>({
package/src/api/dev.ts CHANGED
@@ -3,7 +3,9 @@ import { startApiDev, startDev } from "../dev";
3
3
  import { logger } from "../logger";
4
4
 
5
5
  import type { Environment } from "../config";
6
+ import type { Rule } from "../config/environment";
6
7
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
8
+ import type { CfModule } from "../worker";
7
9
  import type { RequestInit, Response, RequestInfo } from "undici";
8
10
 
9
11
  export interface UnstableDevOptions {
@@ -42,11 +44,15 @@ export interface UnstableDevOptions {
42
44
  bucket_name: string;
43
45
  preview_bucket_name?: string;
44
46
  }[];
47
+ moduleRoot?: string;
48
+ rules?: Rule[];
45
49
  logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"]
46
50
  inspect?: boolean;
47
51
  local?: boolean;
48
52
  accountId?: string;
49
53
  experimental?: {
54
+ processEntrypoint?: boolean;
55
+ additionalModules?: CfModule[];
50
56
  d1Databases?: Environment["d1_databases"];
51
57
  disableExperimentalWarning?: boolean; // Disables wrangler's warning when unstable APIs are used.
52
58
  disableDevRegistry?: boolean; // Disables wrangler's support multi-worker setups. May reduce flakiness when used in tests in CI.
@@ -92,6 +98,8 @@ export async function unstable_dev(
92
98
  const {
93
99
  // there are two types of "experimental" options:
94
100
  // 1. options to unstable_dev that we're still testing or are unsure of
101
+ processEntrypoint = false,
102
+ additionalModules,
95
103
  disableDevRegistry,
96
104
  disableExperimentalWarning,
97
105
  forceLocal,
@@ -150,6 +158,8 @@ export async function unstable_dev(
150
158
  },
151
159
  config: options?.config,
152
160
  env: options?.env,
161
+ processEntrypoint,
162
+ additionalModules,
153
163
  bundle: options?.bundle,
154
164
  compatibilityDate: options?.compatibilityDate,
155
165
  compatibilityFlags: options?.compatibilityFlags,
@@ -240,6 +250,8 @@ export async function unstable_dev(
240
250
  },
241
251
  config: options?.config,
242
252
  env: options?.env,
253
+ processEntrypoint,
254
+ additionalModules,
243
255
  bundle: options?.bundle,
244
256
  compatibilityDate: options?.compatibilityDate,
245
257
  compatibilityFlags: options?.compatibilityFlags,
@@ -52,11 +52,13 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData {
52
52
  send_email: undefined,
53
53
  wasm_modules: undefined,
54
54
  text_blobs: undefined,
55
+ browser: undefined,
55
56
  data_blobs: undefined,
56
57
  durable_objects: undefined,
57
58
  queues: undefined,
58
59
  r2_buckets: undefined,
59
60
  d1_databases: undefined,
61
+ constellation: undefined,
60
62
  services: undefined,
61
63
  analytics_engine_datasets: undefined,
62
64
  dispatch_namespaces: undefined,
@@ -70,6 +72,8 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData {
70
72
  usage_model: undefined,
71
73
  keepVars: undefined,
72
74
  logpush: undefined,
75
+ placement: undefined,
76
+ tail_consumers: undefined,
73
77
  };
74
78
 
75
79
  return createWorkerUploadForm(worker);
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, lstatSync, readFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, resolve as resolvePath } from "node:path";
4
4
  import { cwd } from "node:process";
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  buildRawWorker,
16
16
  checkRawWorker,
17
+ traverseAndBuildWorkerJSDirectory,
17
18
  } from "../../pages/functions/buildWorker";
18
19
  import { validateRoutes } from "../../pages/functions/routes-validation";
19
20
  import { upload } from "../../pages/upload";
@@ -65,7 +66,7 @@ interface PagesPublishOptions {
65
66
 
66
67
  /**
67
68
  * Whether to run bundling on `_worker.js` before deploying.
68
- * Default: false
69
+ * Default: true
69
70
  */
70
71
  bundle?: boolean;
71
72
 
@@ -95,9 +96,12 @@ export async function publish({
95
96
  _redirects: string | undefined,
96
97
  _routesGenerated: string | undefined,
97
98
  _routesCustom: string | undefined,
99
+ _workerJSIsDirectory = false,
98
100
  _workerJS: string | undefined;
99
101
 
100
- const workerScriptPath = resolvePath(directory, "_worker.js");
102
+ bundle = bundle ?? true;
103
+
104
+ const _workerPath = resolvePath(directory, "_worker.js");
101
105
 
102
106
  try {
103
107
  _headers = readFileSync(join(directory, "_headers"), "utf-8");
@@ -116,7 +120,10 @@ export async function publish({
116
120
  } catch {}
117
121
 
118
122
  try {
119
- _workerJS = readFileSync(workerScriptPath, "utf-8");
123
+ _workerJSIsDirectory = lstatSync(_workerPath).isDirectory();
124
+ if (!_workerJSIsDirectory) {
125
+ _workerJS = readFileSync(_workerPath, "utf-8");
126
+ }
120
127
  } catch {}
121
128
 
122
129
  // Grab the bindings from the API, we need these for shims and other such hacky inserts
@@ -240,16 +247,23 @@ export async function publish({
240
247
  * Advanced Mode
241
248
  * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
242
249
  *
243
- * When using a _worker.js file, the entire /functions directory is ignored
250
+ * When using a _worker.js file or _worker.js/ directory, the entire /functions directory is ignored
244
251
  * – this includes its routing and middleware characteristics.
245
252
  */
246
- if (_workerJS) {
253
+ if (_workerJSIsDirectory) {
254
+ workerBundle = await traverseAndBuildWorkerJSDirectory({
255
+ workerJSDirectory: _workerPath,
256
+ buildOutputDirectory: directory,
257
+ d1Databases,
258
+ nodejsCompat,
259
+ });
260
+ } else if (_workerJS) {
247
261
  if (bundle) {
248
262
  const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
249
263
  workerBundle = await buildRawWorker({
250
- workerScriptPath,
264
+ workerScriptPath: _workerPath,
251
265
  outfile,
252
- directory: directory ?? ".",
266
+ directory,
253
267
  local: false,
254
268
  sourcemap: true,
255
269
  watch: false,
@@ -258,17 +272,19 @@ export async function publish({
258
272
  nodejsCompat,
259
273
  });
260
274
  } else {
261
- await checkRawWorker(workerScriptPath, () => {});
262
- // TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769
275
+ await checkRawWorker(_workerPath, () => {});
276
+ // TODO: Let users configure this in the future.
263
277
  workerBundle = {
264
278
  modules: [],
265
279
  dependencies: {},
266
280
  stop: undefined,
267
- resolvedEntryPointPath: workerScriptPath,
281
+ resolvedEntryPointPath: _workerPath,
268
282
  bundleType: "esm",
269
283
  };
270
284
  }
285
+ }
271
286
 
287
+ if (_workerJS || _workerJSIsDirectory) {
272
288
  const workerBundleContents = await createUploadWorkerBundleContents(
273
289
  workerBundle as BundleResult
274
290
  );
@@ -302,7 +318,7 @@ export async function publish({
302
318
  * Pages Functions
303
319
  * https://developers.cloudflare.com/pages/platform/functions/
304
320
  */
305
- if (builtFunctions && !_workerJS) {
321
+ if (builtFunctions && !_workerJS && !_workerJSIsDirectory) {
306
322
  const workerBundleContents = await createUploadWorkerBundleContents(
307
323
  workerBundle as BundleResult
308
324
  );
package/src/bundle.ts CHANGED
@@ -7,11 +7,12 @@ 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, toUrlPath } from "./paths";
10
+ import { getBasePath } from "./paths";
11
11
  import type { Config } from "./config";
12
12
  import type { DurableObjectBindings } from "./config/environment";
13
13
  import type { WorkerRegistry } from "./dev-registry";
14
14
  import type { Entry } from "./entry";
15
+ import type { SourceMapMetadata } from "./inspect";
15
16
  import type { CfModule } from "./worker";
16
17
 
17
18
  export const COMMON_ESBUILD_OPTIONS = {
@@ -27,6 +28,7 @@ export type BundleResult = {
27
28
  bundleType: "esm" | "commonjs";
28
29
  stop: (() => void) | undefined;
29
30
  sourceMapPath?: string | undefined;
31
+ sourceMapMetadata?: SourceMapMetadata | undefined;
30
32
  };
31
33
 
32
34
  type StaticAssetsConfig =
@@ -77,15 +79,25 @@ export function isBuildFailure(err: unknown): err is esbuild.BuildFailure {
77
79
  * Rewrites esbuild BuildFailures for failing to resolve Node built-in modules
78
80
  * to suggest enabling Node compat as opposed to `platform: "node"`.
79
81
  */
80
- export function rewriteNodeCompatBuildFailure(err: esbuild.BuildFailure) {
82
+ export function rewriteNodeCompatBuildFailure(
83
+ err: esbuild.BuildFailure,
84
+ forPages = false
85
+ ) {
81
86
  for (const error of err.errors) {
82
87
  const match = nodeBuiltinResolveErrorText.exec(error.text);
83
88
  if (match !== null) {
89
+ const issue = `The package "${match[1]}" wasn't found on the file system but is built into node.`;
90
+
91
+ const instructionForUser = `${
92
+ forPages
93
+ ? 'Add the "nodejs_compat" compatibility flag to your Pages project'
94
+ : 'Add "node_compat = true" to your wrangler.toml file'
95
+ } to enable Node.js compatibility.`;
96
+
84
97
  error.notes = [
85
98
  {
86
99
  location: null,
87
- text: `The package "${match[1]}" wasn't found on the file system but is built into node.
88
- Add "node_compat = true" to your wrangler.toml file to enable Node compatibility.`,
100
+ text: `${issue}\n${instructionForUser}`,
89
101
  },
90
102
  ];
91
103
  }
@@ -117,6 +129,8 @@ export async function bundleWorker(
117
129
  entry: Entry,
118
130
  destination: string,
119
131
  options: {
132
+ // When `bundle` is set to false, we apply shims to the Worker, but won't pull in any imports
133
+ bundle?: boolean;
120
134
  serveAssetsFromWorker: boolean;
121
135
  assets?: StaticAssetsConfig;
122
136
  betaD1Shims?: string[];
@@ -143,12 +157,15 @@ export async function bundleWorker(
143
157
  loader?: Record<string, string>;
144
158
  sourcemap?: esbuild.CommonOptions["sourcemap"];
145
159
  plugins?: esbuild.Plugin[];
160
+ additionalModules?: CfModule[];
146
161
  // TODO: Rip these out https://github.com/cloudflare/workers-sdk/issues/2153
147
162
  disableModuleCollection?: boolean;
148
163
  isOutfile?: boolean;
164
+ forPages?: boolean;
149
165
  }
150
166
  ): Promise<BundleResult> {
151
167
  const {
168
+ bundle = true,
152
169
  serveAssetsFromWorker,
153
170
  betaD1Shims,
154
171
  doBindings,
@@ -176,6 +193,8 @@ export async function bundleWorker(
176
193
  plugins,
177
194
  disableModuleCollection,
178
195
  isOutfile,
196
+ forPages,
197
+ additionalModules = [],
179
198
  } = options;
180
199
 
181
200
  // We create a temporary directory for any oneoff files we
@@ -350,7 +369,7 @@ export async function bundleWorker(
350
369
 
351
370
  const buildOptions: esbuild.BuildOptions & { metafile: true } = {
352
371
  entryPoints: [inputEntry.file],
353
- bundle: true,
372
+ bundle,
354
373
  absWorkingDir: entry.directory,
355
374
  outdir: destination,
356
375
  entryNames: entryName || path.parse(entry.file).name,
@@ -362,10 +381,10 @@ export async function bundleWorker(
362
381
  }
363
382
  : {}),
364
383
  inject,
365
- external: ["__STATIC_CONTENT_MANIFEST"],
384
+ external: bundle ? ["__STATIC_CONTENT_MANIFEST"] : undefined,
366
385
  format: entry.format === "modules" ? "esm" : "iife",
367
386
  target: COMMON_ESBUILD_OPTIONS.target,
368
- sourcemap: sourcemap ?? true, // this needs to use ?? to accept false
387
+ sourcemap: sourcemap ?? true,
369
388
  // Include a reference to the output folder in the sourcemap.
370
389
  // This is omitted by default, but we need it to properly resolve source paths in error output.
371
390
  sourceRoot: destination,
@@ -409,7 +428,7 @@ export async function bundleWorker(
409
428
  result = await esbuild.build(buildOptions);
410
429
  } catch (e) {
411
430
  if (!legacyNodeCompat && isBuildFailure(e))
412
- rewriteNodeCompatBuildFailure(e);
431
+ rewriteNodeCompatBuildFailure(e, forPages);
413
432
  throw e;
414
433
  }
415
434
 
@@ -440,21 +459,34 @@ export async function bundleWorker(
440
459
  entryPointOutputs[0][0]
441
460
  );
442
461
 
462
+ // A collision between additionalModules and moduleCollector.modules is incredibly unlikely because moduleCollector hashes the modules it collects.
463
+ // However, if it happens, let's trust the explicitly provided additionalModules over the ones we discovered.
464
+ const modules = dedupeModulesByName([
465
+ ...moduleCollector.modules,
466
+ ...additionalModules,
467
+ ]);
468
+
443
469
  // copy all referenced modules into the output bundle directory
444
- for (const module of moduleCollector.modules) {
445
- fs.writeFileSync(
446
- path.join(path.dirname(resolvedEntryPointPath), module.name),
447
- module.content
470
+ for (const module of modules) {
471
+ const modulePath = path.join(
472
+ path.dirname(resolvedEntryPointPath),
473
+ module.name
448
474
  );
475
+ fs.mkdirSync(path.dirname(modulePath), { recursive: true });
476
+ fs.writeFileSync(modulePath, module.content);
449
477
  }
450
478
 
451
479
  return {
452
- modules: moduleCollector.modules,
480
+ modules,
453
481
  dependencies,
454
482
  resolvedEntryPointPath,
455
483
  bundleType,
456
484
  stop: result.stop,
457
485
  sourceMapPath,
486
+ sourceMapMetadata: {
487
+ tmpDir: tmpDir.path,
488
+ entryDirectory: entry.directory,
489
+ },
458
490
  };
459
491
  }
460
492
 
@@ -599,7 +631,7 @@ async function applyMiddlewareLoaderFacade(
599
631
  ...Object.fromEntries(
600
632
  middleware.map((val, index) => [
601
633
  middlewareIdentifiers[index],
602
- toUrlPath(path.resolve(getBasePath(), val.path)),
634
+ path.resolve(getBasePath(), val.path),
603
635
  ])
604
636
  ),
605
637
  }),
@@ -905,4 +937,16 @@ function listEntryPoints(
905
937
  return outputs.map(([_input, output]) => output.entryPoint).join("\n");
906
938
  }
907
939
 
940
+ /**
941
+ * Prefer modules towards the end of the array in the case of a collision by name.
942
+ */
943
+ export function dedupeModulesByName(modules: CfModule[]): CfModule[] {
944
+ return Object.values(
945
+ modules.reduce((moduleMap, module) => {
946
+ moduleMap[module.name] = module;
947
+ return moduleMap;
948
+ }, {} as Record<string, CfModule>)
949
+ );
950
+ }
951
+
908
952
  type ValueOf<T> = T[keyof T];
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { fetch, File, Headers } from "undici";
3
+ import { Response } from "undici";
3
4
  import { version as wranglerVersion } from "../../package.json";
4
5
  import { getCloudflareApiBaseUrl } from "../environment-variables/misc-variables";
5
6
  import { logger } from "../logger";
@@ -7,7 +8,7 @@ import { ParseError, parseJSON } from "../parse";
7
8
  import { loginOrRefreshIfRequired, requireApiToken } from "../user";
8
9
  import type { ApiCredentials } from "../user";
9
10
  import type { URLSearchParams } from "node:url";
10
- import type { RequestInit, HeadersInit, Response } from "undici";
11
+ import type { RequestInit, HeadersInit } from "undici";
11
12
 
12
13
  /*
13
14
  * performApiFetch does everything required to make a CF API request,
@@ -217,19 +218,20 @@ export async function fetchR2Objects(
217
218
  export async function fetchDashboardScript(
218
219
  resource: string,
219
220
  bodyInit: RequestInit = {}
220
- ): Promise<string> {
221
+ ): Promise<File[]> {
221
222
  await requireLoggedIn();
222
223
  const auth = requireApiToken();
223
224
  const headers = cloneHeaders(bodyInit.headers);
224
225
  addAuthorizationHeaderIfUnspecified(headers, auth);
225
226
  addUserAgent(headers);
226
227
 
227
- const response = await fetch(`${getCloudflareApiBaseUrl()}${resource}`, {
228
+ let response = await fetch(`${getCloudflareApiBaseUrl()}${resource}`, {
228
229
  ...bodyInit,
229
230
  headers,
230
231
  });
231
232
 
232
233
  if (!response.ok || !response.body) {
234
+ console.error(response.ok, response.body);
233
235
  throw new Error(
234
236
  `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
235
237
  );
@@ -240,19 +242,22 @@ export async function fetchDashboardScript(
240
242
  ?.startsWith("multipart");
241
243
 
242
244
  if (usesModules) {
243
- // Response from edge contains generic "name = worker.js" for dashboard created scripts
244
- const form = await response.formData();
245
- const entries = Array.from(form.entries());
246
- if (entries.length > 1)
247
- throw new RangeError("Expected only one entry in multipart response");
248
- const [_, file] = entries[0];
249
-
250
- if (file instanceof File) {
251
- return await file.text();
245
+ // For testing purposes only, sorry not sorry -- msw doesn't implement Response#formData
246
+ if (!response.formData) {
247
+ response = new Response(await response.text(), response);
252
248
  }
253
249
 
254
- return file ?? "";
250
+ const form = await response.formData();
251
+ const files = Array.from(form.entries()).map(([filename, contents]) =>
252
+ contents instanceof File ? contents : new File([contents], filename)
253
+ );
254
+
255
+ return files;
255
256
  } else {
256
- return response.text();
257
+ const contents = await response.text();
258
+ const filename = response.headers.get("cf-entrypoint") ?? "index.js";
259
+ const file = new File([contents], filename, { type: "text" });
260
+
261
+ return [file];
257
262
  }
258
263
  }
@@ -265,6 +265,13 @@ interface EnvironmentInheritable {
265
265
  * @inheritable
266
266
  */
267
267
  logpush: boolean | undefined;
268
+
269
+ /**
270
+ * Specify how the worker should be located to minimize round-trip time.
271
+ *
272
+ * More details: https://developers.cloudflare.com/workers/platform/smart-placement/
273
+ */
274
+ placement: { mode: "off" | "smart" } | undefined;
268
275
  }
269
276
 
270
277
  export type DurableObjectBindings = {
@@ -448,6 +455,22 @@ interface EnvironmentNonInheritable {
448
455
  database_internal_env?: string;
449
456
  }[];
450
457
 
458
+ /**
459
+ * Specifies Constellation projects that are bound to this Worker environment.
460
+ *
461
+ * NOTE: This field is not automatically inherited from the top level environment,
462
+ * and so must be specified in every named environment.
463
+ *
464
+ * @default `[]`
465
+ * @nonInheritable
466
+ */
467
+ constellation: {
468
+ /** The binding name used to refer to the project in the worker. */
469
+ binding: string;
470
+ /** The id of the project. */
471
+ project_id: string;
472
+ }[];
473
+
451
474
  /**
452
475
  * Specifies service bindings (worker-to-worker) that are bound to this Worker environment.
453
476
  *
@@ -484,6 +507,15 @@ interface EnvironmentNonInheritable {
484
507
  dataset?: string;
485
508
  }[];
486
509
 
510
+ /**
511
+ * A browser that will be usable from the worker.
512
+ */
513
+ browser:
514
+ | {
515
+ binding: string;
516
+ }
517
+ | undefined;
518
+
487
519
  /**
488
520
  * "Unsafe" tables for features that aren't directly supported by wrangler.
489
521
  *
@@ -519,6 +551,8 @@ interface EnvironmentNonInheritable {
519
551
  /** The uuid of the uploaded mTLS certificate */
520
552
  certificate_id: string;
521
553
  }[];
554
+
555
+ tail_consumers?: TailConsumer[];
522
556
  }
523
557
 
524
558
  /**
@@ -616,3 +650,10 @@ export type ConfigModuleRuleType =
616
650
  | "CompiledWasm"
617
651
  | "Text"
618
652
  | "Data";
653
+
654
+ export type TailConsumer = {
655
+ /** The name of the service tail events will be forwarded to. */
656
+ service: string;
657
+ /** (Optional) The environt of the service. */
658
+ environment?: string;
659
+ };
@@ -100,11 +100,13 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
100
100
  send_email,
101
101
  queues,
102
102
  d1_databases,
103
+ constellation,
103
104
  r2_buckets,
104
105
  logfwdr,
105
106
  services,
106
107
  analytics_engine_datasets,
107
108
  text_blobs,
109
+ browser,
108
110
  unsafe,
109
111
  vars,
110
112
  wasm_modules,
@@ -207,6 +209,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
207
209
  });
208
210
  }
209
211
 
212
+ if (constellation !== undefined && constellation.length > 0) {
213
+ output.push({
214
+ type: "Constellation Projects",
215
+ entries: constellation.map(({ binding, project_id }) => {
216
+ return {
217
+ key: binding,
218
+ value: project_id,
219
+ };
220
+ }),
221
+ });
222
+ }
223
+
210
224
  if (r2_buckets !== undefined && r2_buckets.length > 0) {
211
225
  output.push({
212
226
  type: "R2 Buckets",
@@ -273,6 +287,13 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
273
287
  });
274
288
  }
275
289
 
290
+ if (browser !== undefined) {
291
+ output.push({
292
+ type: "Browser",
293
+ entries: [{ key: "Name", value: browser.binding }],
294
+ });
295
+ }
296
+
276
297
  if (unsafe?.bindings !== undefined && unsafe.bindings.length > 0) {
277
298
  output.push({
278
299
  type: "Unsafe",