wrangler 0.0.0-fb43821 → 0.0.0-fe5dcfd

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 (41) hide show
  1. package/package.json +25 -10
  2. package/pages/functions/buildWorker.ts +62 -0
  3. package/pages/functions/filepath-routing.test.ts +39 -0
  4. package/pages/functions/filepath-routing.ts +228 -0
  5. package/pages/functions/identifiers.ts +78 -0
  6. package/pages/functions/routes.ts +158 -0
  7. package/pages/functions/template-worker.ts +136 -0
  8. package/src/__tests__/dev.test.tsx +67 -0
  9. package/src/__tests__/index.test.ts +42 -175
  10. package/src/__tests__/jest.setup.ts +22 -0
  11. package/src/__tests__/kv.test.ts +1199 -0
  12. package/src/__tests__/mock-cfetch.ts +173 -0
  13. package/src/__tests__/mock-dialogs.ts +65 -0
  14. package/src/__tests__/publish.test.ts +737 -0
  15. package/src/__tests__/run-in-tmp.ts +19 -0
  16. package/src/__tests__/run-wrangler.ts +32 -0
  17. package/src/__tests__/whoami.test.tsx +130 -0
  18. package/src/api/form_data.ts +66 -76
  19. package/src/api/preview.ts +4 -4
  20. package/src/api/worker.ts +38 -44
  21. package/src/cfetch/index.ts +110 -0
  22. package/src/cfetch/internal.ts +71 -0
  23. package/src/cli.ts +6 -7
  24. package/src/config.ts +634 -110
  25. package/src/dev.tsx +275 -272
  26. package/src/dialogs.tsx +2 -2
  27. package/src/index.tsx +504 -403
  28. package/src/inspect.ts +543 -0
  29. package/src/kv.tsx +109 -44
  30. package/src/pages.tsx +444 -222
  31. package/src/proxy.ts +302 -0
  32. package/src/publish.ts +123 -87
  33. package/src/sites.tsx +169 -41
  34. package/src/tail.tsx +5 -3
  35. package/src/user.tsx +64 -74
  36. package/src/whoami.tsx +69 -0
  37. package/wrangler-dist/cli.js +42792 -42619
  38. package/wrangler-dist/cli.js.map +3 -3
  39. package/src/__tests__/mock-cfetch.js +0 -42
  40. package/src/api/inspect.ts +0 -430
  41. package/src/cfetch.ts +0 -72
package/src/dev.tsx CHANGED
@@ -1,61 +1,75 @@
1
1
  import esbuild from "esbuild";
2
- import httpProxy from "http-proxy";
3
- import { readFile } from "fs/promises";
4
- import type { DirectoryResult } from "tmp-promise";
2
+ import assert from "node:assert";
3
+ import { spawn } from "node:child_process";
4
+ import { readFile } from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { Box, Text, useApp, useInput } from "ink";
8
+ import { watch } from "chokidar";
9
+ import clipboardy from "clipboardy";
10
+ import commandExists from "command-exists";
11
+ import { execa } from "execa";
12
+ import fetch from "node-fetch";
13
+ import open from "open";
14
+ import React, { useState, useEffect, useRef } from "react";
15
+ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
16
+ import onExit from "signal-exit";
5
17
  import tmp from "tmp-promise";
18
+ import type { DirectoryResult } from "tmp-promise";
19
+
6
20
  import type { CfPreviewToken } from "./api/preview";
7
- import { Box, Text, useInput } from "ink";
8
- import React, { useState, useEffect, useRef } from "react";
9
- import path from "path";
10
- import open from "open";
11
- import { DtInspector } from "./api/inspect";
12
- import type { CfModule, CfVariable } from "./api/worker";
21
+ import type { CfModule } from "./api/worker";
13
22
  import { createWorker } from "./api/worker";
14
23
  import type { CfWorkerInit } from "./api/worker";
