wrangler 0.0.0-e88a54e → 0.0.0-e95e5a0

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +530 -201
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1516 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +20 -4
  43. package/src/config/environment.ts +32 -4
  44. package/src/config/index.ts +4 -3
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +388 -54
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +689 -394
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +875 -56
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +28 -19
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21162 -50227
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
package/src/dev/local.tsx CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { useState, useEffect, useRef } from "react";
6
6
  import onExit from "signal-exit";
7
7
  import useInspector from "../inspect";
8
+ import { logger } from "../logger";
8
9
  import { DEFAULT_MODULE_RULES } from "../module-collection";
9
10
  import { waitForPortToBeAvailable } from "../proxy";
10
11
  import type { Config } from "../config";
@@ -14,19 +15,20 @@ import type { EsbuildBundle } from "./use-esbuild";
14
15
  import type { MiniflareOptions } from "miniflare";
15
16
 
16
17
  interface LocalProps {
17
- name: undefined | string;
18
+ name: string | undefined;
18
19
  bundle: EsbuildBundle | undefined;
19
20
  format: CfScriptFormat | undefined;
20
- compatibilityDate: string | undefined;
21
- compatibilityFlags: undefined | string[];
21
+ compatibilityDate: string;
22
+ compatibilityFlags: string[] | undefined;
22
23
  bindings: CfWorkerInit["bindings"];
23
- assetPaths: undefined | AssetPaths;
24
- public: undefined | string;
24
+ assetPaths: AssetPaths | undefined;
25
+ public: string | undefined;
25
26
  port: number;
26
27
  ip: string;
27
28
  rules: Config["rules"];
28
29
  inspectorPort: number;
29
30
  enableLocalPersistence: boolean;
31
+ crons: Config["triggers"]["crons"];
30
32
  }
31
33
 
