wrangler 0.0.0-e3cab74 → 0.0.0-e3e3243

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 (66) hide show
  1. package/README.md +1 -1
  2. package/bin/wrangler.js +10 -1
  3. package/miniflare-dist/index.mjs +79 -0
  4. package/miniflare-dist/index.mjs.map +7 -0
  5. package/package.json +10 -7
  6. package/pages/functions/filepath-routing.test.ts +125 -25
  7. package/pages/functions/filepath-routing.ts +6 -3
  8. package/src/__tests__/configuration.test.ts +959 -324
  9. package/src/__tests__/dev.test.tsx +512 -84
  10. package/src/__tests__/guess-worker-format.test.ts +19 -17
  11. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  12. package/src/__tests__/helpers/mock-cfetch.ts +3 -3
  13. package/src/__tests__/helpers/mock-console.ts +17 -9
  14. package/src/__tests__/helpers/mock-kv.ts +9 -16
  15. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  16. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  17. package/src/__tests__/https-options.test.ts +127 -0
  18. package/src/__tests__/index.test.ts +128 -11
  19. package/src/__tests__/jest.setup.ts +16 -4
  20. package/src/__tests__/kv.test.ts +364 -125
  21. package/src/__tests__/logout.test.ts +15 -13
  22. package/src/__tests__/pages.test.ts +82 -0
  23. package/src/__tests__/publish.test.ts +1838 -735
  24. package/src/__tests__/r2.test.ts +4 -4
  25. package/src/__tests__/route.test.ts +45 -0
  26. package/src/__tests__/secret.test.ts +167 -68
  27. package/src/__tests__/tail.test.ts +2 -4
  28. package/src/__tests__/whoami.test.tsx +21 -7
  29. package/src/bundle.ts +27 -5
  30. package/src/cfetch/internal.ts +11 -5
  31. package/src/config/README.md +6 -6
  32. package/src/config/config.ts +54 -101
  33. package/src/config/environment.ts +67 -1
  34. package/src/config/index.ts +13 -3
  35. package/src/config/validation-helpers.ts +207 -23
  36. package/src/config/validation.ts +344 -129
  37. package/src/{api/preview.ts → create-worker-preview.ts} +39 -20
  38. package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
  39. package/src/dev/dev.tsx +392 -0
  40. package/src/dev/local.tsx +232 -0
  41. package/src/dev/remote.tsx +203 -0
  42. package/src/dev/use-esbuild.ts +104 -0
  43. package/src/entry.ts +192 -9
  44. package/src/https-options.ts +122 -0
  45. package/src/index.tsx +577 -515
  46. package/src/inspect.ts +9 -1
  47. package/src/intl-polyfill.d.ts +139 -0
  48. package/src/{kv.tsx → kv.ts} +97 -53
  49. package/src/miniflare-cli/README.md +30 -0
  50. package/src/miniflare-cli/enum-keys.ts +17 -0
  51. package/src/miniflare-cli/index.ts +37 -0
  52. package/src/module-collection.ts +90 -2
  53. package/src/open-in-browser.ts +14 -2
  54. package/src/pages.tsx +26 -10
  55. package/src/proxy.ts +104 -40
  56. package/src/publish.ts +53 -71
  57. package/src/reporting.ts +25 -9
  58. package/src/selfsigned.d.ts +29 -0
  59. package/src/sites.tsx +68 -29
  60. package/src/user.tsx +163 -85
  61. package/src/{api/worker.ts → worker.ts} +1 -24
  62. package/wrangler-dist/cli.js +5933 -3842
  63. package/wrangler-dist/cli.js.map +3 -3
  64. package/src/__tests__/helpers/mock-user.ts +0 -27
  65. package/src/dev.tsx +0 -961
  66. package/src/guess-worker-format.ts +0 -68
package/src/pages.tsx CHANGED
@@ -22,11 +22,15 @@ import type { BuilderCallback } from "yargs";
22
22
  // and also modifies some `stream/web` and `undici` prototypes, so we
