wrangler 0.0.0-e043479 → 0.0.0-e0b9366

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 (51) hide show
  1. package/package.json +10 -4
  2. package/pages/functions/filepath-routing.test.ts +125 -25
  3. package/pages/functions/filepath-routing.ts +6 -3
  4. package/src/__tests__/configuration.test.ts +479 -114
  5. package/src/__tests__/dev.test.tsx +162 -8
  6. package/src/__tests__/dev2.test.tsx +152 -0
  7. package/src/__tests__/guess-worker-format.test.ts +9 -17
  8. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  9. package/src/__tests__/helpers/mock-cfetch.ts +3 -3
  10. package/src/__tests__/helpers/mock-kv.ts +9 -16
  11. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  12. package/src/__tests__/https-options.test.ts +127 -0
  13. package/src/__tests__/index.test.ts +125 -0
  14. package/src/__tests__/jest.setup.ts +1 -1
  15. package/src/__tests__/kv.test.ts +27 -53
  16. package/src/__tests__/logout.test.ts +15 -13
  17. package/src/__tests__/pages.test.ts +44 -0
  18. package/src/__tests__/publish.test.ts +504 -32
  19. package/src/__tests__/route.test.ts +45 -0
  20. package/src/__tests__/whoami.test.tsx +21 -7
  21. package/src/bundle.ts +20 -11
  22. package/src/cfetch/internal.ts +11 -5
  23. package/src/config/config.ts +53 -44
  24. package/src/config/validation-helpers.ts +110 -12
  25. package/src/config/validation.ts +59 -41
  26. package/src/{api/preview.ts → create-worker-preview.ts} +23 -4
  27. package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
  28. package/src/dev/dev.tsx +357 -0
  29. package/src/dev/local.tsx +241 -0
  30. package/src/dev/remote.tsx +201 -0
  31. package/src/dev/use-esbuild.ts +100 -0
  32. package/src/entry.ts +111 -9
  33. package/src/https-options.ts +122 -0
  34. package/src/index.tsx +171 -109
  35. package/src/inspect.ts +9 -1
  36. package/src/{kv.tsx → kv.ts} +0 -0
  37. package/src/module-collection.ts +85 -2
  38. package/src/open-in-browser.ts +14 -2
  39. package/src/pages.tsx +1 -1
  40. package/src/proxy.ts +102 -40
  41. package/src/publish.ts +41 -50
  42. package/src/reporting.ts +25 -9
  43. package/src/selfsigned.d.ts +29 -0
  44. package/src/sites.tsx +51 -16
  45. package/src/user.tsx +132 -77
  46. package/src/{api/worker.ts → worker.ts} +0 -23
  47. package/wrangler-dist/cli.js +5012 -3107
  48. package/wrangler-dist/cli.js.map +3 -3
  49. package/src/__tests__/helpers/mock-user.ts +0 -27
  50. package/src/dev.tsx +0 -960
  51. package/src/guess-worker-format.ts +0 -68
@@ -2,8 +2,8 @@ import crypto from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import globToRegExp from "glob-to-regexp";
5
- import type { CfModule, CfModuleType, CfScriptFormat } from "./api/worker";
6
5
  import type { Config, ConfigModuleRuleType } from "./config";
6
+ import type { CfModule, CfModuleType, CfScriptFormat } from "./worker";
7
7
  import type esbuild from "esbuild";
8
8
 
