wrangler 0.0.0-fb43821 → 0.0.0-fb53fda

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 (104) hide show
  1. package/README.md +1 -1
  2. package/bin/wrangler.js +12 -3
  3. package/miniflare-dist/index.mjs +79 -0
  4. package/miniflare-dist/index.mjs.map +7 -0
  5. package/package.json +46 -17
  6. package/pages/functions/buildWorker.ts +62 -0
  7. package/pages/functions/filepath-routing.test.ts +223 -0
  8. package/pages/functions/filepath-routing.ts +181 -0
  9. package/pages/functions/identifiers.ts +78 -0
  10. package/pages/functions/routes.ts +151 -0
  11. package/pages/functions/template-worker.ts +130 -0
  12. package/src/__tests__/configuration.test.ts +2790 -0
  13. package/src/__tests__/dev.test.tsx +532 -0
  14. package/src/__tests__/guess-worker-format.test.ts +68 -0
  15. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  16. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  17. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  18. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  19. package/src/__tests__/helpers/mock-bin.ts +36 -0
  20. package/src/__tests__/helpers/mock-cfetch.ts +207 -0
  21. package/src/__tests__/helpers/mock-console.ts +70 -0
  22. package/src/__tests__/helpers/mock-dialogs.ts +65 -0
  23. package/src/__tests__/helpers/mock-kv.ts +33 -0
  24. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  25. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +32 -0
  27. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  28. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  29. package/src/__tests__/https-options.test.ts +127 -0
  30. package/src/__tests__/index.test.ts +770 -209
  31. package/src/__tests__/jest.setup.ts +69 -0
  32. package/src/__tests__/kv.test.ts +1380 -0
  33. package/src/__tests__/logout.test.ts +52 -0
  34. package/src/__tests__/package-manager.test.ts +206 -0
  35. package/src/__tests__/pages.test.ts +82 -0
  36. package/src/__tests__/parse.test.ts +322 -0
  37. package/src/__tests__/publish.test.ts +3431 -0
  38. package/src/__tests__/r2.test.ts +198 -0
  39. package/src/__tests__/route.test.ts +45 -0
  40. package/src/__tests__/secret.test.ts +425 -0
  41. package/src/__tests__/sentry.test.ts +146 -0
  42. package/src/__tests__/tail.test.ts +562 -0
  43. package/src/__tests__/whoami.test.tsx +100 -0
  44. package/src/bundle.ts +142 -0
  45. package/src/cfetch/index.ts +104 -0
  46. package/src/cfetch/internal.ts +139 -0
  47. package/src/cli.ts +16 -7
  48. package/src/config/README.md +107 -0
  49. package/src/config/config.ts +218 -0
  50. package/src/config/diagnostics.ts +80 -0
  51. package/src/config/environment.ts +366 -0
  52. package/src/config/index.ts +62 -0
  53. package/src/config/validation-helpers.ts +554 -0
  54. package/src/config/validation.ts +1191 -0
  55. package/src/{api/preview.ts → create-worker-preview.ts} +63 -25
  56. package/src/create-worker-upload-form.ts +217 -0
  57. package/src/dev/dev.tsx +390 -0
  58. package/src/dev/local.tsx +232 -0
  59. package/src/dev/remote.tsx +203 -0
  60. package/src/dev/use-esbuild.ts +100 -0
  61. package/src/dialogs.tsx +4 -4
  62. package/src/entry.ts +237 -0
  63. package/src/environment-variables.ts +33 -0
  64. package/src/https-options.ts +122 -0
  65. package/src/index.tsx +1417 -915
  66. package/src/inspect.ts +617 -0
  67. package/src/intl-polyfill.d.ts +139 -0
  68. package/src/kv.ts +319 -0
  69. package/src/miniflare-cli/README.md +30 -0
  70. package/src/miniflare-cli/enum-keys.ts +17 -0
  71. package/src/miniflare-cli/index.ts +37 -0
  72. package/src/module-collection.ts +229 -35
  73. package/src/open-in-browser.ts +25 -0
  74. package/src/package-manager.ts +126 -0
  75. package/src/pages.tsx +470 -227
  76. package/src/parse.ts +183 -0
  77. package/src/paths.ts +26 -0
  78. package/src/proxy.ts +440 -0
  79. package/src/publish.ts +292 -290
  80. package/src/r2.ts +50 -0
  81. package/src/reporting.ts +131 -0
  82. package/src/selfsigned.d.ts +29 -0
  83. package/src/sites.tsx +224 -59
  84. package/src/tail/filters.ts +279 -0
  85. package/src/tail/index.ts +299 -0
  86. package/src/tail/printing.ts +62 -0
  87. package/src/user.tsx +301 -203
  88. package/src/whoami.tsx +69 -0
  89. package/src/worker.ts +163 -0
  90. package/templates/new-worker.js +15 -0
  91. package/templates/new-worker.ts +15 -0
  92. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  93. package/templates/tsconfig.json +105 -0
  94. package/wrangler-dist/cli.js +134241 -116339
  95. package/wrangler-dist/cli.js.map +3 -3
  96. package/src/__tests__/mock-cfetch.js +0 -42
  97. package/src/api/form_data.ts +0 -141
  98. package/src/api/inspect.ts +0 -430
  99. package/src/api/worker.ts +0 -161
  100. package/src/cfetch.ts +0 -72
  101. package/src/config.ts +0 -122
  102. package/src/dev.tsx +0 -776
  103. package/src/kv.tsx +0 -211
  104. package/src/tail.tsx +0 -71