32
34
  export function Local(props: LocalProps) {
@@ -52,17 +54,33 @@ function useLocalWorker({
52
54
  rules,
53
55
  enableLocalPersistence,
54
56
  ip,
57
+ crons,
55
58
  }: LocalProps) {
56
59
  // TODO: pass vars via command line
57
60
  const local = useRef<ReturnType<typeof spawn>>();
58
61
  const removeSignalExitListener = useRef<() => void>();
59
62
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
63
+ // if we're using local persistence for data, we should use the cwd
64
+ // as an explicit path, or else it'll use the temp dir
65
+ // which disappears when dev ends
66
+ const localPersistencePath = enableLocalPersistence
67
+ ? // Maybe we could make the path configurable as well?
68
+ path.join(process.cwd(), "wrangler-local-state")
69
+ : // We otherwise choose null, but choose true later
70
+ // so that it's persisted in the temp dir across a dev session
71
+ // even when we change source and reload
72
+ null;
60
73
  useEffect(() => {
74
+ const abortController = new AbortController();
61
75
  async function startLocalWorker() {
62
76
  if (!bundle || !format) return;
63
77
 
64
78
  // port for the worker
65
- await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
79
+ await waitForPortToBeAvailable(port, {
80
+ retryPeriod: 200,
81
+ timeout: 2000,
82
+ abortSignal: abortController.signal,
83
+ });
66
84
 
67
85
  if (publicDirectory) {
68
86
  throw new Error(
@@ -81,7 +99,35 @@ function useLocalWorker({
81
99
 
82
100
  const scriptPath = realpathSync(bundle.path);
83
101
 
84
- const wasmBindings = { ...bindings.wasm_modules };
102
+ // the wasm_modules/text_blobs/data_blobs bindings are
103
+ // relative to process.cwd(), but the actual worker bundle
104
+ // is in the temp output directory; so we rewrite the paths to be absolute,
105
+ // letting miniflare resolve them correctly
106
+
107
+ // wasm
108
+ const wasmBindings: Record<string, string> = {};
109
+ for (const [name, filePath] of Object.entries(
110
+ bindings.wasm_modules || {}
111
+ )) {
112
+ wasmBindings[name] = path.join(process.cwd(), filePath);
113
+ }
114
+
115
+ // text
116
+ const textBlobBindings: Record<string, string> = {};
117
+ for (const [name, filePath] of Object.entries(
118
+ bindings.text_blobs || {}
119
+ )) {
120
+ textBlobBindings[name] = path.join(process.cwd(), filePath);
121
+ }
122
+
123
+ // data
124
+ const dataBlobBindings: Record<string, string> = {};
125
+ for (const [name, filePath] of Object.entries(
126
+ bindings.data_blobs || {}
127
+ )) {
128
+ dataBlobBindings[name] = path.join(process.cwd(), filePath);
129
+ }
130
+
85
131
  if (format === "service-worker") {
86
132
  for (const { type, name } of bundle.modules) {
87
133
  if (type === "compiled-wasm") {
@@ -91,6 +137,20 @@ function useLocalWorker({
91
137
  // characters with an underscore.
92
138
  const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
93
139
  wasmBindings[identifier] = name;
140
+ } else if (type === "text") {
141
+ // In service-worker format, text modules are referenced by global identifiers,
142
+ // so we convert it here.
143
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
144
+ // characters with an underscore.
145
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
146
+ textBlobBindings[identifier] = name;
147
+ } else if (type === "buffer") {
148
+ // In service-worker format, data blobs are referenced by global identifiers,
149
+ // so we convert it here.
150
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
151
+ // characters with an underscore.
152
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
153
+ dataBlobBindings[identifier] = name;
94
154
  }
95
155
  }
96
156
  }
@@ -111,14 +171,28 @@ function useLocalWorker({
111
171
  compatibilityDate,
112
172
  compatibilityFlags,
113
173
  kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
114
- kvPersist: enableLocalPersistence,
115
174
  durableObjects: Object.fromEntries(
116
175
  (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
117
176
  (value) => [value.name, value.class_name]
118
177
  )
119
178
  ),
120
- durableObjectsPersist: enableLocalPersistence,
121
- cachePersist: enableLocalPersistence,
179
+ ...(localPersistencePath
180
+ ? {
181
+ kvPersist: path.join(localPersistencePath, "kv"),
182
+ durableObjectsPersist: path.join(localPersistencePath, "do"),
183
+ cachePersist: path.join(localPersistencePath, "cache"),
184
+ }
185
+ : {
186
+ // We mark these as true, so that they'll
187
+ // persist in the temp directory.
188
+ // This means they'll persist across a dev session,
189
+ // even if we change source and reload,
190
+ // and be deleted when the dev session ends
191
+ durableObjectsPersist: true,
192
+ cachePersist: true,
193
+ kvPersist: true,
194
+ }),
195
+
122
196
  sitePath: assetPaths?.assetDirectory
123
197
  ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
124
198
  : undefined,
@@ -130,8 +204,11 @@ function useLocalWorker({
130
204
  : undefined,
131
205
  bindings: bindings.vars,
132
206
  wasmBindings,
207
+ textBlobBindings,
208
+ dataBlobBindings,
133
209
  sourceMap: true,
134
210
  logUnhandledRejections: true,
211
+ crons,
135
212
  };
136
213
 
137
214
  // The path to the Miniflare CLI assumes that this file is being run from
@@ -143,7 +220,7 @@ function useLocalWorker({
143
220
  );
144
221
  const optionsArg = JSON.stringify(options, null);
145
222
 
146
- console.log("⎔ Starting a local server...");
223
+ logger.log("⎔ Starting a local server...");
147
224
  local.current = spawn(
148
225
  "node",
149
226
  [
@@ -161,16 +238,15 @@ function useLocalWorker({
161
238
 
162
239
  local.current.on("close", (code) => {
163
240
  if (code) {
164
- console.log(`Miniflare process exited with code ${code}`);
241
+ logger.log(`Miniflare process exited with code ${code}`);
165
242
  }
166
243
  });
167
244
 
168
245
  local.current.stdout?.on("data", (data: Buffer) => {
169
- console.log(`${data.toString()}`);
246
+ process.stdout.write(data);
170
247
  });
171
248
 
172
249
  local.current.stderr?.on("data", (data: Buffer) => {
173
- console.error(`${data.toString()}`);
174
250
  const matches =
175
251
  /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9-]+)/.exec(
176
252
  data.toString()
@@ -182,29 +258,30 @@ function useLocalWorker({
182
258
 
183
259
  local.current.on("exit", (code) => {
184
260
  if (code) {
185
- console.error(`Miniflare process exited with code ${code}`);
261
+ logger.error(`Miniflare process exited with code ${code}`);
186
262
  }
187
263
  });
188
264
 
189
265
  local.current.on("error", (error: Error) => {
190
- console.error(`Miniflare process failed to spawn`);
191
- console.error(error);
266
+ logger.error(`Miniflare process failed to spawn`);
267
+ logger.error(error);
192
268
  });
193
269
 
194
270
  removeSignalExitListener.current = onExit((_code, _signal) => {
195
- console.log("⎔ Shutting down local server.");
271
+ logger.log("⎔ Shutting down local server.");
196
272
  local.current?.kill();
197
273
  local.current = undefined;
198
274
  });
199
275
  }
200
276
 
201
277
  startLocalWorker().catch((err) => {
202
- console.error("local worker:", err);
278
+ logger.error("local worker:", err);
203
279
  });
204
280
 
205
281
  return () => {
282
+ abortController.abort();
206
283
  if (local.current) {
207
- console.log("⎔ Shutting down local server.");
284
+ logger.log("⎔ Shutting down local server.");
208
285
  local.current?.kill();
209
286
  local.current = undefined;
210
287
  removeSignalExitListener.current && removeSignalExitListener.current();
@@ -222,11 +299,14 @@ function useLocalWorker({
222
299
  bindings.vars,
223
300
  compatibilityDate,
224
301
  compatibilityFlags,
225
- enableLocalPersistence,
302
+ localPersistencePath,
226
303
  assetPaths,
227
304
  publicDirectory,
228
305
  rules,
229
306
  bindings.wasm_modules,
307
+ bindings.text_blobs,
308
+ bindings.data_blobs,
309
+ crons,
230
310
  ]);
231
311
  return { inspectorUrl };
232
312
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { useState, useEffect, useRef } from "react";
5
5
  import { createWorkerPreview } from "../create-worker-preview";
6
6
  import useInspector from "../inspect";
7
+ import { logger } from "../logger";
7
8
  import { usePreviewServer } from "../proxy";
8
9
  import { syncAssets } from "../sites";
9
10
  import type { CfPreviewToken } from "../create-worker-preview";
@@ -15,18 +16,18 @@ export function Remote(props: {
15
16
  name: string | undefined;
16
17
  bundle: EsbuildBundle | undefined;
17
18
  format: CfScriptFormat | undefined;
18
- public: undefined | string;
19
- assetPaths: undefined | AssetPaths;
19
+ public: string | undefined;
20
+ assetPaths: AssetPaths | undefined;
20
21
  port: number;
21
22
  ip: string;
22
23
  localProtocol: "https" | "http";
23
24
  inspectorPort: number;
24
- accountId: undefined | string;
25
- apiToken: undefined | string;
25
+ accountId: string | undefined;
26
+ apiToken: string | undefined;
26
27
  bindings: CfWorkerInit["bindings"];
27
- compatibilityDate: string | undefined;
28
- compatibilityFlags: undefined | string[];
29
- usageModel: undefined | "bundled" | "unbound";
28
+ compatibilityDate: string;
29
+ compatibilityFlags: string[] | undefined;
30
+ usageModel: "bundled" | "unbound" | undefined;
30
31
  env: string | undefined;
31
32
  legacyEnv: boolean | undefined;
32
33
  zone: { id: string; host: string } | undefined;
@@ -75,11 +76,11 @@ export function useWorker(props: {
75
76
  accountId: string;
76
77
  apiToken: string;
77
78
  bindings: CfWorkerInit["bindings"];
78
- assetPaths: undefined | AssetPaths;
79
+ assetPaths: AssetPaths | undefined;
79
80
  port: number;
80
81
  compatibilityDate: string | undefined;
81
82
  compatibilityFlags: string[] | undefined;
82
- usageModel: undefined | "bundled" | "unbound";
83
+ usageModel: "bundled" | "unbound" | undefined;
83
84
  env: string | undefined;
84
85
  legacyEnv: boolean | undefined;
85
86
  zone: { id: string; host: string } | undefined;
@@ -106,6 +107,7 @@ export function useWorker(props: {
106
107
  const startedRef = useRef(false);
107
108
 
108
109
  useEffect(() => {
110
+ const abortController = new AbortController();
109
111
  async function start() {
110
112
  setToken(undefined); // reset token in case we're re-running
111
113
 
@@ -114,7 +116,7 @@ export function useWorker(props: {
114
116
  if (!startedRef.current) {
115
117
  startedRef.current = true;
116
118
  } else {
117
- console.log("⎔ Detected changes, restarted server.");
119
+ logger.log("⎔ Detected changes, restarted server.");
118
120
  }
119
121
 
120
122
  const assets = await syncAssets(
@@ -125,7 +127,8 @@ export function useWorker(props: {
125
127
  // concept of service environments for kv namespaces yet).
126
128
  name + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
127
129
  assetPaths,
128
- true
130
+ true,
131
+ false
129
132
  ); // TODO: cancellable?
130
133
 
131
134
  const content = await readFile(bundle.path, "utf-8");
@@ -173,15 +176,22 @@ export function useWorker(props: {
173
176
  accountId,
174
177
  apiToken,
175
178
  },
176
- { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone }
179
+ { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone },
180
+ abortController.signal
177
181
  )
178
182
  );
179
183
  }
180
184
  start().catch((err) => {
181
185
  // we want to log the error, but not end the process
182
186
  // since it could recover after the developer fixes whatever's wrong
183
- console.error("remote worker:", err);
187
+ if ((err as { code: string }).code !== "ABORT_ERR") {
188
+ logger.error("Error on remote worker:", err);
189
+ }
184
190
  });
191
+
192
+ return () => {
193
+ abortController.abort();
194
+ };
185
195
  }, [
186
196
  name,
187
197
  bundle,
@@ -1,6 +1,8 @@
1
1
  import assert from "node:assert";
2
+ import { useApp } from "ink";
2
3
  import { useState, useEffect } from "react";
3
4
  import { bundleWorker } from "../bundle";
5
+ import { logger } from "../logger";
4
6
  import type { Config } from "../config";
5
7
  import type { Entry } from "../entry";
6
8
  import type { CfModule } from "../worker";
@@ -23,22 +25,29 @@ export function useEsbuild({
23
25
  jsxFragment,
24
26
  rules,
25
27
  serveAssetsFromWorker,
28
+ tsconfig,
29
+ minify,
30
+ nodeCompat,
26
31
  }: {
27
32
  entry: Entry;
28
33
  destination: string | undefined;
29
- staticRoot: undefined | string;
34
+ staticRoot: string | undefined;
30
35
  jsxFactory: string | undefined;
31
36
  jsxFragment: string | undefined;
32
37
  rules: Config["rules"];
33
38
  serveAssetsFromWorker: boolean;
39
+ tsconfig: string | undefined;
40
+ minify: boolean | undefined;
41
+ nodeCompat: boolean | undefined;
34
42
  }): EsbuildBundle | undefined {
35
43
  const [bundle, setBundle] = useState<EsbuildBundle>();
44
+ const { exit } = useApp();
36
45
  useEffect(() => {
37
46
  let stopWatching: (() => void) | undefined = undefined;
38
47
 
39
48
  const watchMode: WatchMode = {
40
49
  async onRebuild(error) {
41
- if (error) console.error("watch build failed:", error);
50
+ if (error) logger.error("Watch build failed:", error);
42
51
  else {
43
52
  // nothing really changes here, so let's increment the id
44
53
  // to change the return object's identity
@@ -64,6 +73,9 @@ export function useEsbuild({
64
73
  jsxFragment,
65
74
  rules,
66
75
  watch: watchMode,
76
+ tsconfig,
77
+ minify,
78
+ nodeCompat,
67
79
  });
68
80
 
69
81
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -79,9 +91,11 @@ export function useEsbuild({
79
91
  });
80
92
  }
81
93
 
82
- build().catch(() => {
83
- // esbuild already logs errors to stderr and we don't want to end the process
84
- // on build errors anyway so this is a no-op error handler
94
+ build().catch((err) => {
95
+ // If esbuild fails on first run, we want to quit the process
96
+ // since we can't recover from here
97
+ // related: https://github.com/evanw/esbuild/issues/1037
98
+ exit(err);
85
99
  });
86
100
 
87
101
  return () => {
@@ -95,6 +109,10 @@ export function useEsbuild({
95
109
  jsxFragment,
96
110
  serveAssetsFromWorker,
97
111
  rules,
112
+ tsconfig,
113
+ exit,
114
+ minify,
115
+ nodeCompat,
98
116
  ]);
99
117
  return bundle;
100
118
  }
package/src/dialogs.tsx CHANGED
@@ -1,24 +1,28 @@
1
+ import chalk from "chalk";
1
2
  import { Box, Text, useInput, render } from "ink";
2
3
  import TextInput from "ink-text-input";
3
4
  import * as React from "react";
4
5
  import { useState } from "react";
6
+ import { logger } from "./logger";
5
7
  type ConfirmProps = {
6
8
  text: string;
7
9
  onConfirm: (answer: boolean) => void;
8
10
  };
9
11
  function Confirm(props: ConfirmProps) {
10
- useInput((input: string) => {
11
- if (input === "y") {
12
+ useInput((input: string, key) => {
13
+ if (input === "y" || key.return === true) {
12
14
  props.onConfirm(true);
13
15
  } else if (input === "n") {
14
16
  props.onConfirm(false);
15
17
  } else {
16
- console.log("Unrecognised input");
18
+ logger.warn("Unrecognised input:", input);
17
19
  }
18
20
  });
19
21
  return (
20
22
  <Box>
21
- <Text>{props.text} (y/n) </Text>
23
+ <Text>
24
+ {props.text} ({chalk.bold("y")}/n)
25
+ </Text>
22
26
  </Box>
23
27
  );
24
28
  }
@@ -39,12 +43,13 @@ export function confirm(text: string): Promise<boolean> {
39
43
 
40
44
  type PromptProps = {
41
45
  text: string;
46
+ defaultValue?: string;
42
47
  type?: "text" | "password";
43
48
  onSubmit: (text: string) => void;
44
49
  };
45
50
 
46
51
  function Prompt(props: PromptProps) {
47
- const [value, setValue] = useState("");
52
+ const [value, setValue] = useState(props.defaultValue || "");
48
53
 
49
54
  return (
50
55
  <Box>
@@ -61,11 +66,16 @@ function Prompt(props: PromptProps) {
61
66
  );
62
67
  }
63
68
 
64
- export async function prompt(text: string, type: "text" | "password" = "text") {
69
+ export async function prompt(
70
+ text: string,
71
+ type: "text" | "password" = "text",
72
+ defaultValue?: string
73
+ ): Promise<string> {
65
74
  return new Promise((resolve) => {
66
75
  const { unmount } = render(
67
76
  <Prompt
68
77
  text={text}
78
+ defaultValue={defaultValue}
69
79
  type={type}
70
80
  onSubmit={(inputText) => {
71
81
  unmount();
package/src/entry.ts CHANGED
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import * as esbuild from "esbuild";
5
5
  import { execaCommand } from "execa";
6
+ import { logger } from "./logger";
6
7
  import type { Config } from "./config";
7
8
  import type { CfScriptFormat } from "./worker";
8
9
  import type { Metafile } from "esbuild";
@@ -20,7 +21,7 @@ export type Entry = { file: string; directory: string; format: CfScriptFormat };
20
21
  export async function getEntry(
21
22
  args: { script?: string; format?: CfScriptFormat | undefined },
22
23
  config: Config,
23
- command: string
24
+ command: "dev" | "publish"
24
25
  ): Promise<Entry> {
25
26
  let file: string;
26
27
  let directory = process.cwd();
@@ -28,9 +29,17 @@ export async function getEntry(
28
29
  // If the script name comes from the command line it is relative to the current working directory.
29
30
  file = path.resolve(args.script);
30
31
  } else if (config.main === undefined) {
31
- throw new Error(
32
- `Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler ${command} path/to/script\`) or the \`main\` config field.`
33
- );
32
+ if (config.site?.["entry-point"]) {
33
+ directory = path.resolve(path.dirname(config.configPath ?? "."));
34
+ file = path.extname(config.site?.["entry-point"])
35
+ ? path.resolve(config.site?.["entry-point"])
36
+ : // site.entry-point could be a directory
37
+ path.resolve(config.site?.["entry-point"], "index.js");
38
+ } else {
39
+ throw new Error(
40
+ `Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler ${command} path/to/script\`) or the \`main\` config field.`
41
+ );
42
+ }
34
43
  } else {
35
44
  directory = path.resolve(path.dirname(config.configPath ?? "."));
36
45
  file = path.resolve(directory, config.main);
@@ -46,15 +55,28 @@ export async function getEntry(
46
55
  const format = await guessWorkerFormat(
47
56
  file,
48
57
  directory,
49
- args.format ?? config.build?.upload?.format
58
+ args.format ?? config.build?.upload?.format,
59
+ config.tsconfig
50
60
  );
51
61
 
52
- if (format === "service-worker" && hasDurableObjectImplementations(config)) {
62
+ const { localBindings, remoteBindings } =
63
+ partitionDurableObjectBindings(config);
64
+
65
+ if (command === "dev" && remoteBindings.length > 0) {
66
+ logger.warn(
67
+ "WARNING: You have Durable Object bindings that are not defined locally in the worker being developed.\n" +
68
+ "Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.\n" +
69
+ "Remote Durable Objects that are affected:\n" +
70
+ remoteBindings.map((b) => `- ${JSON.stringify(b)}`).join("\n")
71
+ );
72
+ }
73
+
74
+ if (format === "service-worker" && localBindings.length > 0) {
53
75
  const errorMessage =
54
- "You seem to be trying to use Durable Objects in a Worker written with Service Worker syntax.";
76
+ "You seem to be trying to use Durable Objects in a Worker written as a service-worker.";
55
77
  const addScriptName =
56
78
  "You can use Durable Objects defined in other Workers by specifying a `script_name` in your wrangler.toml, where `script_name` is the name of the Worker that implements that Durable Object. For example:";
57
- const addScriptNameExamples = generateAddScriptNameExamples(config);
79
+ const addScriptNameExamples = generateAddScriptNameExamples(localBindings);
58
80
  const migrateText =
59
81
  "Alternatively, migrate your worker to ES Module syntax to implement a Durable Object in this Worker:";
60
82
  const migrateUrl =
@@ -73,12 +95,13 @@ export async function runCustomBuild(
73
95
  ) {
74
96
  if (build?.command) {
75
97
  // TODO: add a deprecation message here?
76
- console.log("running:", build.command);
98
+ logger.log("Running custom build:", build.command);
77
99
  await execaCommand(build.command, {
78
100
  shell: true,
101
+ // we keep these two as "inherit" so that
102
+ // logs are still visible.
79
103
  stdout: "inherit",
80
104
  stderr: "inherit",
81
- timeout: 1000 * 30,
82
105
  ...(build.cwd && { cwd: build.cwd }),
83
106
  });
84
107
 
@@ -104,7 +127,8 @@ export async function runCustomBuild(
104
127
  export default async function guessWorkerFormat(
105
128
  entryFile: string,
106
129
  entryWorkingDirectory: string,
107
- hint: CfScriptFormat | undefined
130
+ hint: CfScriptFormat | undefined,
131
+ tsconfig?: string | undefined
108
132
  ): Promise<CfScriptFormat> {
109
133
  const result = await esbuild.build({
110
134
  entryPoints: [entryFile],
@@ -112,7 +136,14 @@ export default async function guessWorkerFormat(
112
136
  metafile: true,
113
137
  bundle: false,
114
138
  format: "esm",
139
+ target: "es2020",
115
140
  write: false,
141
+ loader: {
142
+ ".js": "jsx",
143
+ ".mjs": "jsx",
144
+ ".cjs": "jsx",
145
+ },
146
+ ...(tsconfig && { tsconfig }),
116
147
  });
117
148
  // result.metafile is defined because of the `metafile: true` option above.
118
149
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -130,14 +161,30 @@ export default async function guessWorkerFormat(
130
161
  "More than one entry-point found for generated bundle." +
131
162
  listEntryPoints(entryPoints)
132
163
  );
133
- const guessedWorkerFormat =
134
- entryPoints[0][1].exports.length > 0 ? "modules" : "service-worker";
164
+
165
+ let guessedWorkerFormat: CfScriptFormat;
166
+ const scriptExports = entryPoints[0][1].exports;
167
+ if (scriptExports.length > 0) {
168
+ if (scriptExports.includes("default")) {
169
+ guessedWorkerFormat = "modules";
170
+ } else {
171
+ logger.warn(
172
+ `The entrypoint ${path.relative(
173
+ process.cwd(),
174
+ entryFile
175
+ )} has exports like an ES Module, but hasn't defined a default export like a module worker normally would. Building the worker using "service-worker" format...`
176
+ );
177
+ guessedWorkerFormat = "service-worker";
178
+ }
179
+ } else {
180
+ guessedWorkerFormat = "service-worker";
181
+ }
135
182
 
136
183
  if (hint) {
137
184
  if (hint !== guessedWorkerFormat) {
138
185
  if (hint === "service-worker") {
139
186
  throw new Error(
140
- "You configured this worker to be a 'service-worker', but the file you are trying to build appears to have ES module exports. Please pass `--format modules`, or simply remove the configuration."
187
+ "You configured this worker to be a 'service-worker', but the file you are trying to build appears to have a `default` export like a module worker. Please pass `--format modules`, or simply remove the configuration."
141
188
  );
142
189
  } else {
143
190
  throw new Error(
@@ -174,15 +221,26 @@ export function fileExists(filePath: string): boolean {
174
221
  return false;
175
222
  }
176
223
 
224
+ type DurableObjectBindings = Config["durable_objects"]["bindings"];
225
+
177
226
  /**
178
- * Returns true if the given config contains Durable Object bindings that are implemented
179
- * in this worker instead of being implemented elsewhere, and bound via a `script_name`
180
- * property in wrangler.toml
227
+ * Groups the durable object bindings into two lists:
228
+ * those that are defined locally and those that refer to a durable object defined in another script.
181
229
  */
182
- function hasDurableObjectImplementations(config: Config): boolean {
183
- return config.durable_objects.bindings.some(
184
- (binding) => binding.script_name === undefined
185
- );
230
+ function partitionDurableObjectBindings(config: Config): {
231
+ localBindings: DurableObjectBindings;
232
+ remoteBindings: DurableObjectBindings;
233
+ } {
234
+ const localBindings: DurableObjectBindings = [];
235
+ const remoteBindings: DurableObjectBindings = [];
236
+ for (const binding of config.durable_objects.bindings) {
237
+ if (binding.script_name === undefined) {
238
+ localBindings.push(binding);
239
+ } else {
240
+ remoteBindings.push(binding);
241
+ }
242
+ }
243
+ return { localBindings, remoteBindings };
186
244
  }
187
245
 
188
246
  /**
@@ -190,13 +248,14 @@ function hasDurableObjectImplementations(config: Config): boolean {
190
248
  * config indicating how the user can add a `script_name` field to bind an
191
249
  * externally defined Durable Object.
192
250
  */
193
- function generateAddScriptNameExamples(config: Config): string {
251
+ function generateAddScriptNameExamples(
252
+ localBindings: DurableObjectBindings
253
+ ): string {
194
254
  function exampleScriptName(binding_name: string): string {
195
255
  return `${binding_name.toLowerCase().replaceAll("_", "-")}-worker`;
196
256
  }
197
257
 
198
- return config.durable_objects.bindings
199
- .filter((binding) => binding.script_name === undefined)
258
+ return localBindings
200
259
  .map(({ name, class_name }) => {
201
260
  const script_name = exampleScriptName(name);
202
261
  const currentBinding = `{ name = ${name}, class_name = ${class_name} }`;