9
9
  const RuleTypeToModuleType: Record<ConfigModuleRuleType, CfModuleType> = {
@@ -28,9 +28,15 @@ export const DEFAULT_MODULE_RULES: Config["rules"] = [
28
28
  { type: "CompiledWasm", globs: ["**/*.wasm"] },
29
29
  ];
30
30
 
31
- export default function makeModuleCollector(props: {
31
+ export default function createModuleCollector(props: {
32
32
  format: CfScriptFormat;
33
33
  rules?: Config["rules"];
34
+ // a collection of "legacy" style module references, which are just file names
35
+ // we will eventually deprecate this functionality, hence the verbose greppable name
36
+ wrangler1xlegacyModuleReferences: {
37
+ rootDirectory: string;
38
+ fileNames: Set<string>;
39
+ };
34
40
  }): {
35
41
  modules: CfModule[];
36
42
  plugin: esbuild.Plugin;
@@ -41,6 +47,10 @@ export default function makeModuleCollector(props: {
41
47
  ...DEFAULT_MODULE_RULES!,
42
48
  ];
43
49
 
50
+ // First, we want to add some validations to the module rules
51
+ // We want to warn if rules are accidentally configured in such a way that
52
+ // subsequent rules will never match because `fallthrough` hasn't been set
53
+
44
54
  const completedRuleLocations: Record<string, number> = {};
45
55
  let index = 0;
46
56
  const rulesToRemove: Config["rules"] = [];
@@ -92,6 +102,79 @@ export default function makeModuleCollector(props: {
92
102
  modules.splice(0);
93
103
  });
94
104
 
105
+ // ~ start legacy module specifier support ~
106
+
107
+ // This section detects usage of "legacy" 1.x style module specifiers
108
+ // and modifies them so they "work" in wrangler v2, but with a warning
109
+
110
+ const rulesMatchers = rules.flatMap((rule) => {
111
+ return rule.globs.map((glob) => {
112
+ const regex = globToRegExp(glob);
113
+ return {
114
+ regex,
115
+ rule,
116
+ };
117
+ });
118
+ });
119
+
120
+ build.onResolve(
121
+ {
122
+ filter: new RegExp(
123
+ [...props.wrangler1xlegacyModuleReferences.fileNames]
124
+ .map((fileName) => `^${fileName}$`)
125
+ .join("|")
126
+ ),
127
+ },
128
+ async (args: esbuild.OnResolveArgs) => {
129
+ if (
130
+ args.kind !== "import-statement" &&
131
+ args.kind !== "require-call"
132
+ ) {
133
+ return;
134
+ }
135
+ // In the future, this will simply throw an error
136
+ console.warn(
137
+ `Deprecation warning: detected a legacy module import in "./${path.relative(
138
+ process.cwd(),
139
+ args.importer
140
+ )}". This will stop working in the future. Replace references to "${
141
+ args.path
142
+ }" with "./${args.path}";`
143
+ );
144
+
145
+ // take the file and massage it to a
146
+ // transportable/manageable format
147
+ const filePath = path.join(
148
+ props.wrangler1xlegacyModuleReferences.rootDirectory,
149
+ args.path
150
+ );
151
+ const fileContent = await readFile(filePath);
152
+ const fileHash = crypto
153
+ .createHash("sha1")
154
+ .update(fileContent)
155
+ .digest("hex");
156
+ const fileName = `./${fileHash}-${path.basename(args.path)}`;
157
+
158
+ const { rule } =
159
+ rulesMatchers.find(({ regex }) => regex.test(fileName)) || {};
160
+ if (rule) {
161
+ // add the module to the array
162
+ modules.push({
163
+ name: fileName,
164
+ content: fileContent,
165
+ type: RuleTypeToModuleType[rule.type],
166
+ });
167
+ return {
168
+ path: fileName, // change the reference to the changed module
169
+ external: props.format === "modules", // mark it as external in the bundle
170
+ namespace: `wrangler-module-${rule.type}`, // just a tag, this isn't strictly necessary
171
+ watchFiles: [filePath], // we also add the file to esbuild's watch list
172
+ };
173
+ }
174
+ }
175
+ );
176
+ // ~ end legacy module specifier support ~
177
+
95
178
  rules?.forEach((rule) => {
96
179
  if (rule.type === "ESModule" || rule.type === "CommonJS") return; // TODO: we should treat these as js files, and use the jsx loader
97
180
 
@@ -1,12 +1,24 @@
1
1
  import open from "open";
2
+
2
3
  /**
3
4
  * An extremely simple wrapper around the open command.
4
5
  * Specifically, it adds an 'error' event handler so that when this function
5
6
  * is called in environments where we can't open the browser (e.g. github codespaces,
6
7
  * stackblitz, remote servers), it doesn't just crash the process.
8
+ *
9
+ * @param url the URL to point the browser at
10
+ * @param options open a chromium-based browser instead of the default
7
11
  */
8
- export default async function openInBrowser(url: string): Promise<void> {
9
- const childProcess = await open(url);
12
+ export default async function openInBrowser(
13
+ url: string,
14
+ { forceChromium }: { forceChromium: boolean } = { forceChromium: false }
15
+ ): Promise<void> {
16
+ const options: open.Options | undefined = forceChromium
17
+ ? {
18
+ app: [{ name: open.apps.chrome }, { name: open.apps.edge }],
19
+ }
20
+ : undefined;
21
+ const childProcess = await open(url, options);
10
22
  childProcess.on("error", () => {
11
23
  console.warn(`Failed to open ${url} in a browser`);
12
24
  });
package/src/pages.tsx CHANGED
@@ -895,7 +895,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
895
895
  // User bindings
896
896
  bindings: {
897
897
  ...Object.fromEntries(
898
- bindings.map((binding) => binding.toString().split("="))
898
+ bindings.map((binding) => binding.toString().split("=", 1))
899
899
  ),
900
900
  },
901
901
 
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,31 @@ function rewriteRemoteHostToLocalHostInHeaders(
62
70
  export function usePreviewServer({
63
71
  previewToken,
64
72
  publicRoot,
65
- port,
73
+ localProtocol,
74
+ localPort: port,
66
75
  }: {
67
76
  previewToken: CfPreviewToken | undefined;
68
77
  publicRoot: undefined | string;
69
- port: number;
78
+ localProtocol: "https" | "http";
79
+ localPort: number;
70
80
  }) {
71
81
  /** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
72
- const proxyServer = useRef<Server>();
73
- const proxy = (proxyServer.current ??= createProxyServer());
82
+ const [proxyServer, setProxyServer] = useState<HttpServer | HttpsServer>();
83
+
84
+ /**
85
+ * Create the instance of the local proxy server that will pass on
86
+ * requests to the preview worker.
87
+ */
88
+ useEffect(() => {
89
+ if (proxyServer === undefined) {
90
+ createProxyServer(localProtocol)
91
+ .then((proxy) => setProxyServer(proxy))
92
+ .catch(async (err) => {
93
+ console.error("Failed to create proxy server.", err);
94
+ await reportError(err);
95
+ });
96
+ }
97
+ }, [proxyServer, localProtocol]);
74
98
 
75
99
  /**
76
100
  * When we're not connected / getting a fresh token on changes,
@@ -98,6 +122,10 @@ export function usePreviewServer({
98
122
  }
99
123
 
100
124
  useEffect(() => {
125
+ if (proxyServer === undefined) {
126
+ return;
127
+ }
128
+
101
129
  // If we don't have a token, that means either we're just starting up,
102
130
  // or we're refreshing the token.
103
131
  if (!previewToken) {
@@ -109,8 +137,8 @@ export function usePreviewServer({
109
137
  // store the stream in a buffer so we can replay it later
110
138
  streamBufferRef.current.push({ stream, headers });
111
139
  };
112
- proxy.on("stream", bufferStream);
113
- cleanupListeners.push(() => proxy.off("stream", bufferStream));
140
+ proxyServer.on("stream", bufferStream);
141
+ cleanupListeners.push(() => proxyServer.off("stream", bufferStream));
114
142
 
115
143
  const bufferRequestResponse = (
116
144
  request: IncomingMessage,
@@ -120,8 +148,10 @@ export function usePreviewServer({
120
148
  requestResponseBufferRef.current.push({ request, response });
121
149
  };
122
150
 
123
- proxy.on("request", bufferRequestResponse);
124
- cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
151
+ proxyServer.on("request", bufferRequestResponse);
152
+ cleanupListeners.push(() =>
153
+ proxyServer.off("request", bufferRequestResponse)
154
+ );
125
155
  return () => {
126
156
  cleanupListeners.forEach((cleanup) => cleanup());
127
157
  };
@@ -142,9 +172,14 @@ export function usePreviewServer({
142
172
  cleanupListeners.push(() => remote.off("close", retryServerSetup));
143
173
 
144
174
  /** HTTP/2 -> HTTP/2 */
145
- const handleStream = createStreamHandler(previewToken, remote, port);
146
- proxy.on("stream", handleStream);
147
- cleanupListeners.push(() => proxy.off("stream", handleStream));
175
+ const handleStream = createStreamHandler(
176
+ previewToken,
177
+ remote,
178
+ port,
179
+ localProtocol
180
+ );
181
+ proxyServer.on("stream", handleStream);
182
+ cleanupListeners.push(() => proxyServer.off("stream", handleStream));
148
183
 
149
184
  // flush and replay buffered streams
150
185
  streamBufferRef.current.forEach((buffer) =>
@@ -177,7 +212,8 @@ export function usePreviewServer({
177
212
  rewriteRemoteHostToLocalHostInHeaders(
178
213
  responseHeaders,
179
214
  previewToken.host,
180
- port
215
+ port,
216
+ localProtocol
181
217
  );
182
218
  for (const name of Object.keys(responseHeaders)) {
183
219
  if (name.startsWith(":")) {
@@ -195,8 +231,10 @@ export function usePreviewServer({
195
231
  ? createHandleAssetsRequest(assetPath, handleRequest)
196
232
  : handleRequest;
197
233
 
198
- proxy.on("request", actualHandleRequest);
199
- cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
234
+ proxyServer.on("request", actualHandleRequest);
235
+ cleanupListeners.push(() =>
236
+ proxyServer.off("request", actualHandleRequest)
237
+ );
200
238
 
201
239
  // flush and replay buffered requests
202
240
  requestResponseBufferRef.current.forEach(({ request, response }) =>
@@ -227,8 +265,8 @@ export function usePreviewServer({
227
265
  remoteWebsocketClient.close();
228
266
  });
229
267
  };
230
- proxy.on("upgrade", handleUpgrade);
231
- cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
268
+ proxyServer.on("upgrade", handleUpgrade);
269
+ cleanupListeners.push(() => proxyServer.off("upgrade", handleUpgrade));
232
270
 
233
271
  return () => {
234
272
  cleanupListeners.forEach((cleanup) => cleanup());
@@ -237,7 +275,8 @@ export function usePreviewServer({
237
275
  previewToken,
238
276
  publicRoot,
239
277
  port,
240
- proxy,
278
+ localProtocol,
279
+ proxyServer,
241
280
  // We use a state value as a sigil to trigger reconnecting the server.
242
281
  // It's not used inside the effect, so react-hooks/exhaustive-deps
243
282
  // doesn't complain if it's not included in the dependency array.
@@ -248,19 +287,23 @@ export function usePreviewServer({
248
287
  // Start/stop the server whenever the
249
288
  // containing component is mounted/unmounted.
250
289
  useEffect(() => {
290
+ if (proxyServer === undefined) {
291
+ return;
292
+ }
293
+
251
294
  waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
252
295
  .then(() => {
253
- proxy.listen(port);
254
- console.log(`⬣ Listening at http://localhost:${port}`);
296
+ proxyServer.listen(port);
297
+ console.log(`⬣ Listening at ${localProtocol}://localhost:${port}`);
255
298
  })
256
299
  .catch((err) => {
257
300
  console.error(`⬣ Failed to start server: ${err}`);
258
301
  });
259
302
 
260
303
  return () => {
261
- proxy.close();
304
+ proxyServer.close();
262
305
  };
263
- }, [port, proxy]);
306
+ }, [port, proxyServer, localProtocol]);
264
307
  }
265
308
 
266
309
  function createHandleAssetsRequest(
@@ -288,8 +331,15 @@ const HTTP1_HEADERS = new Set([
288
331
  "http2-settings",
289
332
  ]);
290
333
 
291
- function createProxyServer() {
292
- return createServer()
334
+ async function createProxyServer(
335
+ localProtocol: "https" | "http"
336
+ ): Promise<HttpServer | HttpsServer> {
337
+ const server: HttpServer | HttpsServer =
338
+ localProtocol === "https"
339
+ ? createHttpsServer(await getHttpsOptions())
340
+ : createHttpServer();
341
+
342
+ return server
293
343
  .on("request", function (req, res) {
294
344
  // log all requests
295
345
  console.log(
@@ -318,7 +368,8 @@ function createProxyServer() {
318
368
  function createStreamHandler(
319
369
  previewToken: CfPreviewToken,
320
370
  remote: ClientHttp2Session,
321
- port: number
371
+ localPort: number,
372
+ localProtocol: "https" | "http"
322
373
  ) {
323
374
  return function handleStream(
324
375
  stream: ServerHttp2Stream,
@@ -331,7 +382,8 @@ function createStreamHandler(
331
382
  rewriteRemoteHostToLocalHostInHeaders(
332
383
  responseHeaders,
333
384
  previewToken.host,
334
- port
385
+ localPort,
386
+ localProtocol
335
387
  );
336
388
  stream.respond(responseHeaders);
337
389
  request.pipe(stream, { end: true });
@@ -348,29 +400,39 @@ export async function waitForPortToBeAvailable(
348
400
  ): Promise<void> {
349
401
  return new Promise((resolve, reject) => {
350
402
  const timeout = setTimeout(() => {
351
- reject(new Error(`Timed out waiting for port ${port}`));
403
+ doReject(new Error(`Timed out waiting for port ${port}`));
352
404
  }, options.timeout);
353
405
 
406
+ const interval = setInterval(checkPort, options.retryPeriod);
407
+ checkPort();
408
+
409
+ function doResolve() {
410
+ clearTimeout(timeout);
411
+ clearInterval(interval);
412
+ resolve();
413
+ }
414
+
415
+ function doReject(err: unknown) {
416
+ clearInterval(interval);
417
+ clearTimeout(timeout);
418
+ reject(err);
419
+ }
420
+
354
421
  function checkPort() {
355
422
  // Testing whether a port is 'available' involves simply
356
423
  // trying to make a server listen on that port, and retrying
357
424
  // until it succeeds.
358
- const server = createServer();
425
+ const server = createHttpServer();
359
426
  server.on("error", (err) => {
360
427
  // @ts-expect-error non standard property on Error
361
- if (err.code === "EADDRINUSE") {
362
- setTimeout(checkPort, options.retryPeriod);
363
- } else {
364
- reject(err);
428
+ if (err.code !== "EADDRINUSE") {
429
+ doReject(err);
365
430
  }
366
431
  });
367
432
  server.listen(port, () => {
368
433
  server.close();
369
- clearTimeout(timeout);
370
- resolve();
434
+ doResolve();
371
435
  });
372
436
  }
373
-
374
- checkPort();
375
437
  });
376
438
  }
package/src/publish.ts CHANGED
@@ -2,23 +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
17
  accountId: string;
20
- format: CfScriptFormat | undefined;
21
- entry: { file: string; directory: string };
18
+ entry: Entry;
22
19
  rules: Config["rules"];
23
20
  name: string | undefined;
24
21
  env: string | undefined;
@@ -45,7 +42,7 @@ export default async function publish(props: Props): Promise<void> {
45
42
  const envRootObj = (props.env && config.env[props.env]) || config;
46
43
 
47
44
  assert(
48
- envRootObj.compatibility_date || props.compatibilityDate,
45
+ props.compatibilityDate || envRootObj.compatibility_date,
49
46
  "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."
50
47
  );
51
48
 
@@ -56,7 +53,7 @@ export default async function publish(props: Props): Promise<void> {
56
53
  (envRootObj.route ? [envRootObj.route] : []) ??
57
54
  [];
58
55
 
59
- const { workers_dev: deployToWorkersDev } = config;
56
+ const deployToWorkersDev = envRootObj.workers_dev;
60
57
 
61
58
  const jsxFactory = props.jsxFactory || envRootObj.jsx_factory;
62
59
  const jsxFragment = props.jsxFragment || envRootObj.jsx_fragment;
@@ -67,14 +64,6 @@ export default async function publish(props: Props): Promise<void> {
67
64
  '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>"`'
68
65
  );
69
66
 
70
- if (config.site?.["entry-point"]) {
71
- console.warn(
72
- "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
73
- "The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `main` config field.\n" +
74
- "Please remove the `site.entry-point` field from the `wrangler.toml` file."
75
- );
76
- }
77
-
78
67
  assert(
79
68
  !config.site || config.site.bucket,
80
69
  "A [site] definition requires a `bucket` field with a path to the site's public directory."
@@ -84,28 +73,7 @@ export default async function publish(props: Props): Promise<void> {
84
73
  try {
85
74
  const envName = props.env ?? "production";
86
75
 
87
- if (config.build.command) {
88
- // TODO: add a deprecation message here?
89
- console.log("running:", config.build.command);
90
- await execaCommand(config.build.command, {
91
- shell: true,
92
- stdout: "inherit",
93
- stderr: "inherit",
94
- timeout: 1000 * 30,
95
- ...(config.build.cwd && { cwd: config.build.cwd }),
96
- });
97
-
98
- if (fileExists(props.entry.file) === false) {
99
- throw new Error(
100
- `Could not resolve "${path.relative(
101
- process.cwd(),
102
- props.entry.file
103
- )}".`
104
- );
105
- }
106
- }
107
-
108
- const format = await guessWorkerFormat(props.entry, props.format);
76
+ const { format } = props.entry;
109
77
 
110
78
  if (props.experimentalPublic && format === "service-worker") {
111
79
  // TODO: check config too
@@ -133,7 +101,6 @@ export default async function publish(props: Props): Promise<void> {
133
101
  serveAssetsFromWorker: props.experimentalPublic,
134
102
  jsxFactory,
135
103
  jsxFragment,
136
- format,
137
104
  rules: props.rules,
138
105
  }
139
106
  );
@@ -230,9 +197,11 @@ export default async function publish(props: Props): Promise<void> {
230
197
  bindings,
231
198
  migrations,
232
199
  modules,
233
- compatibility_date: config.compatibility_date,
234
- compatibility_flags: config.compatibility_flags,
235
- usage_model: config.usage_model,
200
+ compatibility_date:
201
+ props.compatibilityDate ?? envRootObj.compatibility_date,
202
+ compatibility_flags:
203
+ props.compatibilityFlags ?? envRootObj.compatibility_flags,
204
+ usage_model: envRootObj.usage_model,
236
205
  };
237
206
 
238
207
  const start = Date.now();
@@ -247,22 +216,17 @@ export default async function publish(props: Props): Promise<void> {
247
216
  workerUrl,
248
217
  {
249
218
  method: "PUT",
250
- body: toFormData(worker),
219
+ body: createWorkerUploadForm(worker),
251
220
  },
252
221
  new URLSearchParams({ available_on_subdomain: "true" })
253
222
  );
254
223
 
255
224
  const uploadMs = Date.now() - start;
256
- console.log("Uploaded", workerName, formatTime(uploadMs));
257
225
  const deployments: Promise<string[]>[] = [];
258
226
 
259
227
  if (deployToWorkersDev) {
260
228
  // Deploy to a subdomain of `workers.dev`
261
- const userSubdomain = (
262
- await fetchResult<{ subdomain: string }>(
263
- `/accounts/${accountId}/workers/subdomain`
264
- )
265
- ).subdomain;
229
+ const userSubdomain = await getSubdomain(accountId);
266
230
  const scriptURL =
267
231
  props.legacyEnv || !props.env
268
232
  ? `${scriptName}.${userSubdomain}.workers.dev`
@@ -301,6 +265,8 @@ export default async function publish(props: Props): Promise<void> {
301
265
  });
302
266
  }
303
267
 
268
+ console.log("Uploaded", workerName, formatTime(uploadMs));
269
+
304
270
  // Update routing table for the script.
305
271
  if (routes.length > 0) {
306
272
  deployments.push(
@@ -361,3 +327,28 @@ export default async function publish(props: Props): Promise<void> {
361
327
  function formatTime(duration: number) {
362
328
  return `(${(duration / 1000).toFixed(2)} sec)`;
363
329
  }
330
+
331
+ async function getSubdomain(accountId: string): Promise<string> {
332
+ try {
333
+ const { subdomain } = await fetchResult(
334
+ `/accounts/${accountId}/workers/subdomain`
335
+ );
336
+ return subdomain;
337
+ } catch (e) {
338
+ const error = e as { code?: number };
339
+ if (typeof error === "object" && !!error && error.code === 10007) {
340
+ // 10007 error code: not found
341
+ // https://api.cloudflare.com/#worker-subdomain-get-subdomain
342
+
343
+ const errorMessage =
344
+ "Error: You need to register a workers.dev subdomain before publishing to workers.dev";
345
+ const solutionMessage =
346
+ "You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
347
+ const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
348
+
349
+ throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
350
+ } else {
351
+ throw e;
352
+ }
353
+ }
354
+ }
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,