wrangler 0.0.0-e95bea2 → 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 (93) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +33 -25
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +136 -25
  8. package/pages/functions/filepath-routing.ts +14 -3
  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 +1179 -271
  14. package/src/__tests__/dev.test.tsx +382 -25
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  18. package/src/__tests__/helpers/mock-console.ts +20 -3
  19. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +27 -0
  22. package/src/__tests__/helpers/mock-kv.ts +9 -16
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  24. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  25. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  26. package/src/__tests__/https-options.test.ts +5 -2
  27. package/src/__tests__/index.test.ts +27 -769
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +4 -4
  30. package/src/__tests__/kv.test.ts +172 -126
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +311 -26
  33. package/src/__tests__/publish.test.ts +2946 -524
  34. package/src/__tests__/r2.test.ts +26 -10
  35. package/src/__tests__/route.test.ts +4 -4
  36. package/src/__tests__/secret.test.ts +55 -34
  37. package/src/__tests__/tail.test.ts +146 -20
  38. package/src/__tests__/user.test.ts +131 -0
  39. package/src/__tests__/whoami.test.tsx +26 -7
  40. package/src/bundle.ts +30 -3
  41. package/src/cfetch/internal.ts +9 -7
  42. package/src/cli.ts +5 -11
  43. package/src/config/README.md +7 -7
  44. package/src/config/config.ts +33 -78
  45. package/src/config/environment.ts +93 -4
  46. package/src/config/index.ts +15 -4
  47. package/src/config/validation-helpers.ts +202 -15
  48. package/src/config/validation.ts +530 -133
  49. package/src/config-cache.ts +45 -0
  50. package/src/create-worker-preview.ts +22 -13
  51. package/src/create-worker-upload-form.ts +32 -9
  52. package/src/dev/dev-vars.ts +36 -0
  53. package/src/dev/dev.tsx +136 -75
  54. package/src/dev/local.tsx +196 -125
  55. package/src/dev/remote.tsx +25 -13
  56. package/src/dev/use-esbuild.ts +23 -5
  57. package/src/dialogs.tsx +16 -6
  58. package/src/entry.ts +119 -11
  59. package/src/environment-variables.ts +3 -1
  60. package/src/errors.ts +11 -0
  61. package/src/https-options.ts +3 -2
  62. package/src/index.tsx +739 -400
  63. package/src/inspect.ts +17 -11
  64. package/src/intl-polyfill.d.ts +139 -0
  65. package/src/{kv/index.tsx → kv.ts} +8 -28
  66. package/src/logger.ts +66 -0
  67. package/src/miniflare-cli/README.md +30 -0
  68. package/src/miniflare-cli/enum-keys.ts +17 -0
  69. package/src/miniflare-cli/index.ts +41 -0
  70. package/src/miniflare-cli/request-context.ts +40 -0
  71. package/src/module-collection.ts +63 -58
  72. package/src/open-in-browser.ts +5 -4
  73. package/src/package-manager.ts +9 -8
  74. package/src/pages.tsx +883 -62
  75. package/src/parse.ts +1 -1
  76. package/src/proxy.ts +32 -21
  77. package/src/publish.ts +276 -161
  78. package/src/sites.tsx +88 -33
  79. package/src/tail/index.ts +2 -2
  80. package/src/tail/printing.ts +13 -8
  81. package/src/update-check.ts +19 -0
  82. package/src/user.tsx +231 -103
  83. package/src/whoami.tsx +2 -1
  84. package/src/worker.ts +15 -6
  85. package/templates/gitignore +171 -0
  86. package/templates/static-asset-facade.js +1 -1
  87. package/wrangler-dist/cli.js +22087 -49340
  88. package/src/__tests__/dev2.test.tsx +0 -152
  89. package/src/__tests__/helpers/mock-user.ts +0 -27
  90. package/src/__tests__/logout.test.ts +0 -50
  91. package/src/__tests__/sentry.test.ts +0 -146
  92. package/src/reporting.ts +0 -131
  93. package/wrangler-dist/cli.js.map +0 -7