package/src/dev.tsx DELETED
@@ -1,776 +0,0 @@
1
- import esbuild from "esbuild";
2
- import httpProxy from "http-proxy";
3
- import { readFile } from "fs/promises";
4
- import type { DirectoryResult } from "tmp-promise";
5
- import tmp from "tmp-promise";
6
- 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";
13
- import { createWorker } from "./api/worker";
14
- import type { CfWorkerInit } from "./api/worker";
15
- import { spawn } from "child_process";
16
- import onExit from "signal-exit";
17
- 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
- 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
-
28
- type CfScriptFormat = void | "modules" | "service-worker";
29
-
30
- type Props = {
31
- name?: string;
32
- entry: string;
33
- port?: number;
34
- format: CfScriptFormat;
35
- accountId: void | string;
36
- 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";
45
- };
46
-
47
- function Dev(props: Props): JSX.Element {
48
- if (props.public && props.format === "service-worker") {
49
- throw new Error(
50
- "You cannot use the service worker format with a `public` directory."
51
- );
52
- }
53
- const port = props.port || 8787;
54
- const apiToken = getAPIToken();
55
- const directory = useTmpDir();
56
-
57
- const bundle = useEsbuild({
58
- entry: props.entry,
59
- destination: directory,
60
- staticRoot: props.public,
61
- jsxFactory: props.jsxFactory,
62
- jsxFragment: props.jsxFragment,
63
- });
64
- if (bundle && bundle.type === "commonjs" && !props.format && props.public) {
65
- throw new Error(
66
- "You cannot use the service worker format with a `public` directory."
67
- );
68
- }
69
-
70
- // @ts-expect-error whack
71
- useDevtoolsRefresh(bundle?.id ?? 0);
72
-
73
- const toggles = useHotkeys(
74
- {
75
- local: props.initialMode === "local",
76
- tunnel: false,
77
- },
78
- port
79
- );
80
-
81
- useTunnel(toggles.tunnel);
82
-
83
- return (
84
- <>
85
- {toggles.local ? (
86
- <Local
87
- name={props.name}
88
- bundle={bundle}
89
- format={props.format}
90
- variables={props.variables}
91
- site={props.site}
92
- public={props.public}
93
- port={props.port}
94
- />
95
- ) : (
96
- <Remote
97
- name={props.name}
98
- bundle={bundle}
99
- format={props.format}
100
- accountId={props.accountId}
101
- apiToken={apiToken}
102
- variables={props.variables}
103
- site={props.site}
104
- public={props.public}
105
- port={props.port}
106
- compatibilityDate={props.compatibilityDate}
107
- compatibilityFlags={props.compatibilityFlags}
108
- usageModel={props.usageModel}
109
- />
110
- )}
111
- <Box borderStyle="round" paddingLeft={1} paddingRight={1}>
112
- <Text>
113
- {`B to open a browser, D to open Devtools, S to ${
114
- toggles.tunnel ? "turn off" : "turn on"
115
- } (experimental) sharing, L to ${
116
- toggles.local ? "turn off" : "turn on"
117
- } local mode, X to exit`}
118
- </Text>
119
- </Box>
120
- </>
121
- );
122
- }
123
-
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
- function Remote(props: {
155
- name: void | string;
156
- bundle: EsbuildBundle | void;
157
- format: CfScriptFormat;
158
- public: void | string;
159
- site: void | string;
160
- 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";
167
- }) {
168
- assert(props.accountId, "accountId is required");
169
- assert(props.apiToken, "apiToken is required");
170
- const token = useWorker({
171
- name: props.name,
172
- bundle: props.bundle,
173
- format: props.format,
174
- modules: props.bundle ? props.bundle.modules : [],
175
- accountId: props.accountId,
176
- apiToken: props.apiToken,
177
- variables: props.variables,
178
- sitesFolder: props.site,
179
- port: props.port,
180
- compatibilityDate: props.compatibilityDate,
181
- compatibilityFlags: props.compatibilityFlags,
182
- usageModel: props.usageModel,
183
- });
184
-
185
- useProxy({ token, publicRoot: props.public, port: props.port });
186
-
187
- useInspector(token ? token.inspectorUrl.href : undefined);
188
- return null;
189
- }
190
- function Local(props: {
191
- name: void | string;
192
- bundle: EsbuildBundle | void;
193
- format: CfScriptFormat;
194
- variables: { [name: string]: CfVariable };
195
- public: void | string;
196
- site: void | string;
197
- port: number;
198
- }) {
199
- const { inspectorUrl } = useLocalWorker({
200
- name: props.name,
201
- bundle: props.bundle,
202
- format: props.format,
203
- variables: props.variables,
204
- port: props.port,
205
- });
206
- useInspector(inspectorUrl);
207
- return null;
208
- }
209
-
210
- function useLocalWorker(props: {
211
- name: void | string;
212
- bundle: EsbuildBundle | void;
213
- format: CfScriptFormat;
214
- variables: { [name: string]: CfVariable };
215
- port: number;
216
- }) {
217
- // TODO: pass vars via command line
218
- const { bundle, format, variables, port } = props;
219
- const local = useRef<ReturnType<typeof spawn>>();
220
- const removeSignalExitListener = useRef<() => void>();
221
- const [inspectorUrl, setInspectorUrl] = useState<string | void>();
222
- useEffect(() => {
223
- async function startLocalWorker() {
224
- if (!bundle) return;
225
- if (format === "modules" && bundle.type === "commonjs") {
226
- console.error("⎔ Cannot use modules with a commonjs bundle.");
227
- // TODO: a much better error message here, with what to do next
228
- return;
229
- }
230
- if (format === "service-worker" && bundle.type !== "esm") {
231
- console.error("⎔ Cannot use service-worker with a esm bundle.");
232
- // TODO: a much better error message here, with what to do next
233
- return;
234
- }
235
-
236
- console.log("⎔ Starting a local server...");
237
- // TODO: just use execa for this
238
- local.current = spawn("node", [
239
- "--experimental-vm-modules",
240
- "--inspect",
241
- require.resolve("miniflare/cli"),
242
- bundle.path,
243
- "--watch",
244
- "--wrangler-config",
245
- path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
246
- "--env",
247
- path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
248
- "--package",
249
- path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
250
- "--port",
251
- port.toString(),
252
- "--kv-persist",
253
- "--cache-persist",
254
- "--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),
269
- "--modules",
270
- format ||
271
- (bundle.type === "esm" ? "modules" : "service-worker") === "modules"
272
- ? "true"
273
- : "false",
274
- ]);
275
- console.log(`⬣ Listening at http://localhost:${port}`);
276
-
277
- local.current.on("close", (code) => {
278
- if (code !== null) {
279
- console.log(`miniflare process exited with code ${code}`);
280
- }
281
- });
282
-
283
- local.current.stdout.on("data", (_data: string) => {
284
- // console.log(`stdout: ${data}`);
285
- });
286
-
287
- local.current.stderr.on("data", (data: string) => {
288
- // console.error(`stderr: ${data}`);
289
- const matches =
290
- /Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
291
- data
292
- );
293
- if (matches) {
294
- setInspectorUrl(matches[1]);
295
- }
296
- });
297
-
298
- local.current.on("exit", (code) => {
299
- if (code !== 0) {
300
- console.error(`miniflare process exited with code ${code}`);
301
- }
302
- });
303
-
304
- local.current.on("error", (error: Error) => {
305
- console.error(`miniflare process failed to spawn`);
306
- console.error(error);
307
- });
308
-
309
- removeSignalExitListener.current = onExit((_code, _signal) => {
310
- console.log("⎔ Shutting down local server.");
311
- local.current?.kill();
312
- local.current = undefined;
313
- });
314
- }
315
-
316
- startLocalWorker().catch((err) => {
317
- console.error("local worker:", err);
318
- });
319
-
320
- return () => {
321
- if (local.current) {
322
- console.log("⎔ Shutting down local server.");
323
- local.current?.kill();
324
- local.current = undefined;
325
- removeSignalExitListener.current && removeSignalExitListener.current();
326
- removeSignalExitListener.current = undefined;
327
- }
328
- };
329
- }, [bundle, format, port]);
330
- return { inspectorUrl };
331
- }
332
-
333
- function useTmpDir(): string | void {
334
- const [directory, setDirectory] = useState<DirectoryResult>();
335
- const handleError = useErrorHandler();
336
- useEffect(() => {
337
- let dir: DirectoryResult;
338
- async function create() {
339
- try {
340
- dir = await tmp.dir({ unsafeCleanup: true });
341
- setDirectory(dir);
342
- return;
343
- } catch (err) {
344
- console.error("failed to create tmp dir");
345
- throw err;
346
- }
347
- }
348
- create().catch((err) => {
349
- // we want to break here
350
- // we can't do much without a temp dir anyway
351
- handleError(err);
352
- });
353
- return () => {
354
- dir.cleanup().catch(() => {
355
- // extremely unlikely,
356
- // but it's 2021 after all
357
- console.error("failed to cleanup tmp dir");
358
- });
359
- };
360
- }, [handleError]);
361
- return directory?.path;
362
- }
363
-
364
- type EsbuildBundle = {
365
- id: number;
366
- path: string;
367
- entry: string;
368
- type: "esm" | "commonjs";
369
- exports: string[];
370
- modules: CfModule[];
371
- };
372
-
373
- 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 {
380
- const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
381
- const [bundle, setBundle] = useState<EsbuildBundle>();
382
- useEffect(() => {
383
- let result: esbuild.BuildResult;
384
- async function build() {
385
- if (!destination) return;
386
- const moduleCollector = makeModuleCollector();
387
- result = await esbuild.build({
388
- entryPoints: [entry],
389
- bundle: true,
390
- outdir: destination,
391
- metafile: true,
392
- format: "esm",
393
- sourcemap: true,
394
- loader: {
395
- ".js": "jsx",
396
- },
397
- ...(jsxFactory && { jsxFactory }),
398
- ...(jsxFragment && { jsxFragment }),
399
- external: ["__STATIC_CONTENT_MANIFEST"],
400
- conditions: ["worker", "browser"],
401
- plugins: [moduleCollector.plugin],
402
- // TODO: import.meta.url
403
- watch: {
404
- async onRebuild(error) {
405
- if (error) console.error("watch build failed:", error);
406
- else {
407
- // nothing really changes here, so let's increment the id
408
- // to change the return object's identity
409
- setBundle((bundle) => ({ ...bundle, id: bundle.id + 1 }));
410
- }
411
- },
412
- },
413
- });
414
-
415
- const chunks = Object.entries(result.metafile.outputs).find(
416
- ([_path, { entryPoint }]) => entryPoint === entry
417
- ); // assumedly only one entry point
418
-
419
- setBundle({
420
- id: 0,
421
- entry,
422
- path: chunks[0],
423
- type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
424
- exports: chunks[1].exports,
425
- modules: moduleCollector.modules,
426
- });
427
- }
428
- build().catch((_err) => {
429
- // esbuild already logs errors to stderr
430
- // and we don't want to end the process
431
- // on build errors anyway
432
- // so this is a no-op error handler
433
- });
434
- return () => {
435
- result?.stop();
436
- };
437
- }, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
438
- return bundle;
439
- }
440
-
441
- function useWorker(props: {
442
- name: void | string;
443
- bundle: EsbuildBundle | void;
444
- format: CfScriptFormat;
445
- modules: CfModule[];
446
- accountId: string;
447
- apiToken: string;
448
- variables: { [name: string]: CfVariable };
449
- sitesFolder: void | string;
450
- port: number;
451
- compatibilityDate: string | void;
452
- compatibilityFlags: string[] | void;
453
- usageModel: void | "bundled" | "unbound";
454
- }): CfPreviewToken | void {
455
- const {
456
- name,
457
- bundle,
458
- format,
459
- modules,
460
- accountId,
461
- apiToken,
462
- variables,
463
- sitesFolder,
464
- compatibilityDate,
465
- compatibilityFlags,
466
- usageModel,
467
- port,
468
- } = props;
469
- const [token, setToken] = useState<CfPreviewToken>();
470
- useEffect(() => {
471
- async function start() {
472
- if (!bundle) return;
473
- if (format === "modules" && bundle.type === "commonjs") {
474
- console.error("⎔ Cannot use modules with a commonjs bundle.");
475
- // TODO: a much better error message here, with what to do next
476
- return;
477
- }
478
- if (format === "service-worker" && bundle.type !== "esm") {
479
- console.error("⎔ Cannot use service-worker with a esm bundle.");
480
- // TODO: a much better error message here, with what to do next
481
- return;
482
- }
483
-
484
- if (token) {
485
- console.log("⎔ Detected changes, restarting server...");
486
- } else {
487
- console.log("⎔ Starting server...");
488
- }
489
-
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?
502
-
503
- const content = await readFile(bundle.path, "utf-8");
504
- const init: CfWorkerInit = {
505
- name,
506
- main: {
507
- name: path.basename(bundle.path),
508
- type: format || bundle.type === "esm" ? "esm" : "commonjs",
509
- content,
510
- },
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,
524
- migrations: undefined, // no migrations in dev
525
- compatibility_date: compatibilityDate,
526
- compatibility_flags: compatibilityFlags,
527
- usage_model: usageModel,
528
- };
529
- setToken(
530
- await createWorker(init, {
531
- accountId,
532
- apiToken,
533
- })
534
- );
535
- console.log(`⬣ Listening at http://localhost:${port}`);
536
- }
537
- start().catch((err) => {
538
- // we want to log the error, but not end the process
539
- // since it could recover after the developer fixes whatever's wrong
540
- console.error("remote worker:", err);
541
- });
542
- }, [
543
- name,
544
- bundle,
545
- format,
546
- accountId,
547
- apiToken,
548
- port,
549
- sitesFolder,
550
- compatibilityDate,
551
- compatibilityFlags,
552
- usageModel,
553
- ]);
554
- return token;
555
- }
556
-
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
- function sleep(period: number) {
627
- return new Promise((resolve) => setTimeout(resolve, period));
628
- }
629
- const SLEEP_DURATION = 2000;
630
- // really need a first class api for this
631
- const hostNameRegex = /userHostname="(.*)"/g;
632
- async function findTunnelHostname() {
633
- let hostName: string;
634
- while (!hostName) {
635
- try {
636
- const resp = await fetch("http://localhost:8789/metrics");
637
- const data = await resp.text();
638
- const matches = Array.from(data.matchAll(hostNameRegex));
639
- hostName = matches[0][1];
640
- } catch (err) {
641
- await sleep(SLEEP_DURATION);
642
- }
643
- }
644
- return hostName;
645
- }
646
-
647
- function useTunnel(toggle: boolean) {
648
- const tunnel = useRef<ReturnType<typeof spawn>>();
649
- const removeSignalExitListener = useRef<() => void>();
650
- // TODO: test if cloudflared is available, if not
651
- // point them to a url where they can get docs to install it
652
- useEffect(() => {
653
- async function startTunnel() {
654
- if (toggle) {
655
- try {
656
- await commandExists("cloudflared");
657
- } catch (e) {
658
- console.error(
659
- "To share your worker on the internet, please install `cloudflared` from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation"
660
- );
661
- return;
662
- }
663
- console.log("⎔ Starting a tunnel...");
664
- tunnel.current = spawn("cloudflared", [
665
- "tunnel",
666
- "--url",
667
- "http://localhost:8787",
668
- "--metrics",
669
- "localhost:8789",
670
- ]);
671
-
672
- tunnel.current.on("close", (code) => {
673
- if (code !== 0) {
674
- console.log(`Tunnel process exited with code ${code}`);
675
- }
676
- });
677
-
678
- removeSignalExitListener.current = onExit((_code, _signal) => {
679
- console.log("⎔ Shutting down local tunnel.");
680
- tunnel.current?.kill();
681
- tunnel.current = undefined;
682
- });
683
-
684
- const hostName = await findTunnelHostname();
685
- await clipboardy.write(hostName);
686
- console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
687
- }
688
- }
689
-
690
- startTunnel().catch((err) => {
691
- console.error("tunnel:", err);
692
- });
693
-
694
- return () => {
695
- if (tunnel.current) {
696
- console.log("⎔ Shutting down tunnel.");
697
- tunnel.current?.kill();
698
- tunnel.current = undefined;
699
- removeSignalExitListener.current && removeSignalExitListener.current();
700
- removeSignalExitListener.current = undefined;
701
- }
702
- };
703
- }, [toggle]);
704
- }
705
-
706
- type useHotkeysInitialState = {
707
- local: boolean;
708
- tunnel: boolean;
709
- };
710
- function useHotkeys(initial: useHotkeysInitialState, port: number) {
711
- // UGH, we should put port in context instead
712
- const [toggles, setToggles] = useState(initial);
713
- useInput(
714
- async (
715
- input,
716
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
717
- key
718
- ) => {
719
- switch (input) {
720
- 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
- );
729
- break;
730
- case "d": // toggle inspector
731
- await open(
732
- `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
- );
740
- break;
741
- case "s": // toggle tunnel
742
- setToggles((toggles) => ({ ...toggles, tunnel: !toggles.tunnel }));
743
- break;
744
- case "l": // toggle local
745
- setToggles((toggles) => ({ ...toggles, local: !toggles.local }));
746
- break;
747
- case "q": // shut down
748
- case "x": // shut down
749
- process.exit(0);
750
- break;
751
- default:
752
- // nothing?
753
- break;
754
- }
755
- }
756
- );
757
- return toggles;
758
- }
759
-
760
- function ErrorFallback(props: {
761
- error: Error;
762
- resetErrorBoundary: () => void;
763
- }) {
764
- useEffect(() => {
765
- console.error(props.error);
766
- process.exit(1);
767
- });
768
- return (
769
- <Box>
770
- <Text>Something went wrong:</Text>
771
- <Text>{props.error.message}</Text>
772
- </Box>
773
- );
774
- }
775
-
776
- export default withErrorBoundary(Dev, { FallbackComponent: ErrorFallback });