wrangler 0.0.0-e1c615f → 0.0.0-e5d3690

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,51 @@
1
1
  import esbuild from "esbuild";
2
- import { readFile } from "fs/promises";
3
- import { existsSync } from "fs";
4
- import type { DirectoryResult } from "tmp-promise";
5
- import tmp from "tmp-promise";
6
- import type { CfPreviewToken } from "./api/preview";
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
7
  import { Box, Text, useApp, useInput } from "ink";
8
- import React, { useState, useEffect, useRef } from "react";
9
- import path from "path";
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";
10
13
  import open from "open";
11
- import { DtInspector } from "./api/inspect";
14
+ import React, { useState, useEffect, useRef } from "react";
15
+ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
16
+ import onExit from "signal-exit";
17
+ import tmp from "tmp-promise";
18
+ import type { DirectoryResult } from "tmp-promise";
19
+
20
+ import type { CfPreviewToken } from "./api/preview";
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";
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 commandExists from "command-exists";
21
- import assert from "assert";
22
30
  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
31
 
30
- type CfScriptFormat = void | "modules" | "service-worker";
32
+ type CfScriptFormat = undefined | "modules" | "service-worker";
31
33
 
32
34
  export type DevProps = {
33
35
  name?: string;
34
36
  entry: string;
35
37
  port?: number;
36
38
  format: CfScriptFormat;
37
- accountId: void | string;
39
+ accountId: undefined | string;
38
40
  initialMode: "local" | "remote";
39
- jsxFactory: void | string;
40
- jsxFragment: void | string;
41
+ jsxFactory: undefined | string;
42
+ jsxFragment: undefined | string;
41
43
  bindings: CfWorkerInit["bindings"];
42
- public: void | string;
43
- site: void | string;
44
- compatibilityDate: void | string;
45
- compatibilityFlags: void | string[];
46
- usageModel: void | "bundled" | "unbound";
44
+ public: undefined | string;
45
+ assetPaths: undefined | AssetPaths;
46
+ compatibilityDate: undefined | string;
47
+ compatibilityFlags: undefined | string[];
48
+ usageModel: undefined | "bundled" | "unbound";
47
49
  buildCommand: {
48
50
  command?: undefined | string;
49
51
  cwd?: undefined | string;
@@ -57,7 +59,7 @@ function Dev(props: DevProps): JSX.Element {
57
59
  "You cannot use the service worker format with a `public` directory."
58
60
  );
59
61
  }
60
- const port = props.port || 8787;
62
+ const port = props.port ?? 8787;
61
63
  const apiToken = getAPIToken();
62
64
  const directory = useTmpDir();
63
65
 
@@ -79,9 +81,6 @@ function Dev(props: DevProps): JSX.Element {
79
81
  );
80
82
  }
81
83
 