package/src/dev/local.tsx CHANGED
@@ -5,36 +5,34 @@ 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";
11
12
  import type { AssetPaths } from "../sites";
12
13
  import type { CfWorkerInit, CfScriptFormat } from "../worker";
13
14
  import type { EsbuildBundle } from "./use-esbuild";
15
+ import type { MiniflareOptions } from "miniflare";
14
16
 
15
- export function Local(props: {
16
- name: undefined | string;
17
+ interface LocalProps {
18
+ name: string | undefined;
17
19
  bundle: EsbuildBundle | undefined;
18
20
  format: CfScriptFormat | undefined;
21
+ compatibilityDate: string;
22
+ compatibilityFlags: string[] | undefined;
19
23
  bindings: CfWorkerInit["bindings"];
20
- assetPaths: undefined | AssetPaths;
21
- public: undefined | string;
24
+ assetPaths: AssetPaths | undefined;
25
+ public: string | undefined;
22
26
  port: number;
27
+ ip: string;
23
28
  rules: Config["rules"];
24
29
  inspectorPort: number;
25
30
  enableLocalPersistence: boolean;
26
- }) {
27
- const { inspectorUrl } = useLocalWorker({
28
- name: props.name,
29
- bundle: props.bundle,
30
- format: props.format,
31
- bindings: props.bindings,
32
- assetPaths: props.assetPaths,
33
- public: props.public,
34
- port: props.port,
35
- rules: props.rules,
36
- enableLocalPersistence: props.enableLocalPersistence,
37
- });
31
+ crons: Config["triggers"]["crons"];
32
+ }
33
+
34
+ export function Local(props: LocalProps) {
35
+ const { inspectorUrl } = useLocalWorker(props);
38
36
  useInspector({
39
37
  inspectorUrl,
40
38
  port: props.inspectorPort,
@@ -43,30 +41,48 @@ export function Local(props: {
43
41
  return null;
44
42
  }
45
43
 
46
- function useLocalWorker(props: {
47
- name: undefined | string;
48
- bundle: EsbuildBundle | undefined;
49
- rules: Config["rules"];
50
- format: CfScriptFormat | undefined;
51
- bindings: CfWorkerInit["bindings"];
52
- assetPaths: undefined | AssetPaths;
53
- public: undefined | string;
54
- port: number;
55
- enableLocalPersistence: boolean;
56
- }) {
44
+ function useLocalWorker({
45
+ name: workerName,
46
+ bundle,
47
+ format,
48
+ compatibilityDate,
49
+ compatibilityFlags,
50
+ bindings,
51
+ assetPaths,
52
+ public: publicDirectory,
53
+ port,
54
+ rules,
55
+ enableLocalPersistence,
56
+ ip,
57
+ crons,
58
+ }: LocalProps) {
57
59
  // TODO: pass vars via command line
58
- const { bundle, format, bindings, port, assetPaths } = props;
59
60
  const local = useRef<ReturnType<typeof spawn>>();
60
61
  const removeSignalExitListener = useRef<() => void>();
61
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;
62
73
  useEffect(() => {
74
+ const abortController = new AbortController();
63
75
  async function startLocalWorker() {
64
76
  if (!bundle || !format) return;
65
77
 
66
78
  // port for the worker
67
- await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
79
+ await waitForPortToBeAvailable(port, {
80
+ retryPeriod: 200,
81
+ timeout: 2000,
82
+ abortSignal: abortController.signal,
83
+ });
68
84
 
69
- if (props.public) {
85
+ if (publicDirectory) {
70
86
  throw new Error(
71
87
  '⎔ A "public" folder is not yet supported in local mode.'
72
88
  );
@@ -81,109 +97,156 @@ function useLocalWorker(props: {
81
97
  );
82
98
  }
83
99
 
84
- console.log("⎔ Starting a local server...");
85
- // TODO: just use execa for this
100
+ const scriptPath = realpathSync(bundle.path);
101
+
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
+
131
+ if (format === "service-worker") {
132
+ for (const { type, name } of bundle.modules) {
133
+ if (type === "compiled-wasm") {
134
+ // In service-worker format, .wasm modules are referenced by global identifiers,
135
+ // so we convert it here.
136
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
137
+ // characters with an underscore.
138
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
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;
154
+ }
155
+ }
156
+ }
157
+
158
+ const options: MiniflareOptions = {
159
+ name: workerName,
160
+ port,
161
+ scriptPath,
162
+ host: ip,
163
+ modules: format === "modules",
164
+ modulesRules: (rules || [])
165
+ .concat(DEFAULT_MODULE_RULES)
166
+ .map(({ type, globs: include, fallthrough }) => ({
167
+ type,
168
+ include,
169
+ fallthrough,
170
+ })),
171
+ compatibilityDate,
172
+ compatibilityFlags,
173
+ kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
174
+ durableObjects: Object.fromEntries(
175
+ (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
176
+ (value) => [value.name, value.class_name]
177
+ )
178
+ ),
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
+
196
+ sitePath: assetPaths?.assetDirectory
197
+ ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
198
+ : undefined,
199
+ siteInclude: assetPaths?.includePatterns.length
200
+ ? assetPaths?.includePatterns
201
+ : undefined,
202
+ siteExclude: assetPaths?.excludePatterns.length
203
+ ? assetPaths.excludePatterns
204
+ : undefined,
205
+ bindings: bindings.vars,
206
+ wasmBindings,
207
+ textBlobBindings,
208
+ dataBlobBindings,
209
+ sourceMap: true,
210
+ logUnhandledRejections: true,
211
+ crons,
212
+ };
213
+
214
+ // The path to the Miniflare CLI assumes that this file is being run from
215
+ // `wrangler-dist` and that the CLI is found in `miniflare-dist`.
216
+ // If either of those paths change this line needs updating.
217
+ const miniflareCLIPath = path.resolve(
218
+ __dirname,
219
+ "../miniflare-dist/index.mjs"
220
+ );
221
+ const optionsArg = JSON.stringify(options, null);
222
+
223
+ logger.log("⎔ Starting a local server...");
86
224
  local.current = spawn(
87
225
  "node",
88
226
  [
89
- "--experimental-vm-modules",
90
- "--inspect",
91
- require.resolve("miniflare/cli"),
92
- realpathSync(bundle.path),
93
- "--watch",
94
- "--wrangler-config",
95
- path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
96
- "--env",
97
- path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
98
- "--package",
99
- path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
100
- "--port",
101
- port.toString(),
102
- ...(assetPaths
103
- ? [
104
- "--site",
105
- path.join(process.cwd(), assetPaths.baseDirectory),
106
- ...assetPaths.includePatterns.map((pattern) => [
107
- "--site-include",
108
- pattern,
109
- ]),
110
- ...assetPaths.excludePatterns.map((pattern) => [
111
- "--site-exclude",
112
- pattern,
113
- ]),
114
- ].flatMap((x) => x)
115
- : []),
116
- ...(props.enableLocalPersistence
117
- ? ["--kv-persist", "--cache-persist", "--do-persist"]
118
- : []),
119
- ...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
120
- return ["--binding", `${key}=${value}`];
121
- }),
122
- ...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
123
- return ["--kv", binding];
124
- }),
125
- ...(bindings.durable_objects?.bindings || []).flatMap(
126
- ({ name, class_name }) => {
127
- return ["--do", `${name}=${class_name}`];
128
- }
129
- ),
130
- ...Object.entries(bindings.wasm_modules || {}).flatMap(
131
- ([name, filePath]) => {
132
- return [
133
- "--wasm",
134
- `${name}=${path.join(process.cwd(), filePath)}`,
135
- ];
136
- }
137
- ),
138
- ...bundle.modules.reduce<string[]>((cmd, { name, type }) => {
139
- if (format === "service-worker") {
140
- if (type === "compiled-wasm") {
141
- // In service-worker format, .wasm modules are referenced
142
- // by global identifiers, so we convert it here.
143
- // This identifier has to be a valid JS identifier,
144
- // so we replace all non alphanumeric characters
145
- // with an underscore.
146
- const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
147
- return cmd.concat([`--wasm`, `${identifier}=${name}`]);
148
- } else {
149
- // TODO: we should actually support this
150
- throw new Error(
151
- `⎔ Unsupported module type ${type} for file ${name} in service-worker format`
152
- );
153
- }
154
- }
155
- return cmd;
156
- }, []),
157
- "--modules",
158
- String(format === "modules"),
159
- ...(props.rules || [])
160
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
161
- .concat(DEFAULT_MODULE_RULES!)
162
- .flatMap((rule) =>
163
- rule.globs.flatMap((glob) => [
164
- "--modules-rule",
165
- `${rule.type}=${glob}`,
166
- ])
167
- ),
227
+ "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
228
+ "--no-warnings", // hide annoying Node warnings
229
+ "--inspect", // start Miniflare listening for a debugger to attach
230
+ miniflareCLIPath,
231
+ optionsArg,
232
+ // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
168
233
  ],
169
234
  {
170
- cwd: path.dirname(realpathSync(bundle.path)),
235
+ cwd: path.dirname(scriptPath),
171
236
  }
172
237
  );
173
- console.log(`⬣ Listening at http://localhost:${port}`);
174
238
 
175
239
  local.current.on("close", (code) => {
176
240
  if (code) {
177
- console.log(`miniflare process exited with code ${code}`);
241
+ logger.log(`Miniflare process exited with code ${code}`);
178
242
  }
179
243
  });
180
244
 
181
245
  local.current.stdout?.on("data", (data: Buffer) => {
182
- console.log(`${data.toString()}`);
246
+ process.stdout.write(data);
183
247
  });
184
248
 
185
249
  local.current.stderr?.on("data", (data: Buffer) => {
186
- console.error(`${data.toString()}`);
187
250
  const matches =
188
251
  /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9-]+)/.exec(
189
252
  data.toString()
@@ -195,29 +258,30 @@ function useLocalWorker(props: {
195
258
 
196
259
  local.current.on("exit", (code) => {
197
260
  if (code) {
198
- console.error(`miniflare process exited with code ${code}`);
261
+ logger.error(`Miniflare process exited with code ${code}`);
199
262
  }
200
263
  });
201
264
 
202
265
  local.current.on("error", (error: Error) => {
203
- console.error(`miniflare process failed to spawn`);
204
- console.error(error);
266
+ logger.error(`Miniflare process failed to spawn`);
267
+ logger.error(error);
205
268
  });
206
269
 
207
270
  removeSignalExitListener.current = onExit((_code, _signal) => {
208
- console.log("⎔ Shutting down local server.");
271
+ logger.log("⎔ Shutting down local server.");
209
272
  local.current?.kill();
210
273
  local.current = undefined;
211
274
  });
212
275
  }
213
276
 
214
277
  startLocalWorker().catch((err) => {
215
- console.error("local worker:", err);
278
+ logger.error("local worker:", err);
216
279
  });
217
280
 
218
281
  return () => {
282
+ abortController.abort();
219
283
  if (local.current) {
220
- console.log("⎔ Shutting down local server.");
284
+ logger.log("⎔ Shutting down local server.");
221
285
  local.current?.kill();
222
286
  local.current = undefined;
223
287
  removeSignalExitListener.current && removeSignalExitListener.current();
@@ -226,16 +290,23 @@ function useLocalWorker(props: {
226
290
  };
227
291
  }, [
228
292
  bundle,
293
+ workerName,
229
294
  format,
230
295
  port,
296
+ ip,
231
297
  bindings.durable_objects?.bindings,
232
298
  bindings.kv_namespaces,
233
299
  bindings.vars,
234
- props.enableLocalPersistence,
300
+ compatibilityDate,
301
+ compatibilityFlags,
302
+ localPersistencePath,
235
303
  assetPaths,
236
- props.public,
237
- props.rules,
304
+ publicDirectory,
305
+ rules,
238
306
  bindings.wasm_modules,
307
+ bindings.text_blobs,
308
+ bindings.data_blobs,
309
+ crons,
239
310
  ]);
240
311
  return { inspectorUrl };
241
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,17 +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;
22
+ ip: string;
21
23
  localProtocol: "https" | "http";
22
24
  inspectorPort: number;
23
- accountId: undefined | string;
24
- apiToken: undefined | string;
25
+ accountId: string | undefined;
26
+ apiToken: string | undefined;
25
27
  bindings: CfWorkerInit["bindings"];
26
- compatibilityDate: string | undefined;
27
- compatibilityFlags: undefined | string[];
28
- usageModel: undefined | "bundled" | "unbound";
28
+ compatibilityDate: string;
29
+ compatibilityFlags: string[] | undefined;
30
+ usageModel: "bundled" | "unbound" | undefined;
29
31
  env: string | undefined;
30
32
  legacyEnv: boolean | undefined;
31
33
  zone: { id: string; host: string } | undefined;
@@ -55,6 +57,7 @@ export function Remote(props: {
55
57
  publicRoot: props.public,
56
58
  localProtocol: props.localProtocol,
57
59
  localPort: props.port,
60
+ ip: props.ip,
58
61
  });
59
62
 
60
63
  useInspector({
@@ -73,11 +76,11 @@ export function useWorker(props: {
73
76
  accountId: string;
74
77
  apiToken: string;
75
78
  bindings: CfWorkerInit["bindings"];
76
- assetPaths: undefined | AssetPaths;
79
+ assetPaths: AssetPaths | undefined;
77
80
  port: number;
78
81
  compatibilityDate: string | undefined;
79
82
  compatibilityFlags: string[] | undefined;
80
- usageModel: undefined | "bundled" | "unbound";
83
+ usageModel: "bundled" | "unbound" | undefined;
81
84
  env: string | undefined;
82
85
  legacyEnv: boolean | undefined;
83
86
  zone: { id: string; host: string } | undefined;
@@ -104,6 +107,7 @@ export function useWorker(props: {
104
107
  const startedRef = useRef(false);
105
108
 
106
109
  useEffect(() => {
110
+ const abortController = new AbortController();
107
111
  async function start() {
108
112
  setToken(undefined); // reset token in case we're re-running
109
113
 
@@ -112,7 +116,7 @@ export function useWorker(props: {
112
116
  if (!startedRef.current) {
113
117
  startedRef.current = true;
114
118
  } else {
115
- console.log("⎔ Detected changes, restarted server.");
119
+ logger.log("⎔ Detected changes, restarted server.");
116
120
  }
117
121
 
118
122
  const assets = await syncAssets(
@@ -123,7 +127,8 @@ export function useWorker(props: {
123
127
  // concept of service environments for kv namespaces yet).
124
128
  name + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
125
129
  assetPaths,
126
- true
130
+ true,
131
+ false
127
132
  ); // TODO: cancellable?
128
133
 
129
134
  const content = await readFile(bundle.path, "utf-8");
@@ -171,15 +176,22 @@ export function useWorker(props: {
171
176
  accountId,
172
177
  apiToken,
173
178
  },
174
- { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone }
179
+ { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone },
180
+ abortController.signal
175
181
  )
176
182
  );
177
183
  }
178
184
  start().catch((err) => {
179
185
  // we want to log the error, but not end the process
180
186
  // since it could recover after the developer fixes whatever's wrong
181
- console.error("remote worker:", err);
187
+ if ((err as { code: string }).code !== "ABORT_ERR") {
188
+ logger.error("Error on remote worker:", err);
189
+ }
182
190
  });
191
+
192
+ return () => {
193
+ abortController.abort();
194
+ };
183
195
  }, [
184
196
  name,
185
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();