23
23
  // don't want to do this if pages commands aren't being called.
24
24
 
25
+ export const pagesBetaWarning =
26
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
27
+
25
28
  const EXIT_CALLBACKS: (() => void)[] = [];
26
29
  const EXIT = (message?: string, code?: number) => {
27
30
  if (message) console.log(message);
28
31
  if (code) process.exitCode = code;
29
32
  EXIT_CALLBACKS.forEach((callback) => callback());
33
+ RUNNING_BUILDERS.forEach((builder) => builder.stop?.());
30
34
  process.exit(code);
31
35
  };
32
36
 
@@ -759,7 +763,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
759
763
  description: "Auto reload HTML pages when change is detected",
760
764
  },
761
765
  // TODO: Miniflare user options
762
- });
766
+ })
767
+ .epilogue(pagesBetaWarning);
763
768
  },
764
769
  async ({
765
770
  local,
@@ -773,6 +778,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
773
778
  "live-reload": liveReload,
774
779
  _: [_pages, _dev, ...remaining],
775
780
  }) => {
781
+ // Beta message for `wrangler pages <commands>` usage
782
+ console.log(pagesBetaWarning);
783
+
776
784
  if (!local) {
777
785
  console.error("Only local mode is supported at the moment.");
778
786
  return;
@@ -805,13 +813,15 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
805
813
 
806
814
  console.log(`Compiling worker to "${scriptPath}"...`);
807
815
 
808
- await buildFunctions({
809
- scriptPath,
810
- functionsDirectory,
811
- sourcemap: true,
812
- watch: true,
813
- onEnd: () => scriptReadyResolve(),
814
- });
816
+ try {
817
+ await buildFunctions({
818
+ scriptPath,
819
+ functionsDirectory,
820
+ sourcemap: true,
821
+ watch: true,
822
+ onEnd: () => scriptReadyResolve(),
823
+ });
824
+ } catch {}
815
825
 
816
826
  watch([functionsDirectory], {
817
827
  persistent: true,
@@ -895,7 +905,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
895
905
  // User bindings
896
906
  bindings: {
897
907
  ...Object.fromEntries(
898
- bindings.map((binding) => binding.toString().split("="))
908
+ bindings
909
+ .map((binding) => binding.toString().split("="))
910
+ .map(([key, ...values]) => [key, values.join("=")])
899
911
  ),
900
912
  },
901
913
 
@@ -1014,7 +1026,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
1014
1026
  description:
1015
1027
  "Watch for changes to the functions and automatically rebuild the Worker script",
1016
1028
  },
1017
- }),
1029
+ })
1030
+ .epilogue(pagesBetaWarning),
1018
1031
  async ({
1019
1032
  directory,
1020
1033
  "script-path": scriptPath,
@@ -1024,6 +1037,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
1024
1037
  fallbackService,
1025
1038
  watch,
1026
1039
  }) => {
1040
+ // Beta message for `wrangler pages <commands>` usage
1041
+ console.log(pagesBetaWarning);
1042
+
1027
1043
  await buildFunctions({
1028
1044
  scriptPath,
1029
1045
  outputConfigPath,
package/src/proxy.ts CHANGED
@@ -1,17 +1,21 @@
1
- import { createServer } from "node:http";
1
+ import { createServer as createHttpServer } from "node:http";
2
2
  import { connect } from "node:http2";
3
+ import { createServer as createHttpsServer } from "node:https";
3
4
  import WebSocket from "faye-websocket";
4
5
  import { useEffect, useRef, useState } from "react";
5
6
  import serveStatic from "serve-static";
6
- import type { CfPreviewToken } from "./api/preview";
7
+ import { getHttpsOptions } from "./https-options";
8
+ import { reportError } from "./reporting";
9
+ import type { CfPreviewToken } from "./create-worker-preview";
7
10
  import type {
8
11
  IncomingHttpHeaders,
9
12
  RequestListener,
10
13
  IncomingMessage,
11
14
  ServerResponse,
12
- Server,
15
+ Server as HttpServer,
13
16
  } from "node:http";
14
17
  import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
18
+ import type { Server as HttpsServer } from "node:https";
15
19
  import type ws from "ws";
16
20
 
17
21
  interface IWebsocket extends ws {
@@ -47,13 +51,17 @@ function addCfPreviewTokenHeader(
47
51
  function rewriteRemoteHostToLocalHostInHeaders(
48
52
  headers: IncomingHttpHeaders,
49
53
  remoteHost: string,
50
- localPort: number
54
+ localPort: number,
55
+ localProtocol: "https" | "http"
51
56
  ) {
52
57
  for (const [name, value] of Object.entries(headers)) {
53
58
  // Rewrite the remote host to the local host.
54
59
  if (typeof value === "string" && value.includes(remoteHost)) {
55
60
  headers[name] = value
56
- .replaceAll(`https://${remoteHost}`, `http://localhost:${localPort}`)
61
+ .replaceAll(
62
+ `https://${remoteHost}`,
63
+ `${localProtocol}://localhost:${localPort}`
64
+ )
57
65
  .replaceAll(remoteHost, `localhost:${localPort}`);
58
66
  }
59
67
  }
@@ -62,15 +70,33 @@ function rewriteRemoteHostToLocalHostInHeaders(
62
70
  export function usePreviewServer({
63
71
  previewToken,
64
72
  publicRoot,
65
- port,
73
+ localProtocol,
74
+ localPort: port,
75
+ ip,
66
76
  }: {
67
77
  previewToken: CfPreviewToken | undefined;
68
78
  publicRoot: undefined | string;
69
- port: number;
79
+ localProtocol: "https" | "http";
80
+ localPort: number;
81
+ ip: string;
70
82
  }) {
71
83
  /** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
72
- const proxyServer = useRef<Server>();
73
- const proxy = (proxyServer.current ??= createProxyServer());
84
+ const [proxyServer, setProxyServer] = useState<HttpServer | HttpsServer>();
85
+
86
+ /**
87
+ * Create the instance of the local proxy server that will pass on
88
+ * requests to the preview worker.
89
+ */
90
+ useEffect(() => {
91
+ if (proxyServer === undefined) {
92
+ createProxyServer(localProtocol)
93
+ .then((proxy) => setProxyServer(proxy))
94
+ .catch(async (err) => {
95
+ console.error("Failed to create proxy server.", err);
96
+ await reportError(err);
97
+ });
98
+ }
99
+ }, [proxyServer, localProtocol]);
74
100
 
75
101
  /**
76
102
  * When we're not connected / getting a fresh token on changes,
@@ -98,6 +124,10 @@ export function usePreviewServer({
98
124
  }
99
125
 
100
126
  useEffect(() => {
127
+ if (proxyServer === undefined) {
128
+ return;
129
+ }
130
+
101
131
  // If we don't have a token, that means either we're just starting up,
102
132
  // or we're refreshing the token.
103
133
  if (!previewToken) {
@@ -109,8 +139,8 @@ export function usePreviewServer({
109
139
  // store the stream in a buffer so we can replay it later
110
140
  streamBufferRef.current.push({ stream, headers });
111
141
  };
112
- proxy.on("stream", bufferStream);
113
- cleanupListeners.push(() => proxy.off("stream", bufferStream));
142
+ proxyServer.on("stream", bufferStream);
143
+ cleanupListeners.push(() => proxyServer.off("stream", bufferStream));
114
144
 
115
145
  const bufferRequestResponse = (
116
146
  request: IncomingMessage,
@@ -120,8 +150,10 @@ export function usePreviewServer({
120
150
  requestResponseBufferRef.current.push({ request, response });
121
151
  };
122
152
 
123
- proxy.on("request", bufferRequestResponse);
124
- cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
153
+ proxyServer.on("request", bufferRequestResponse);
154
+ cleanupListeners.push(() =>
155
+ proxyServer.off("request", bufferRequestResponse)
156
+ );
125
157
  return () => {
126
158
  cleanupListeners.forEach((cleanup) => cleanup());
127
159
  };
@@ -142,9 +174,14 @@ export function usePreviewServer({
142
174
  cleanupListeners.push(() => remote.off("close", retryServerSetup));
143
175
 
144
176
  /** HTTP/2 -> HTTP/2 */
145
- const handleStream = createStreamHandler(previewToken, remote, port);
146
- proxy.on("stream", handleStream);
147
- cleanupListeners.push(() => proxy.off("stream", handleStream));
177
+ const handleStream = createStreamHandler(
178
+ previewToken,
179
+ remote,
180
+ port,
181
+ localProtocol
182
+ );
183
+ proxyServer.on("stream", handleStream);
184
+ cleanupListeners.push(() => proxyServer.off("stream", handleStream));
148
185
 
149
186
  // flush and replay buffered streams
150
187
  streamBufferRef.current.forEach((buffer) =>
@@ -177,7 +214,8 @@ export function usePreviewServer({
177
214
  rewriteRemoteHostToLocalHostInHeaders(
178
215
  responseHeaders,
179
216
  previewToken.host,
180
- port
217
+ port,
218
+ localProtocol
181
219
  );
182
220
  for (const name of Object.keys(responseHeaders)) {
183
221
  if (name.startsWith(":")) {
@@ -195,8 +233,10 @@ export function usePreviewServer({
195
233
  ? createHandleAssetsRequest(assetPath, handleRequest)
196
234
  : handleRequest;
197
235
 
198
- proxy.on("request", actualHandleRequest);
199
- cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
236
+ proxyServer.on("request", actualHandleRequest);
237
+ cleanupListeners.push(() =>
238
+ proxyServer.off("request", actualHandleRequest)
239
+ );
200
240
 
201
241
  // flush and replay buffered requests
202
242
  requestResponseBufferRef.current.forEach(({ request, response }) =>
@@ -227,8 +267,8 @@ export function usePreviewServer({
227
267
  remoteWebsocketClient.close();
228
268
  });
229
269
  };
230
- proxy.on("upgrade", handleUpgrade);
231
- cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
270
+ proxyServer.on("upgrade", handleUpgrade);
271
+ cleanupListeners.push(() => proxyServer.off("upgrade", handleUpgrade));
232
272
 
233
273
  return () => {
234
274
  cleanupListeners.forEach((cleanup) => cleanup());
@@ -237,7 +277,8 @@ export function usePreviewServer({
237
277
  previewToken,
238
278
  publicRoot,
239
279
  port,
240
- proxy,
280
+ localProtocol,
281
+ proxyServer,
241
282
  // We use a state value as a sigil to trigger reconnecting the server.
242
283
  // It's not used inside the effect, so react-hooks/exhaustive-deps
243
284
  // doesn't complain if it's not included in the dependency array.
@@ -248,19 +289,23 @@ export function usePreviewServer({
248
289
  // Start/stop the server whenever the
249
290
  // containing component is mounted/unmounted.
250
291
  useEffect(() => {
292
+ if (proxyServer === undefined) {
293
+ return;
294
+ }
295
+
251
296
  waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
252
297
  .then(() => {
253
- proxy.listen(port);
254
- console.log(`⬣ Listening at http://localhost:${port}`);
298
+ proxyServer.listen(port, ip);
299
+ console.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
255
300
  })
256
301
  .catch((err) => {
257
302
  console.error(`⬣ Failed to start server: ${err}`);
258
303
  });
259
304
 
260
305
  return () => {
261
- proxy.close();
306
+ proxyServer.close();
262
307
  };
263
- }, [port, proxy]);
308
+ }, [port, ip, proxyServer, localProtocol]);
264
309
  }
265
310
 
266
311
  function createHandleAssetsRequest(
@@ -288,8 +333,15 @@ const HTTP1_HEADERS = new Set([
288
333
  "http2-settings",
289
334
  ]);
290
335
 
291
- function createProxyServer() {
292
- return createServer()
336
+ async function createProxyServer(
337
+ localProtocol: "https" | "http"
338
+ ): Promise<HttpServer | HttpsServer> {
339
+ const server: HttpServer | HttpsServer =
340
+ localProtocol === "https"
341
+ ? createHttpsServer(await getHttpsOptions())
342
+ : createHttpServer();
343
+
344
+ return server
293
345
  .on("request", function (req, res) {
294
346
  // log all requests
295
347
  console.log(
@@ -318,7 +370,8 @@ function createProxyServer() {
318
370
  function createStreamHandler(
319
371
  previewToken: CfPreviewToken,
320
372
  remote: ClientHttp2Session,
321
- port: number
373
+ localPort: number,
374
+ localProtocol: "https" | "http"
322
375
  ) {
323
376
  return function handleStream(
324
377
  stream: ServerHttp2Stream,
@@ -331,7 +384,8 @@ function createStreamHandler(
331
384
  rewriteRemoteHostToLocalHostInHeaders(
332
385
  responseHeaders,
333
386
  previewToken.host,
334
- port
387
+ localPort,
388
+ localProtocol
335
389
  );
336
390
  stream.respond(responseHeaders);
337
391
  request.pipe(stream, { end: true });
@@ -348,29 +402,39 @@ export async function waitForPortToBeAvailable(
348
402
  ): Promise<void> {
349
403
  return new Promise((resolve, reject) => {
350
404
  const timeout = setTimeout(() => {
351
- reject(new Error(`Timed out waiting for port ${port}`));
405
+ doReject(new Error(`Timed out waiting for port ${port}`));
352
406
  }, options.timeout);
353
407
 
408
+ const interval = setInterval(checkPort, options.retryPeriod);
409
+ checkPort();
410
+
411
+ function doResolve() {
412
+ clearTimeout(timeout);
413
+ clearInterval(interval);
414
+ resolve();
415
+ }
416
+
417
+ function doReject(err: unknown) {
418
+ clearInterval(interval);
419
+ clearTimeout(timeout);
420
+ reject(err);
421
+ }
422
+
354
423
  function checkPort() {
355
424
  // Testing whether a port is 'available' involves simply
356
425
  // trying to make a server listen on that port, and retrying
357
426
  // until it succeeds.
358
- const server = createServer();
427
+ const server = createHttpServer();
359
428
  server.on("error", (err) => {
360
429
  // @ts-expect-error non standard property on Error
361
- if (err.code === "EADDRINUSE") {
362
- setTimeout(checkPort, options.retryPeriod);
363
- } else {
364
- reject(err);
430
+ if (err.code !== "EADDRINUSE") {
431
+ doReject(err);
365
432
  }
366
433
  });
367
434
  server.listen(port, () => {
368
435
  server.close();
369
- clearTimeout(timeout);
370
- resolve();
436
+ doResolve();
371
437
  });
372
438
  }
373
-
374
- checkPort();
375
439
  });
376
440
  }
package/src/publish.ts CHANGED
@@ -2,22 +2,20 @@ import assert from "node:assert";
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { URLSearchParams } from "node:url";
5
- import { execaCommand } from "execa";
6
5
  import tmp from "tmp-promise";
7
- import { toFormData } from "./api/form_data";
8
6
  import { bundleWorker } from "./bundle";
9
7
  import { fetchResult } from "./cfetch";
10
- import { fileExists } from "./entry";
11
- import guessWorkerFormat from "./guess-worker-format";
8
+ import { createWorkerUploadForm } from "./create-worker-upload-form";
12
9
  import { syncAssets } from "./sites";
13
- import type { CfScriptFormat, CfWorkerInit } from "./api/worker";
14
10
  import type { Config } from "./config";
11
+ import type { Entry } from "./entry";
15
12
  import type { AssetPaths } from "./sites";
13
+ import type { CfWorkerInit } from "./worker";
16
14
 
17
15
  type Props = {
18
16
  config: Config;
19
- format: CfScriptFormat | undefined;
20
- entry: { file: string; directory: string };
17
+ accountId: string;
18
+ entry: Entry;
21
19
  rules: Config["rules"];
22
20
  name: string | undefined;
23
21
  env: string | undefined;
@@ -29,6 +27,7 @@ type Props = {
29
27
  legacyEnv: boolean | undefined;
30
28
  jsxFactory: undefined | string;
31
29
  jsxFragment: undefined | string;
30
+ tsconfig: undefined | string;
32
31
  experimentalPublic: boolean;
33
32
  };
34
33
 
@@ -38,33 +37,22 @@ function sleep(ms: number) {
38
37
 
39
38
  export default async function publish(props: Props): Promise<void> {
40
39
  // TODO: warn if git/hg has uncommitted changes
41
- const { config } = props;
42
-
43
- // TODO: should we automatically fallback to top level config if there is no matching environment??
44
- const envRootObj = (props.env && config.env[props.env]) || config;
40
+ const { config, accountId } = props;
45
41
 
46
42
  assert(
47
- envRootObj.compatibility_date || props.compatibilityDate,
43
+ props.compatibilityDate || config.compatibility_date,
48
44
  "A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
49
45
  );
50
46
 
51
- const triggers = props.triggers || envRootObj.triggers?.crons;
47
+ const triggers = props.triggers || config.triggers?.crons;
52
48
  const routes =
53
- props.routes ??
54
- envRootObj.routes ??
55
- (envRootObj.route ? [envRootObj.route] : []) ??
56
- [];
57
-
58
- const { account_id: accountId, workers_dev: deployToWorkersDev } = config;
59
-
60
- if (accountId === undefined) {
61
- throw new Error("No account_id provided.");
62
- }
49
+ props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
63
50
 
64
- const jsxFactory = props.jsxFactory || envRootObj.jsx_factory;
65
- const jsxFragment = props.jsxFragment || envRootObj.jsx_fragment;
51
+ // deployToWorkersDev defaults to true only if there aren't any routes defined
52
+ const deployToWorkersDev = config.workers_dev ?? routes.length === 0;
66
53
 
67
- assert(config.account_id, "missing account id");
54
+ const jsxFactory = props.jsxFactory || config.jsx_factory;
55
+ const jsxFragment = props.jsxFragment || config.jsx_fragment;
68
56
 
69
57
  const scriptName = props.name;
70
58
  assert(
@@ -72,14 +60,6 @@ export default async function publish(props: Props): Promise<void> {
72
60
  'You need to provide a name when publishing a worker. Either pass it as a cli arg with `--name <name>` or in your config file as `name = "<name>"`'
73
61
  );
74
62
 
75
- if (config.site?.["entry-point"]) {
76
- console.warn(
77
- "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
78
- "The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `main` config field.\n" +
79
- "Please remove the `site.entry-point` field from the `wrangler.toml` file."
80
- );
81
- }
82
-
83
63
  assert(
84
64
  !config.site || config.site.bucket,
85
65
  "A [site] definition requires a `bucket` field with a path to the site's public directory."
@@ -89,28 +69,7 @@ export default async function publish(props: Props): Promise<void> {
89
69
  try {
90
70
  const envName = props.env ?? "production";
91
71
 
92
- if (config.build.command) {
93
- // TODO: add a deprecation message here?
94
- console.log("running:", config.build.command);
95
- await execaCommand(config.build.command, {
96
- shell: true,
97
- stdout: "inherit",
98
- stderr: "inherit",
99
- timeout: 1000 * 30,
100
- ...(config.build.cwd && { cwd: config.build.cwd }),
101
- });
102
-
103
- if (fileExists(props.entry.file) === false) {
104
- throw new Error(
105
- `Could not resolve "${path.relative(
106
- process.cwd(),
107
- props.entry.file
108
- )}".`
109
- );
110
- }
111
- }
112
-
113
- const format = await guessWorkerFormat(props.entry, props.format);
72
+ const { format } = props.entry;
114
73
 
115
74
  if (props.experimentalPublic && format === "service-worker") {
116
75
  // TODO: check config too
@@ -138,8 +97,8 @@ export default async function publish(props: Props): Promise<void> {
138
97
  serveAssetsFromWorker: props.experimentalPublic,
139
98
  jsxFactory,
140
99
  jsxFragment,
141
- format,
142
100
  rules: props.rules,
101
+ tsconfig: props.tsconfig ?? config.tsconfig,
143
102
  }
144
103
  );
145
104
 
@@ -198,12 +157,12 @@ export default async function publish(props: Props): Promise<void> {
198
157
  );
199
158
 
200
159
  const bindings: CfWorkerInit["bindings"] = {
201
- kv_namespaces: (envRootObj.kv_namespaces || []).concat(
160
+ kv_namespaces: (config.kv_namespaces || []).concat(
202
161
  assets.namespace
203
162
  ? { binding: "__STATIC_CONTENT", id: assets.namespace }
204
163
  : []
205
164
  ),
206
- vars: envRootObj.vars,
165
+ vars: config.vars,
207
166
  wasm_modules: config.wasm_modules,
208
167
  text_blobs: {
209
168
  ...config.text_blobs,
@@ -212,9 +171,9 @@ export default async function publish(props: Props): Promise<void> {
212
171
  __STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
213
172
  }),
214
173
  },
215
- durable_objects: envRootObj.durable_objects,
216
- r2_buckets: envRootObj.r2_buckets,
217
- unsafe: envRootObj.unsafe?.bindings,
174
+ durable_objects: config.durable_objects,
175
+ r2_buckets: config.r2_buckets,
176
+ unsafe: config.unsafe?.bindings,
218
177
  };
219
178
 
220
179
  if (assets.manifest) {
@@ -235,8 +194,9 @@ export default async function publish(props: Props): Promise<void> {
235
194
  bindings,
236
195
  migrations,
237
196
  modules,
238
- compatibility_date: config.compatibility_date,
239
- compatibility_flags: config.compatibility_flags,
197
+ compatibility_date: props.compatibilityDate ?? config.compatibility_date,
198
+ compatibility_flags:
199
+ props.compatibilityFlags ?? config.compatibility_flags,
240
200
  usage_model: config.usage_model,
241
201
  };
242
202
 
@@ -252,22 +212,17 @@ export default async function publish(props: Props): Promise<void> {
252
212
  workerUrl,
253
213
  {
254
214
  method: "PUT",
255
- body: toFormData(worker),
215
+ body: createWorkerUploadForm(worker),
256
216
  },
257
217
  new URLSearchParams({ available_on_subdomain: "true" })
258
218
  );
259
219
 
260
220
  const uploadMs = Date.now() - start;
261
- console.log("Uploaded", workerName, formatTime(uploadMs));
262
221
  const deployments: Promise<string[]>[] = [];
263
222
 
264
223
  if (deployToWorkersDev) {
265
224
  // Deploy to a subdomain of `workers.dev`
266
- const userSubdomain = (
267
- await fetchResult<{ subdomain: string }>(
268
- `/accounts/${accountId}/workers/subdomain`
269
- )
270
- ).subdomain;
225
+ const userSubdomain = await getSubdomain(accountId);
271
226
  const scriptURL =
272
227
  props.legacyEnv || !props.env
273
228
  ? `${scriptName}.${userSubdomain}.workers.dev`
@@ -306,6 +261,8 @@ export default async function publish(props: Props): Promise<void> {
306
261
  });
307
262
  }
308
263
 
264
+ console.log("Uploaded", workerName, formatTime(uploadMs));
265
+
309
266
  // Update routing table for the script.
310
267
  if (routes.length > 0) {
311
268
  deployments.push(
@@ -366,3 +323,28 @@ export default async function publish(props: Props): Promise<void> {
366
323
  function formatTime(duration: number) {
367
324
  return `(${(duration / 1000).toFixed(2)} sec)`;
368
325
  }
326
+
327
+ async function getSubdomain(accountId: string): Promise<string> {
328
+ try {
329
+ const { subdomain } = await fetchResult(
330
+ `/accounts/${accountId}/workers/subdomain`
331
+ );
332
+ return subdomain;
333
+ } catch (e) {
334
+ const error = e as { code?: number };
335
+ if (typeof error === "object" && !!error && error.code === 10007) {
336
+ // 10007 error code: not found
337
+ // https://api.cloudflare.com/#worker-subdomain-get-subdomain
338
+
339
+ const errorMessage =
340
+ "Error: You need to register a workers.dev subdomain before publishing to workers.dev";
341
+ const solutionMessage =
342
+ "You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
343
+ const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
344
+
345
+ throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
346
+ } else {
347
+ throw e;
348
+ }
349
+ }
350
+ }
package/src/reporting.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { appendFile, readFile } from "fs/promises";
2
1
  import * as fs from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
- import path from "path/posix";
4
+ import path from "node:path";
5
5
  import TOML from "@iarna/toml";
6
6
  import { RewriteFrames } from "@sentry/integrations";
7
7
  import {
@@ -22,6 +22,14 @@ export function initReporting() {
22
22
  initialScope: {
23
23
  tags: { [pkj.name]: pkj.version },
24
24
  },
25
+ beforeBreadcrumb: (_breadcrumb, _hint) => {
26
+ // eventually, we might want to actually start dropping our own breadcrumbs.
27
+ // until then, we want to just ignore all of them, since sentry will
28
+ // automatically include breadcrumbs for things like console output,
29
+ // which can include unsanitized data that the user logs
30
+ // from their worker during development
31
+ return null;
32
+ },
25
33
  dsn: "https://5089b76bf8a64a9c949bf5c2b5e8003c@o51786.ingest.sentry.io/6190959",
26
34
  tracesSampleRate: 1.0,
27
35
  integrations: [
@@ -43,12 +51,20 @@ export function initReporting() {
43
51
 
44
52
  async function appendReportingDecision(userInput: "true" | "false") {
45
53
  const homePath = path.join(os.homedir(), ".wrangler/config/");
46
- fs.mkdirSync(homePath, { recursive: true });
47
- await appendFile(
48
- path.join(homePath, "reporting.toml"),
49
- `error_tracking_opt = ${userInput} # Sentry \nerror_tracking_opt_date = ${new Date().toISOString()} # Sentry Date Decision \n`,
50
- { encoding: "utf-8" }
51
- );
54
+ const reportingConfigLocation = path.join(homePath, "reporting.toml");
55
+ try {
56
+ fs.mkdirSync(homePath, { recursive: true });
57
+ await writeFile(
58
+ reportingConfigLocation,
59
+ `error_tracking_opt = ${userInput} # Sentry \nerror_tracking_opt_date = ${new Date().toISOString()} # Sentry Date Decision \n`,
60
+ "utf-8"
61
+ );
62
+ } catch (err) {
63
+ console.warn(
64
+ `Unable to create wrangler's error reporting configuration file at ${reportingConfigLocation}: `,
65
+ err
66
+ );
67
+ }
52
68
  }
53
69
 
54
70
  function exceptionTransaction(error: Error, origin = "") {
@@ -72,7 +88,7 @@ export async function reportError(err: Error, origin = "") {
72
88
  message: "Would you like to submit a report when an error occurs?",
73
89
  choices: [
74
90
  { title: "Always", value: true },
75
- { title: "Yes", value: "once" },
91
+ { title: "Only this time", value: "once" },
76
92
  { title: "No", value: false },
77
93
  ],
78
94
  initial: 2,