82
- // @ts-expect-error whack
83
- useDevtoolsRefresh(bundle?.id ?? 0);
84
-
85
84
  const toggles = useHotkeys(
86
85
  {
87
86
  local: props.initialMode === "local",
@@ -100,9 +99,9 @@ function Dev(props: DevProps): JSX.Element {
100
99
  bundle={bundle}
101
100
  format={props.format}
102
101
  bindings={props.bindings}
103
- site={props.site}
102
+ site={props.assetPaths}
104
103
  public={props.public}
105
- port={props.port}
104
+ port={port}
106
105
  />
107
106
  ) : (
108
107
  <Remote
@@ -112,9 +111,9 @@ function Dev(props: DevProps): JSX.Element {
112
111
  accountId={props.accountId}
113
112
  apiToken={apiToken}
114
113
  bindings={props.bindings}
115
- site={props.site}
114
+ assetPaths={props.assetPaths}
116
115
  public={props.public}
117
- port={props.port}
116
+ port={port}
118
117
  compatibilityDate={props.compatibilityDate}
119
118
  compatibilityFlags={props.compatibilityFlags}
120
119
  usageModel={props.usageModel}
@@ -133,53 +132,23 @@ function Dev(props: DevProps): JSX.Element {
133
132
  );
134
133
  }
135
134
 
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
135
  function Remote(props: {
167
- name: void | string;
168
- bundle: EsbuildBundle | void;
136
+ name: undefined | string;
137
+ bundle: EsbuildBundle | undefined;
169
138
  format: CfScriptFormat;
170
- public: void | string;
171
- site: void | string;
139
+ public: undefined | string;
140
+ assetPaths: undefined | AssetPaths;
172
141
  port: number;
173
- accountId: void | string;
174
- apiToken: void | string;
142
+ accountId: undefined | string;
143
+ apiToken: undefined | string;
175
144
  bindings: CfWorkerInit["bindings"];
176
- compatibilityDate: string | void;
177
- compatibilityFlags: void | string[];
178
- usageModel: void | "bundled" | "unbound";
145
+ compatibilityDate: string | undefined;
146
+ compatibilityFlags: undefined | string[];
147
+ usageModel: undefined | "bundled" | "unbound";
179
148
  }) {
180
149
  assert(props.accountId, "accountId is required");
181
150
  assert(props.apiToken, "apiToken is required");
182
- const token = useWorker({
151
+ const previewToken = useWorker({
183
152
  name: props.name,
184
153
  bundle: props.bundle,
185
154
  format: props.format,
@@ -187,25 +156,33 @@ function Remote(props: {
187
156
  accountId: props.accountId,
188
157
  apiToken: props.apiToken,
189
158
  bindings: props.bindings,
190
- sitesFolder: props.site,
159
+ assetPaths: props.assetPaths,
191
160
  port: props.port,
192
161
  compatibilityDate: props.compatibilityDate,
193
162
  compatibilityFlags: props.compatibilityFlags,
194
163
  usageModel: props.usageModel,
195
164
  });
196
165
 
197
- useProxy({ token, publicRoot: props.public, port: props.port });
166
+ usePreviewServer({
167
+ previewToken,
168
+ publicRoot: props.public,
169
+ port: props.port,
170
+ });
198
171
 
199
- useInspector(token ? token.inspectorUrl.href : undefined);
172
+ useInspector({
173
+ inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
174
+ port: 9229,
175
+ logToTerminal: true,
176
+ });
200
177
  return null;
201
178
  }
202
179
  function Local(props: {
203
- name: void | string;
204
- bundle: EsbuildBundle | void;
180
+ name: undefined | string;
181
+ bundle: EsbuildBundle | undefined;
205
182
  format: CfScriptFormat;
206
183
  bindings: CfWorkerInit["bindings"];
207
- public: void | string;
208
- site: void | string;
184
+ public: undefined | string;
185
+ site: undefined | AssetPaths;
209
186
  port: number;
210
187
  }) {
211
188
  const { inspectorUrl } = useLocalWorker({
@@ -215,13 +192,13 @@ function Local(props: {
215
192
  bindings: props.bindings,
216
193
  port: props.port,
217
194
  });
218
- useInspector(inspectorUrl);
195
+ useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
219
196
  return null;
220
197
  }
221
198
 
222
199
  function useLocalWorker(props: {
223
- name: void | string;
224
- bundle: EsbuildBundle | void;
200
+ name: undefined | string;
201
+ bundle: EsbuildBundle | undefined;
225
202
  format: CfScriptFormat;
226
203
  bindings: CfWorkerInit["bindings"];
227
204
  port: number;
@@ -230,7 +207,7 @@ function useLocalWorker(props: {
230
207
  const { bundle, format, bindings, port } = props;
231
208
  const local = useRef<ReturnType<typeof spawn>>();
232
209
  const removeSignalExitListener = useRef<() => void>();
233
- const [inspectorUrl, setInspectorUrl] = useState<string | void>();
210
+ const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
234
211
  useEffect(() => {
235
212
  async function startLocalWorker() {
236
213
  if (!bundle) return;
@@ -289,11 +266,11 @@ function useLocalWorker(props: {
289
266
  }
290
267
  });
291
268
 
292
- local.current.stdout.on("data", (data: Buffer) => {
269
+ local.current.stdout?.on("data", (data: Buffer) => {
293
270
  console.log(`${data.toString()}`);
294
271
  });
295
272
 
296
- local.current.stderr.on("data", (data: Buffer) => {
273
+ local.current.stderr?.on("data", (data: Buffer) => {
297
274
  console.error(`${data.toString()}`);
298
275
  const matches =
299
276
  /Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
@@ -346,7 +323,7 @@ function useLocalWorker(props: {
346
323
  return { inspectorUrl };
347
324
  }
348
325
 
349
- function useTmpDir(): string | void {
326
+ function useTmpDir(): string | undefined {
350
327
  const [directory, setDirectory] = useState<DirectoryResult>();
351
328
  const handleError = useErrorHandler();
352
329
  useEffect(() => {
@@ -384,10 +361,10 @@ function useCustomBuild(
384
361
  cwd?: undefined | string;
385
362
  watch_dir?: undefined | string;
386
363
  }
387
- ): void | string {
388
- const [entry, setEntry] = useState<string | void>(
364
+ ): undefined | string {
365
+ const [entry, setEntry] = useState<string | undefined>(
389
366
  // if there's no build command, just return the expected entry
390
- props.command ? null : expectedEntry
367
+ props.command || expectedEntry
391
368
  );
392
369
  const { command, cwd, watch_dir } = props;
393
370
  useEffect(() => {
@@ -456,16 +433,16 @@ type EsbuildBundle = {
456
433
  };
457
434
 
458
435
  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 {
436
+ entry: undefined | string;
437
+ destination: string | undefined;
438
+ staticRoot: undefined | string;
439
+ jsxFactory: string | undefined;
440
+ jsxFragment: string | undefined;
441
+ }): EsbuildBundle | undefined {
465
442
  const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
466
443
  const [bundle, setBundle] = useState<EsbuildBundle>();
467
444
  useEffect(() => {
468
- let result: esbuild.BuildResult;
445
+ let result: esbuild.BuildResult | undefined;
469
446
  async function build() {
470
447
  if (!destination || !entry) return;
471
448
  const moduleCollector = makeModuleCollector();
@@ -491,25 +468,37 @@ function useEsbuild(props: {
491
468
  else {
492
469
  // nothing really changes here, so let's increment the id
493
470
  // to change the return object's identity
494
- setBundle((previousBundle) => ({
495
- ...previousBundle,
496
- id: previousBundle.id + 1,
497
- }));
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
+ });
498
479
  }
499
480
  },
500
481
  },
501
482
  });
502
483
 
503
- 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(
504
488
  ([_path, { entryPoint }]) => entryPoint === entry
505
489
  ); // assumedly only one entry point
506
490
 
491
+ if (outputEntry === undefined) {
492
+ throw new Error(
493
+ `Cannot find entry-point "${entry}" in generated bundle.`
494
+ );
495
+ }
507
496
  setBundle({
508
497
  id: 0,
509
498
  entry,
510
- path: chunks[0],
511
- type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
512
- exports: chunks[1].exports,
499
+ path: outputEntry[0],
500
+ type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs",
501
+ exports: outputEntry[1].exports,
513
502
  modules: moduleCollector.modules,
514
503
  });
515
504
  }
@@ -520,26 +509,26 @@ function useEsbuild(props: {
520
509
  // so this is a no-op error handler
521
510
  });
522
511
  return () => {
523
- result?.stop();
512
+ result?.stop?.();
524
513
  };
525
514
  }, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
526
515
  return bundle;
527
516
  }
528
517
 
529
518
  function useWorker(props: {
530
- name: void | string;
531
- bundle: EsbuildBundle | void;
519
+ name: undefined | string;
520
+ bundle: EsbuildBundle | undefined;
532
521
  format: CfScriptFormat;
533
522
  modules: CfModule[];
534
523
  accountId: string;
535
524
  apiToken: string;
536
525
  bindings: CfWorkerInit["bindings"];
537
- sitesFolder: void | string;
526
+ assetPaths: undefined | AssetPaths;
538
527
  port: number;
539
- compatibilityDate: string | void;
540
- compatibilityFlags: string[] | void;
541
- usageModel: void | "bundled" | "unbound";
542
- }): CfPreviewToken | void {
528
+ compatibilityDate: string | undefined;
529
+ compatibilityFlags: string[] | undefined;
530
+ usageModel: undefined | "bundled" | "unbound";
531
+ }): CfPreviewToken | undefined {
543
532
  const {
544
533
  name,
545
534
  bundle,
@@ -548,13 +537,13 @@ function useWorker(props: {
548
537
  accountId,
549
538
  apiToken,
550
539
  bindings,
551
- sitesFolder,
540
+ assetPaths,
552
541
  compatibilityDate,
553
542
  compatibilityFlags,
554
543
  usageModel,
555
544
  port,
556
545
  } = props;
557
- const [token, setToken] = useState<CfPreviewToken>();
546
+ const [token, setToken] = useState<CfPreviewToken | undefined>();
558
547
 
559
548
  // This is the most reliable way to detect whether
560
549
  // something's "happened" in our system; We make a ref and
@@ -563,6 +552,8 @@ function useWorker(props: {
563
552
 
564
553
  useEffect(() => {
565
554
  async function start() {
555
+ setToken(undefined); // reset token in case we're re-running
556
+
566
557
  if (!bundle) return;
567
558
  if (format === "modules" && bundle.type === "commonjs") {
568
559
  console.error("⎔ Cannot use modules with a commonjs bundle.");
@@ -576,24 +567,17 @@ function useWorker(props: {
576
567
  }
577
568
 
578
569
  if (!startedRef.current) {
579
- console.log("⎔ Starting server...");
580
570
  startedRef.current = true;
581
571
  } else {
582
572
  console.log("⎔ Detected changes, restarting server...");
583
573
  }
584
574
 
585
- const assets = sitesFolder
586
- ? await syncAssets(
587
- accountId,
588
- path.basename(bundle.path),
589
- sitesFolder,
590
- true,
591
- undefined // TODO: env
592
- )
593
- : {
594
- manifest: undefined,
595
- namespace: undefined,
596
- }; // TODO: cancellable?
575
+ const assets = await syncAssets(
576
+ accountId,
577
+ path.basename(bundle.path),
578
+ assetPaths,
579
+ true
580
+ ); // TODO: cancellable?
597
581
 
598
582
  const content = await readFile(bundle.path, "utf-8");
599
583
  const init: CfWorkerInit = {
@@ -644,7 +628,7 @@ function useWorker(props: {
644
628
  accountId,
645
629
  apiToken,
646
630
  port,
647
- sitesFolder,
631
+ assetPaths,
648
632
  compatibilityDate,
649
633
  compatibilityFlags,
650
634
  usageModel,
@@ -654,85 +638,6 @@ function useWorker(props: {
654
638
  return token;
655
639
  }
656
640
 
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
641
  function sleep(period: number) {
737
642
  return new Promise((resolve) => setTimeout(resolve, period));
738
643
  }
@@ -740,7 +645,7 @@ const SLEEP_DURATION = 2000;
740
645
  // really need a first class api for this
741
646
  const hostNameRegex = /userHostname="(.*)"/g;
742
647
  async function findTunnelHostname() {
743
- let hostName: string;
648
+ let hostName: string | undefined;
744
649
  while (!hostName) {
745
650
  try {
746
651
  const resp = await fetch("http://localhost:8789/metrics");
@@ -866,7 +771,7 @@ function ErrorFallback(props: { error: Error }) {
866
771
  return (
867
772
  <>
868
773
  <Text>Something went wrong:</Text>
869
- <Text>{props.error.message}</Text>
774
+ <Text>{props.error.stack}</Text>
870
775
  </>
871
776
  );
872
777
  }