wrangler 0.0.0-e9a1820 → 0.0.0-e9ab55a

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.
package/src/dev.tsx CHANGED
@@ -1,49 +1,50 @@
1
1
  import esbuild from "esbuild";
2
- import { readFile } from "fs/promises";
3
- import { existsSync } from "fs";
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
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";
17
28
  import { syncAssets } from "./sites";
18
- import clipboardy from "clipboardy";
19
- import http from "node:http";
20
- import commandExists from "command-exists";
21
- import assert from "assert";
22
29
  import { getAPIToken } from "./user";
23
- import fetch from "node-fetch";
24
- import makeModuleCollector from "./module-collection";
25
- import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
26
- import { createHttpProxy } from "./proxy";
27
- import { execa } from "execa";
28
- import { watch } from "chokidar";
29
30
 
30
- type CfScriptFormat = void | "modules" | "service-worker";
31
+ type CfScriptFormat = undefined | "modules" | "service-worker";
31
32
 
32
- type Props = {
33
+ export type DevProps = {
33
34
  name?: string;
34
35
  entry: string;
35
36
  port?: number;
36
37
  format: CfScriptFormat;
37
- accountId: void | string;
38
+ accountId: undefined | string;
38
39
  initialMode: "local" | "remote";
39
- jsxFactory: void | string;
40
- jsxFragment: void | string;
40
+ jsxFactory: undefined | string;
41
+ jsxFragment: undefined | string;
41
42
  bindings: CfWorkerInit["bindings"];
42
- public: void | string;
43
- site: void | string;
44
- compatibilityDate: void | string;
45
- compatibilityFlags: void | string[];
46
- usageModel: void | "bundled" | "unbound";
43
+ public: undefined | string;
44
+ site: undefined | string;
45
+ compatibilityDate: undefined | string;
46
+ compatibilityFlags: undefined | string[];
47
+ usageModel: undefined | "bundled" | "unbound";
47
48
  buildCommand: {
48
49
  command?: undefined | string;
49
50
  cwd?: undefined | string;
@@ -51,13 +52,13 @@ type Props = {
51
52
  };
52
53
  };
53
54
 
54
- function Dev(props: Props): JSX.Element {
55
+ function Dev(props: DevProps): JSX.Element {
55
56
  if (props.public && props.format === "service-worker") {
56
57
  throw new Error(
57
58
  "You cannot use the service worker format with a `public` directory."
58
59
  );
59
60
  }
60
- const port = props.port || 8787;
61
+ const port = props.port ?? 8787;
61
62
  const apiToken = getAPIToken();
62
63
  const directory = useTmpDir();
63
64
 
@@ -79,9 +80,6 @@ function Dev(props: Props): JSX.Element {
79
80
  );
80
81
  }
81
82
 
82
- // @ts-expect-error whack
83
- useDevtoolsRefresh(bundle?.id ?? 0);
84
-
85
83
  const toggles = useHotkeys(
86
84
  {
87
85
  local: props.initialMode === "local",
@@ -102,7 +100,7 @@ function Dev(props: Props): JSX.Element {
102
100
  bindings={props.bindings}
103
101
  site={props.site}
104
102
  public={props.public}
105
- port={props.port}
103
+ port={port}
106
104
  />
107
105
  ) : (
108
106
  <Remote
@@ -114,7 +112,7 @@ function Dev(props: Props): JSX.Element {
114
112
  bindings={props.bindings}
115
113
  site={props.site}
116
114
  public={props.public}
117
- port={props.port}
115
+ port={port}
118
116
  compatibilityDate={props.compatibilityDate}
119
117
  compatibilityFlags={props.compatibilityFlags}
120
118
  usageModel={props.usageModel}
@@ -133,53 +131,23 @@ function Dev(props: Props): JSX.Element {
133
131
  );
134
132
  }
135
133
 
136
- function useDevtoolsRefresh(bundleId: number) {
137
- // TODO: this is a hack while we figure out
138
- // a better cleaner solution to get devtools to reconnect
139
- // without having to do a full refresh
140
- const ref = useRef();
141
- // @ts-expect-error whack
142
- ref.current = bundleId;
143
-
144
- useEffect(() => {
145
- const server = http.createServer((req, res) => {
146
- res.setHeader("Access-Control-Allow-Origin", "*");
147
- res.setHeader("Access-Control-Request-Method", "*");
148
- res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET");
149
- res.setHeader("Access-Control-Allow-Headers", "*");
150
- if (req.method === "OPTIONS") {
151
- res.writeHead(200);
152
- res.end();
153
- return;
154
- }
155
- res.writeHead(200, { "Content-Type": "application/json" });
156
- res.end(JSON.stringify({ value: ref.current }));
157
- });
158
-
159
- server.listen(3142);
160
- return () => {
161
- server.close();
162
- };
163
- }, []);
164
- }
165
-
166
134
  function Remote(props: {
167
- name: void | string;
168
- bundle: EsbuildBundle | void;
135
+ name: undefined | string;
136
+ bundle: EsbuildBundle | undefined;
169
137
  format: CfScriptFormat;
170
- public: void | string;
171
- site: void | string;
138
+ public: undefined | string;
139
+ site: undefined | string;
172
140
  port: number;
173
- accountId: void | string;
174
- apiToken: void | string;
141
+ accountId: undefined | string;
142
+ apiToken: undefined | string;
175
143
  bindings: CfWorkerInit["bindings"];
176
- compatibilityDate: string | void;
177
- compatibilityFlags: void | string[];
178
- usageModel: void | "bundled" | "unbound";
144
+ compatibilityDate: string | undefined;
145
+ compatibilityFlags: undefined | string[];
146
+ usageModel: undefined | "bundled" | "unbound";
179
147
  }) {
180
148
  assert(props.accountId, "accountId is required");
181
149
  assert(props.apiToken, "apiToken is required");
182
- const token = useWorker({
150
+ const previewToken = useWorker({
183
151
  name: props.name,
184
152
  bundle: props.bundle,
185
153
  format: props.format,
@@ -194,18 +162,26 @@ function Remote(props: {
194
162
  usageModel: props.usageModel,
195
163
  });
196
164
 
197
- useProxy({ token, publicRoot: props.public, port: props.port });
165
+ usePreviewServer({
166
+ previewToken,
167
+ publicRoot: props.public,
168
+ port: props.port,
169
+ });
198
170
 
199
- useInspector(token ? token.inspectorUrl.href : undefined);
171
+ useInspector({
172
+ inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
173
+ port: 9229,
174
+ logToTerminal: true,
175
+ });
200
176
  return null;
201
177
  }
202
178
  function Local(props: {
203
- name: void | string;
204
- bundle: EsbuildBundle | void;
179
+ name: undefined | string;
180
+ bundle: EsbuildBundle | undefined;
205
181
  format: CfScriptFormat;
206
182
  bindings: CfWorkerInit["bindings"];
207
- public: void | string;
208
- site: void | string;
183
+ public: undefined | string;
184
+ site: undefined | string;
209
185
  port: number;
210
186
  }) {
211
187
  const { inspectorUrl } = useLocalWorker({
@@ -215,13 +191,13 @@ function Local(props: {
215
191
  bindings: props.bindings,
216
192
  port: props.port,
217
193
  });
218
- useInspector(inspectorUrl);
194
+ useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
219
195
  return null;
220
196
  }
221
197
 
222
198
  function useLocalWorker(props: {
223
- name: void | string;
224
- bundle: EsbuildBundle | void;
199
+ name: undefined | string;
200
+ bundle: EsbuildBundle | undefined;
225
201
  format: CfScriptFormat;
226
202
  bindings: CfWorkerInit["bindings"];
227
203
  port: number;
@@ -230,7 +206,7 @@ function useLocalWorker(props: {
230
206
  const { bundle, format, bindings, port } = props;
231
207
  const local = useRef<ReturnType<typeof spawn>>();
232
208
  const removeSignalExitListener = useRef<() => void>();
233
- const [inspectorUrl, setInspectorUrl] = useState<string | void>();
209
+ const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
234
210
  useEffect(() => {
235
211
  async function startLocalWorker() {
236
212
  if (!bundle) return;
@@ -289,11 +265,11 @@ function useLocalWorker(props: {
289
265
  }
290
266
  });
291
267
 
292
- local.current.stdout.on("data", (data: Buffer) => {
268
+ local.current.stdout?.on("data", (data: Buffer) => {
293
269
  console.log(`${data.toString()}`);
294
270
  });
295
271
 
296
- local.current.stderr.on("data", (data: Buffer) => {
272
+ local.current.stderr?.on("data", (data: Buffer) => {
297
273
  console.error(`${data.toString()}`);
298
274
  const matches =
299
275
  /Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
@@ -346,7 +322,7 @@ function useLocalWorker(props: {
346
322
  return { inspectorUrl };
347
323
  }
348
324
 
349
- function useTmpDir(): string | void {
325
+ function useTmpDir(): string | undefined {
350
326
  const [directory, setDirectory] = useState<DirectoryResult>();
351
327
  const handleError = useErrorHandler();
352
328
  useEffect(() => {
@@ -384,10 +360,10 @@ function useCustomBuild(
384
360
  cwd?: undefined | string;
385
361
  watch_dir?: undefined | string;
386
362
  }
387
- ): void | string {
388
- const [entry, setEntry] = useState<string | void>(
363
+ ): undefined | string {
364
+ const [entry, setEntry] = useState<string | undefined>(
389
365
  // if there's no build command, just return the expected entry
390
- props.command ? null : expectedEntry
366
+ props.command || expectedEntry
391
367
  );
392
368
  const { command, cwd, watch_dir } = props;
393
369
  useEffect(() => {
@@ -456,16 +432,16 @@ type EsbuildBundle = {
456
432
  };
457
433
 
458
434
  function useEsbuild(props: {
459
- entry: void | string;
460
- destination: string | void;
461
- staticRoot: void | string;
462
- jsxFactory: string | void;
463
- jsxFragment: string | void;
464
- }): EsbuildBundle | void {
435
+ entry: undefined | string;
436
+ destination: string | undefined;
437
+ staticRoot: undefined | string;
438
+ jsxFactory: string | undefined;
439
+ jsxFragment: string | undefined;
440
+ }): EsbuildBundle | undefined {
465
441
  const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
466
442
  const [bundle, setBundle] = useState<EsbuildBundle>();
467
443
  useEffect(() => {
468
- let result: esbuild.BuildResult;
444
+ let result: esbuild.BuildResult | undefined;
469
445
  async function build() {
470
446
  if (!destination || !entry) return;
471
447
  const moduleCollector = makeModuleCollector();
@@ -491,25 +467,37 @@ function useEsbuild(props: {
491
467
  else {
492
468
  // nothing really changes here, so let's increment the id
493
469
  // to change the return object's identity
494
- setBundle((previousBundle) => ({
495
- ...previousBundle,
496
- id: previousBundle.id + 1,
497
- }));
470
+ setBundle((previousBundle) => {
471
+ if (previousBundle === undefined) {
472
+ assert.fail(
473
+ "Rebuild triggered with no previous build available"
474
+ );
475
+ }
476
+ return { ...previousBundle, id: previousBundle.id + 1 };
477
+ });
498
478
  }
499
479
  },
500
480
  },
501
481
  });
502
482
 
503
- const chunks = Object.entries(result.metafile.outputs).find(
483
+ // result.metafile is defined because of the `metafile: true` option above.
484
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
485
+ const metafile = result.metafile!;
486
+ const outputEntry = Object.entries(metafile.outputs).find(
504
487
  ([_path, { entryPoint }]) => entryPoint === entry
505
488
  ); // assumedly only one entry point
506
489
 
490
+ if (outputEntry === undefined) {
491
+ throw new Error(
492
+ `Cannot find entry-point "${entry}" in generated bundle.`
493
+ );
494
+ }
507
495
  setBundle({
508
496
  id: 0,
509
497
  entry,
510
- path: chunks[0],
511
- type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
512
- exports: chunks[1].exports,
498
+ path: outputEntry[0],
499
+ type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs",
500
+ exports: outputEntry[1].exports,
513
501
  modules: moduleCollector.modules,
514
502
  });
515
503
  }
@@ -520,26 +508,26 @@ function useEsbuild(props: {
520
508
  // so this is a no-op error handler
521
509
  });
522
510
  return () => {
523
- result?.stop();
511
+ result?.stop?.();
524
512
  };
525
513
  }, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
526
514
  return bundle;
527
515
  }
528
516
 
529
517
  function useWorker(props: {
530
- name: void | string;
531
- bundle: EsbuildBundle | void;
518
+ name: undefined | string;
519
+ bundle: EsbuildBundle | undefined;
532
520
  format: CfScriptFormat;
533
521
  modules: CfModule[];
534
522
  accountId: string;
535
523
  apiToken: string;
536
524
  bindings: CfWorkerInit["bindings"];
537
- sitesFolder: void | string;
525
+ sitesFolder: undefined | string;
538
526
  port: number;
539
- compatibilityDate: string | void;
540
- compatibilityFlags: string[] | void;
541
- usageModel: void | "bundled" | "unbound";
542
- }): CfPreviewToken | void {
527
+ compatibilityDate: string | undefined;
528
+ compatibilityFlags: string[] | undefined;
529
+ usageModel: undefined | "bundled" | "unbound";
530
+ }): CfPreviewToken | undefined {
543
531
  const {
544
532
  name,
545
533
  bundle,
@@ -554,7 +542,7 @@ function useWorker(props: {
554
542
  usageModel,
555
543
  port,
556
544
  } = props;
557
- const [token, setToken] = useState<CfPreviewToken>();
545
+ const [token, setToken] = useState<CfPreviewToken | undefined>();
558
546
 
559
547
  // This is the most reliable way to detect whether
560
548
  // something's "happened" in our system; We make a ref and
@@ -563,6 +551,8 @@ function useWorker(props: {
563
551
 
564
552
  useEffect(() => {
565
553
  async function start() {
554
+ setToken(undefined); // reset token in case we're re-running
555
+
566
556
  if (!bundle) return;
567
557
  if (format === "modules" && bundle.type === "commonjs") {
568
558
  console.error("⎔ Cannot use modules with a commonjs bundle.");
@@ -576,7 +566,6 @@ function useWorker(props: {
576
566
  }
577
567
 
578
568
  if (!startedRef.current) {
579
- console.log("⎔ Starting server...");
580
569
  startedRef.current = true;
581
570
  } else {
582
571
  console.log("⎔ Detected changes, restarting server...");
@@ -654,85 +643,6 @@ function useWorker(props: {
654
643
  return token;
655
644
  }
656
645
 
657
- function useProxy({
658
- token,
659
- publicRoot,
660
- port,
661
- }: {
662
- token: CfPreviewToken | void;
663
- publicRoot: void | string;
664
- port: number;
665
- }) {
666
- useEffect(() => {
667
- if (!token) return;
668
- // TODO(soon): since headers are added in callbacks, the server
669
- // does not need to restart when changes are made.
670
- const host = token.host;
671
- const proxy = createHttpProxy({
672
- host,
673
- assetPath: typeof publicRoot === "string" ? publicRoot : null,
674
- onRequest: (headers) => {
675
- headers["cf-workers-preview-token"] = token.value;
676
- },
677
- onResponse: (headers) => {
678
- for (const [name, value] of Object.entries(headers)) {
679
- // Rewrite the remote host to the local host.
680
- if (typeof value === "string" && value.includes(host)) {
681
- headers[name] = value
682
- .replaceAll(`https://${host}`, `http://localhost:${port}`)
683
- .replaceAll(host, `localhost:${port}`);
684
- }
685
- }
686
- },
687
- });
688
-
689
- console.log(`⬣ Listening at http://localhost:${port}`);
690
-
691
- const server = proxy.listen(port);
692
-
693
- // TODO(soon): refactor logging format into its own function
694
- proxy.on("request", function (req, res) {
695
- // log all requests
696
- console.log(
697
- new Date().toLocaleTimeString(),
698
- req.method,
699
- req.url,
700
- res.statusCode
701
- );
702
- });
703
- proxy.on("upgrade", (req) => {
704
- console.log(
705
- new Date().toLocaleTimeString(),
706
- req.method,
707
- req.url,
708
- 101,
709
- "(WebSocket)"
710
- );
711
- });
712
- proxy.on("error", (err) => {
713
- console.error(new Date().toLocaleTimeString(), err);
714
- });
715
-
716
- return () => {
717
- proxy.close();
718
- server.close();
719
- };
720
- }, [token, publicRoot, port]);
721
- }
722
-
723
- function useInspector(inspectorUrl: string | void) {
724
- useEffect(() => {
725
- if (!inspectorUrl) return;
726
-
727
- const inspector = new DtInspector(inspectorUrl);
728
- const abortController = inspector.proxyTo(9229);
729
- return () => {
730
- inspector.close();
731
- abortController.abort();
732
- };
733
- }, [inspectorUrl]);
734
- }
735
-
736
646
  function sleep(period: number) {
737
647
  return new Promise((resolve) => setTimeout(resolve, period));
738
648
  }
@@ -740,7 +650,7 @@ const SLEEP_DURATION = 2000;
740
650
  // really need a first class api for this
741
651
  const hostNameRegex = /userHostname="(.*)"/g;
742
652
  async function findTunnelHostname() {
743
- let hostName: string;
653
+ let hostName: string | undefined;
744
654
  while (!hostName) {
745
655
  try {
746
656
  const resp = await fetch("http://localhost:8789/metrics");
@@ -860,19 +770,14 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
860
770
  return toggles;
861
771
  }
862
772
 
863
- function ErrorFallback(props: {
864
- error: Error;
865
- resetErrorBoundary: () => void;
866
- }) {
867
- useEffect(() => {
868
- console.error(props.error);
869
- process.exit(1);
870
- });
773
+ function ErrorFallback(props: { error: Error }) {
774
+ const { exit } = useApp();
775
+ useEffect(() => exit(new Error()));
871
776
  return (
872
- <Box>
777
+ <>
873
778
  <Text>Something went wrong:</Text>
874
- <Text>{props.error.message}</Text>
875
- </Box>
779
+ <Text>{props.error.stack}</Text>
780
+ </>
876
781
  );
877
782
  }
878
783