15
- import { spawn } from "child_process";
16
- import onExit from "signal-exit";
24
+
25
+ import useInspector from "./inspect";
26
+ import makeModuleCollector from "./module-collection";
27
+ import { usePreviewServer } from "./proxy";
28
+ import type { AssetPaths } from "./sites";
17
29
  import { syncAssets } from "./sites";
18
- import clipboardy from "clipboardy";
19
- import http from "node:http";
20
- import serveStatic from "serve-static";
21
- import commandExists from "command-exists";
22
- import assert from "assert";
23
30
  import { getAPIToken } from "./user";
24
- import fetch from "node-fetch";
25
- import makeModuleCollector from "./module-collection";
26
- import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
27
31
 
28
- type CfScriptFormat = void | "modules" | "service-worker";
32
+ type CfScriptFormat = undefined | "modules" | "service-worker";
29
33
 
30
- type Props = {
34
+ export type DevProps = {
31
35
  name?: string;
32
36
  entry: string;
33
37
  port?: number;
34
38
  format: CfScriptFormat;
35
- accountId: void | string;
39
+ accountId: undefined | string;
36
40
  initialMode: "local" | "remote";
37
- jsxFactory: void | string;
38
- jsxFragment: void | string;
39
- variables: { [name: string]: CfVariable };
40
- public: void | string;
41
- site: void | string;
42
- compatibilityDate: void | string;
43
- compatibilityFlags: void | string[];
44
- usageModel: void | "bundled" | "unbound";
41
+ jsxFactory: undefined | string;
42
+ jsxFragment: undefined | string;
43
+ bindings: CfWorkerInit["bindings"];
44
+ public: undefined | string;
45
+ assetPaths: undefined | AssetPaths;
46
+ compatibilityDate: undefined | string;
47
+ compatibilityFlags: undefined | string[];
48
+ usageModel: undefined | "bundled" | "unbound";
49
+ buildCommand: {
50
+ command?: undefined | string;
51
+ cwd?: undefined | string;
52
+ watch_dir?: undefined | string;
53
+ };
45
54
  };
46
55
 
47
- function Dev(props: Props): JSX.Element {
56
+ function Dev(props: DevProps): JSX.Element {
48
57
  if (props.public && props.format === "service-worker") {
49
58
  throw new Error(
50
59
  "You cannot use the service worker format with a `public` directory."
51
60
  );
52
61
  }
53
- const port = props.port || 8787;
62
+ const port = props.port ?? 8787;
54
63
  const apiToken = getAPIToken();
55
64
  const directory = useTmpDir();
56
65
 
66
+ // if there isn't a build command, we just return the entry immediately
67
+ // ideally there would be a conditional here, but the rules of hooks
68
+ // kinda forbid that, so we thread the entry through useCustomBuild
69
+ const entry = useCustomBuild(props.entry, props.buildCommand);
70
+
57
71
  const bundle = useEsbuild({
58
- entry: props.entry,
72
+ entry,
59
73
  destination: directory,
60
74
  staticRoot: props.public,
61
75
  jsxFactory: props.jsxFactory,
@@ -67,9 +81,6 @@ function Dev(props: Props): JSX.Element {
67
81
  );
68
82
  }
69
83
 
70
- // @ts-expect-error whack
71
- useDevtoolsRefresh(bundle?.id ?? 0);
72
-
73
84
  const toggles = useHotkeys(
74
85
  {
75
86
  local: props.initialMode === "local",
@@ -87,10 +98,10 @@ function Dev(props: Props): JSX.Element {
87
98
  name={props.name}
88
99
  bundle={bundle}
89
100
  format={props.format}
90
- variables={props.variables}
91
- site={props.site}
101
+ bindings={props.bindings}
102
+ site={props.assetPaths}
92
103
  public={props.public}
93
- port={props.port}
104
+ port={port}
94
105
  />
95
106
  ) : (
96
107
  <Remote
@@ -99,10 +110,10 @@ function Dev(props: Props): JSX.Element {
99
110
  format={props.format}
100
111
  accountId={props.accountId}
101
112
  apiToken={apiToken}
102
- variables={props.variables}
103
- site={props.site}
113
+ bindings={props.bindings}
114
+ assetPaths={props.assetPaths}
104
115
  public={props.public}
105
- port={props.port}
116
+ port={port}
106
117
  compatibilityDate={props.compatibilityDate}
107
118
  compatibilityFlags={props.compatibilityFlags}
108
119
  usageModel={props.usageModel}
@@ -121,104 +132,82 @@ function Dev(props: Props): JSX.Element {
121
132
  );
122
133
  }
123
134
 
124
- function useDevtoolsRefresh(bundleId: number) {
125
- // TODO: this is a hack while we figure out
126
- // a better cleaner solution to get devtools to reconnect
127
- // without having to do a full refresh
128
- const ref = useRef();
129
- // @ts-expect-error whack
130
- ref.current = bundleId;
131
-
132
- useEffect(() => {
133
- const server = http.createServer((req, res) => {
134
- res.setHeader("Access-Control-Allow-Origin", "*");
135
- res.setHeader("Access-Control-Request-Method", "*");
136
- res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET");
137
- res.setHeader("Access-Control-Allow-Headers", "*");
138
- if (req.method === "OPTIONS") {
139
- res.writeHead(200);
140
- res.end();
141
- return;
142
- }
143
- res.writeHead(200, { "Content-Type": "application/json" });
144
- res.end(JSON.stringify({ value: ref.current }));
145
- });
146
-
147
- server.listen(3142);
148
- return () => {
149
- server.close();
150
- };
151
- }, []);
152
- }
153
-
154
135
  function Remote(props: {
155
- name: void | string;
156
- bundle: EsbuildBundle | void;
136
+ name: undefined | string;
137
+ bundle: EsbuildBundle | undefined;
157
138
  format: CfScriptFormat;
158
- public: void | string;
159
- site: void | string;
139
+ public: undefined | string;
140
+ assetPaths: undefined | AssetPaths;
160
141
  port: number;
161
- accountId: void | string;
162
- apiToken: void | string;
163
- variables: { [name: string]: CfVariable };
164
- compatibilityDate: string | void;
165
- compatibilityFlags: void | string[];
166
- usageModel: void | "bundled" | "unbound";
142
+ accountId: undefined | string;
143
+ apiToken: undefined | string;
144
+ bindings: CfWorkerInit["bindings"];
145
+ compatibilityDate: string | undefined;
146
+ compatibilityFlags: undefined | string[];
147
+ usageModel: undefined | "bundled" | "unbound";
167
148
  }) {
168
149
  assert(props.accountId, "accountId is required");
169
150
  assert(props.apiToken, "apiToken is required");
170
- const token = useWorker({
151
+ const previewToken = useWorker({
171
152
  name: props.name,
172
153
  bundle: props.bundle,
173
154
  format: props.format,
174
155
  modules: props.bundle ? props.bundle.modules : [],
175
156
  accountId: props.accountId,
176
157
  apiToken: props.apiToken,
177
- variables: props.variables,
178
- sitesFolder: props.site,
158
+ bindings: props.bindings,
159
+ assetPaths: props.assetPaths,
179
160
  port: props.port,
180
161
  compatibilityDate: props.compatibilityDate,
181
162
  compatibilityFlags: props.compatibilityFlags,
182
163
  usageModel: props.usageModel,
183
164
  });
184
165
 
185
- useProxy({ token, publicRoot: props.public, port: props.port });
166
+ usePreviewServer({
167
+ previewToken,
168
+ publicRoot: props.public,
169
+ port: props.port,
170
+ });
186
171
 
187
- useInspector(token ? token.inspectorUrl.href : undefined);
172
+ useInspector({
173
+ inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
174
+ port: 9229,
175
+ logToTerminal: true,
176
+ });
188
177
  return null;
189
178
  }
190
179
  function Local(props: {
191
- name: void | string;
192
- bundle: EsbuildBundle | void;
180
+ name: undefined | string;
181
+ bundle: EsbuildBundle | undefined;
193
182
  format: CfScriptFormat;
194
- variables: { [name: string]: CfVariable };
195
- public: void | string;
196
- site: void | string;
183
+ bindings: CfWorkerInit["bindings"];
184
+ public: undefined | string;
185
+ site: undefined | AssetPaths;
197
186
  port: number;
198
187
  }) {
199
188
  const { inspectorUrl } = useLocalWorker({
200
189
  name: props.name,
201
190
  bundle: props.bundle,
202
191
  format: props.format,
203
- variables: props.variables,
192
+ bindings: props.bindings,
204
193
  port: props.port,
205
194
  });
206
- useInspector(inspectorUrl);
195
+ useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
207
196
  return null;
208
197
  }
209
198
 
210
199
  function useLocalWorker(props: {
211
- name: void | string;
212
- bundle: EsbuildBundle | void;
200
+ name: undefined | string;
201
+ bundle: EsbuildBundle | undefined;
213
202
  format: CfScriptFormat;
214
- variables: { [name: string]: CfVariable };
203
+ bindings: CfWorkerInit["bindings"];
215
204
  port: number;
216
205
  }) {
217
206
  // TODO: pass vars via command line
218
- const { bundle, format, variables, port } = props;
207
+ const { bundle, format, bindings, port } = props;
219
208
  const local = useRef<ReturnType<typeof spawn>>();
220
209
  const removeSignalExitListener = useRef<() => void>();
221
- const [inspectorUrl, setInspectorUrl] = useState<string | void>();
210
+ const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
222
211
  useEffect(() => {
223
212
  async function startLocalWorker() {
224
213
  if (!bundle) return;
@@ -252,20 +241,17 @@ function useLocalWorker(props: {
252
241
  "--kv-persist",
253
242
  "--cache-persist",
254
243
  "--do-persist",
255
- ...Object.entries(variables)
256
- .map(([varKey, varVal]) => {
257
- if (typeof varVal === "string") {
258
- return `--binding ${varKey}=${varVal}`;
259
- } else if (
260
- "namespaceId" in varVal &&
261
- typeof varVal.namespaceId === "string"
262
- ) {
263
- return `--kv ${varKey}`;
264
- } else if ("class_name" in varVal) {
265
- return `--do ${varKey}=${varVal.class_name}`;
266
- }
267
- })
268
- .filter(Boolean),
244
+ ...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
245
+ return ["--binding", `${key}=${value}`];
246
+ }),
247
+ ...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
248
+ return ["--kv", binding];
249
+ }),
250
+ ...(bindings.durable_objects?.bindings || []).flatMap(
251
+ ({ name, class_name }) => {
252
+ return ["--do", `${name}=${class_name}`];
253
+ }
254
+ ),
269
255
  "--modules",
270
256
  format ||
271
257
  (bundle.type === "esm" ? "modules" : "service-worker") === "modules"
@@ -280,15 +266,15 @@ function useLocalWorker(props: {
280
266
  }
281
267
  });
282
268
 
283
- local.current.stdout.on("data", (_data: string) => {
284
- // console.log(`stdout: ${data}`);
269
+ local.current.stdout?.on("data", (data: Buffer) => {
270
+ console.log(`${data.toString()}`);
285
271
  });
286
272
 
287
- local.current.stderr.on("data", (data: string) => {
288
- // console.error(`stderr: ${data}`);
273
+ local.current.stderr?.on("data", (data: Buffer) => {
274
+ console.error(`${data.toString()}`);
289
275
  const matches =
290
276
  /Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
291
- data
277
+ data.toString()
292
278
  );
293
279
  if (matches) {
294
280
  setInspectorUrl(matches[1]);
@@ -326,11 +312,18 @@ function useLocalWorker(props: {
326
312
  removeSignalExitListener.current = undefined;
327
313
  }
328
314
  };
329
- }, [bundle, format, port]);
315
+ }, [
316
+ bundle,
317
+ format,
318
+ port,
319
+ bindings.durable_objects?.bindings,
320
+ bindings.kv_namespaces,
321
+ bindings.vars,
322
+ ]);
330
323
  return { inspectorUrl };
331
324
  }
332
325
 
333
- function useTmpDir(): string | void {
326
+ function useTmpDir(): string | undefined {
334
327
  const [directory, setDirectory] = useState<DirectoryResult>();
335
328
  const handleError = useErrorHandler();
336
329
  useEffect(() => {
@@ -361,6 +354,75 @@ function useTmpDir(): string | void {
361
354
  return directory?.path;
362
355
  }
363
356
 
357
+ function useCustomBuild(
358
+ expectedEntry: string,
359
+ props: {
360
+ command?: undefined | string;
361
+ cwd?: undefined | string;
362
+ watch_dir?: undefined | string;
363
+ }
364
+ ): undefined | string {
365
+ const [entry, setEntry] = useState<string | undefined>(
366
+ // if there's no build command, just return the expected entry
367
+ props.command || expectedEntry
368
+ );
369
+ const { command, cwd, watch_dir } = props;
370
+ useEffect(() => {
371
+ if (!command) return;
372
+ let cmd, interval;
373
+ console.log("running:", command);
374
+ const commandPieces = command.split(" ");
375
+ cmd = execa(commandPieces[0], commandPieces.slice(1), {
376
+ ...(cwd && { cwd }),
377
+ stderr: "inherit",
378
+ stdout: "inherit",
379
+ });
380
+ if (watch_dir) {
381
+ watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
382
+ "all",
383
+ (_event, _path) => {
384
+ console.log(`The file ${path} changed, restarting build...`);
385
+ cmd.kill();
386
+ cmd = execa(commandPieces[0], commandPieces.slice(1), {
387
+ ...(cwd && { cwd }),
388
+ stderr: "inherit",
389
+ stdout: "inherit",
390
+ });
391
+ }
392
+ );
393
+ }
394
+
395
+ // check every so often whether `expectedEntry` exists
396
+ // if it does, we're done
397
+ const startedAt = Date.now();
398
+ interval = setInterval(() => {
399
+ if (existsSync(expectedEntry)) {
400
+ clearInterval(interval);
401
+ setEntry(expectedEntry);
402
+ } else {
403
+ const elapsed = Date.now() - startedAt;
404
+ // timeout after 30 seconds of waiting
405
+ if (elapsed > 1000 * 60 * 30) {
406
+ console.error("⎔ Build timed out.");
407
+ clearInterval(interval);
408
+ cmd.kill();
409
+ }
410
+ }
411
+ }, 200);
412
+ // TODO: we could probably timeout here after a while
413
+
414
+ return () => {
415
+ if (cmd) {
416
+ cmd.kill();
417
+ cmd = undefined;
418
+ }
419
+ clearInterval(interval);
420
+ interval = undefined;
421
+ };
422
+ }, [command, cwd, expectedEntry, watch_dir]);
423
+ return entry;
424
+ }
425
+
364
426
  type EsbuildBundle = {
365
427
  id: number;
366
428
  path: string;
@@ -371,18 +433,18 @@ type EsbuildBundle = {
371
433
  };
372
434
 
373
435
  function useEsbuild(props: {
374
- entry: string;
375
- destination: string | void;
376
- staticRoot: void | string;
377
- jsxFactory: string | void;
378
- jsxFragment: string | void;
379
- }): EsbuildBundle | void {
436
+ entry: undefined | string;
437
+ destination: string | undefined;
438
+ staticRoot: undefined | string;
439
+ jsxFactory: string | undefined;
440
+ jsxFragment: string | undefined;
441
+ }): EsbuildBundle | undefined {
380
442
  const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
381
443
  const [bundle, setBundle] = useState<EsbuildBundle>();
382
444
  useEffect(() => {
383
- let result: esbuild.BuildResult;
445
+ let result: esbuild.BuildResult | undefined;
384
446
  async function build() {
385
- if (!destination) return;
447
+ if (!destination || !entry) return;
386
448
  const moduleCollector = makeModuleCollector();
387
449
  result = await esbuild.build({
388
450
  entryPoints: [entry],
@@ -406,22 +468,37 @@ function useEsbuild(props: {
406
468
  else {
407
469
  // nothing really changes here, so let's increment the id
408
470
  // to change the return object's identity
409
- setBundle((bundle) => ({ ...bundle, id: bundle.id + 1 }));
471
+ setBundle((previousBundle) => {
472
+ if (previousBundle === undefined) {
473
+ assert.fail(
474
+ "Rebuild triggered with no previous build available"
475
+ );
476
+ }
477
+ return { ...previousBundle, id: previousBundle.id + 1 };
478
+ });
410
479
  }
411
480
  },
412
481
  },
413
482
  });
414
483
 
415
- const chunks = Object.entries(result.metafile.outputs).find(
484
+ // result.metafile is defined because of the `metafile: true` option above.
485
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
486
+ const metafile = result.metafile!;
487
+ const outputEntry = Object.entries(metafile.outputs).find(
416
488
  ([_path, { entryPoint }]) => entryPoint === entry
417
489
  ); // assumedly only one entry point
418
490
 
491
+ if (outputEntry === undefined) {
492
+ throw new Error(
493
+ `Cannot find entry-point "${entry}" in generated bundle.`
494
+ );
495
+ }
419
496
  setBundle({
420
497
  id: 0,
421
498
  entry,
422
- path: chunks[0],
423
- type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
424
- exports: chunks[1].exports,
499
+ path: outputEntry[0],
500
+ type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs",
501
+ exports: outputEntry[1].exports,
425
502
  modules: moduleCollector.modules,
426
503
  });
427
504
  }
@@ -432,26 +509,26 @@ function useEsbuild(props: {
432
509
  // so this is a no-op error handler
433
510
  });
434
511
  return () => {
435
- result?.stop();
512
+ result?.stop?.();
436
513
  };
437
514
  }, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
438
515
  return bundle;
439
516
  }
440
517
 
441
518
  function useWorker(props: {
442
- name: void | string;
443
- bundle: EsbuildBundle | void;
519
+ name: undefined | string;
520
+ bundle: EsbuildBundle | undefined;
444
521
  format: CfScriptFormat;
445
522
  modules: CfModule[];
446
523
  accountId: string;
447
524
  apiToken: string;
448
- variables: { [name: string]: CfVariable };
449
- sitesFolder: void | string;
525
+ bindings: CfWorkerInit["bindings"];
526
+ assetPaths: undefined | AssetPaths;
450
527
  port: number;
451
- compatibilityDate: string | void;
452
- compatibilityFlags: string[] | void;
453
- usageModel: void | "bundled" | "unbound";
454
- }): CfPreviewToken | void {
528
+ compatibilityDate: string | undefined;
529
+ compatibilityFlags: string[] | undefined;
530
+ usageModel: undefined | "bundled" | "unbound";
531
+ }): CfPreviewToken | undefined {
455
532
  const {
456
533
  name,
457
534
  bundle,
@@ -459,16 +536,24 @@ function useWorker(props: {
459
536
  modules,
460
537
  accountId,
461
538
  apiToken,
462
- variables,
463
- sitesFolder,
539
+ bindings,
540
+ assetPaths,
464
541
  compatibilityDate,
465
542
  compatibilityFlags,
466
543
  usageModel,
467
544
  port,
468
545
  } = props;
469
- const [token, setToken] = useState<CfPreviewToken>();
546
+ const [token, setToken] = useState<CfPreviewToken | undefined>();
547
+
548
+ // This is the most reliable way to detect whether
549
+ // something's "happened" in our system; We make a ref and
550
+ // mark it once we log our initial message. Refs are vars!
551
+ const startedRef = useRef(false);
552
+
470
553
  useEffect(() => {
471
554
  async function start() {
555
+ setToken(undefined); // reset token in case we're re-running
556
+
472
557
  if (!bundle) return;
473
558
  if (format === "modules" && bundle.type === "commonjs") {
474
559
  console.error("⎔ Cannot use modules with a commonjs bundle.");
@@ -481,24 +566,18 @@ function useWorker(props: {
481
566
  return;
482
567
  }
483
568
 
484
- if (token) {
485
- console.log("⎔ Detected changes, restarting server...");
569
+ if (!startedRef.current) {
570
+ startedRef.current = true;
486
571
  } else {
487
- console.log("⎔ Starting server...");
572
+ console.log("⎔ Detected changes, restarting server...");
488
573
  }
489
574
 
490
- const assets = sitesFolder
491
- ? await syncAssets(
492
- accountId,
493
- path.basename(bundle.path),
494
- sitesFolder,
495
- true,
496
- undefined // TODO: env
497
- )
498
- : {
499
- manifest: undefined,
500
- namespace: undefined,
501
- }; // TODO: cancellable?
575
+ const assets = await syncAssets(
576
+ accountId,
577
+ path.basename(bundle.path),
578
+ assetPaths,
579
+ true
580
+ ); // TODO: cancellable?
502
581
 
503
582
  const content = await readFile(bundle.path, "utf-8");
504
583
  const init: CfWorkerInit = {
@@ -508,19 +587,23 @@ function useWorker(props: {
508
587
  type: format || bundle.type === "esm" ? "esm" : "commonjs",
509
588
  content,
510
589
  },
511
- modules: assets.manifest
512
- ? modules.concat({
513
- name: "__STATIC_CONTENT_MANIFEST",
514
- content: JSON.stringify(assets.manifest),
515
- type: "text",
516
- })
517
- : modules,
518
- variables: assets.namespace
519
- ? {
520
- ...variables,
521
- __STATIC_CONTENT: { namespaceId: assets.namespace },
522
- }
523
- : variables,
590
+ modules: modules.concat(
591
+ assets.manifest
592
+ ? {
593
+ name: "__STATIC_CONTENT_MANIFEST",
594
+ content: JSON.stringify(assets.manifest),
595
+ type: "text",
596
+ }
597
+ : []
598
+ ),
599
+ bindings: {
600
+ ...bindings,
601
+ kv_namespaces: (bindings.kv_namespaces || []).concat(
602
+ assets.namespace
603
+ ? { binding: "__STATIC_CONTENT", id: assets.namespace }
604
+ : []
605
+ ),
606
+ },
524
607
  migrations: undefined, // no migrations in dev
525
608
  compatibility_date: compatibilityDate,
526
609
  compatibility_flags: compatibilityFlags,
@@ -532,7 +615,6 @@ function useWorker(props: {
532
615
  apiToken,
533
616
  })
534
617
  );
535
- console.log(`⬣ Listening at http://localhost:${port}`);
536
618
  }
537
619
  start().catch((err) => {
538
620
  // we want to log the error, but not end the process
@@ -546,83 +628,16 @@ function useWorker(props: {
546
628
  accountId,
547
629
  apiToken,
548
630
  port,
549
- sitesFolder,
631
+ assetPaths,
550
632
  compatibilityDate,
551
633
  compatibilityFlags,
552
634
  usageModel,
635
+ bindings,
636
+ modules,
553
637
  ]);
554
638
  return token;
555
639
  }
556
640
 
557
- function useProxy({
558
- token,
559
- publicRoot,
560
- port,
561
- }: {
562
- token: CfPreviewToken | void;
563
- publicRoot: void | string;
564
- port: number;
565
- }) {
566
- useEffect(() => {
567
- if (!token) return;
568
- const proxy = httpProxy.createProxyServer({
569
- secure: false,
570
- changeOrigin: true,
571
- headers: {
572
- "cf-workers-preview-token": token.value,
573
- },
574
- target: `https://${token.host}`,
575
- // TODO: log websockets too? validate durables, etc
576
- });
577
-
578
- const servePublic =
579
- publicRoot &&
580
- serveStatic(publicRoot, {
581
- cacheControl: false,
582
- });
583
- const server = http
584
- .createServer((req, res) => {
585
- if (publicRoot) {
586
- servePublic(req, res, () => {
587
- proxy.web(req, res);
588
- });
589
- } else {
590
- proxy.web(req, res);
591
- }
592
- })
593
- .listen(port); // TODO: custom port
594
-
595
- proxy.on("proxyRes", function (proxyRes, req, res) {
596
- // log all requests
597
- console.log(
598
- new Date().toLocaleTimeString(),
599
- req.method,
600
- req.url,
601
- res.statusCode // TODO add a status message like Ok etc?
602
- );
603
- });
604
- // TODO: log errors?
605
-
606
- return () => {
607
- proxy.close();
608
- server.close();
609
- };
610
- }, [token, publicRoot, port]);
611
- }
612
-
613
- function useInspector(inspectorUrl: string | void) {
614
- useEffect(() => {
615
- if (!inspectorUrl) return;
616
-
617
- const inspector = new DtInspector(inspectorUrl);
618
- const abortController = inspector.proxyTo(9229);
619
- return () => {
620
- inspector.close();
621
- abortController.abort();
622
- };
623
- }, [inspectorUrl]);
624
- }
625
-
626
641
  function sleep(period: number) {
627
642
  return new Promise((resolve) => setTimeout(resolve, period));
628
643
  }
@@ -630,7 +645,7 @@ const SLEEP_DURATION = 2000;
630
645
  // really need a first class api for this
631
646
  const hostNameRegex = /userHostname="(.*)"/g;
632
647
  async function findTunnelHostname() {
633
- let hostName: string;
648
+ let hostName: string | undefined;
634
649
  while (!hostName) {
635
650
  try {
636
651
  const resp = await fetch("http://localhost:8789/metrics");
@@ -718,31 +733,24 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
718
733
  ) => {
719
734
  switch (input) {
720
735
  case "b": // open browser
721
- await open(
722
- `http://localhost:${port}/`
723
- // {
724
- // app: {
725
- // name: open.apps.chrome, // TODO: fallback on other browsers
726
- // },
727
- // }
728
- );
736
+ await open(`http://localhost:${port}/`);
729
737
  break;
730
738
  case "d": // toggle inspector
731
739
  await open(
732
740
  `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws`
733
- // {
734
- // app: {
735
- // name: open.apps.chrome,
736
- // // todo - add firefox and edge fallbacks
737
- // },
738
- // }
739
741
  );
740
742
  break;
741
743
  case "s": // toggle tunnel
742
- setToggles((toggles) => ({ ...toggles, tunnel: !toggles.tunnel }));
744
+ setToggles((previousToggles) => ({
745
+ ...previousToggles,
746
+ tunnel: !previousToggles.tunnel,
747
+ }));
743
748
  break;
744
749
  case "l": // toggle local
745
- setToggles((toggles) => ({ ...toggles, local: !toggles.local }));
750
+ setToggles((previousToggles) => ({
751
+ ...previousToggles,
752
+ local: !previousToggles.local,
753
+ }));
746
754
  break;
747
755
  case "q": // shut down
748
756
  case "x": // shut down
@@ -757,19 +765,14 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
757
765
  return toggles;
758
766
  }
759
767
 
760
- function ErrorFallback(props: {
761
- error: Error;
762
- resetErrorBoundary: () => void;
763
- }) {
764
- useEffect(() => {
765
- console.error(props.error);
766
- process.exit(1);
767
- });
768
+ function ErrorFallback(props: { error: Error }) {
769
+ const { exit } = useApp();
770
+ useEffect(() => exit(new Error()));
768
771
  return (
769
- <Box>
772
+ <>
770
773
  <Text>Something went wrong:</Text>
771
- <Text>{props.error.message}</Text>
772
- </Box>
774
+ <Text>{props.error.stack}</Text>
775
+ </>
773
776
  );
774
777
  }
775
778