wrangler 0.0.0-e928f94 → 0.0.0-e9a1820
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/package.json +13 -7
- package/src/__tests__/index.test.ts +254 -120
- package/src/__tests__/jest.setup.ts +18 -0
- package/src/__tests__/kv.test.ts +322 -0
- package/src/__tests__/mock-cfetch.ts +63 -0
- package/src/__tests__/mock-dialogs.ts +65 -0
- package/src/__tests__/run-in-tmp.ts +19 -0
- package/src/__tests__/run-wrangler.ts +32 -0
- package/src/api/form_data.ts +65 -75
- package/src/api/inspect.ts +0 -11
- package/src/api/worker.ts +33 -39
- package/src/cli.ts +6 -7
- package/src/config.ts +14 -5
- package/src/dev.tsx +264 -109
- package/src/dialogs.tsx +3 -11
- package/src/index.tsx +254 -210
- package/src/kv.tsx +65 -19
- package/src/pages.tsx +107 -99
- package/src/proxy.ts +103 -0
- package/src/publish.ts +37 -33
- package/src/user.tsx +9 -16
- package/wrangler-dist/cli.js +20754 -26926
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/fixtures/init/.gitkeep +0 -0
- package/src/__tests__/mock-cfetch.js +0 -46
package/src/dev.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import esbuild from "esbuild";
|
|
2
|
-
import httpProxy from "http-proxy";
|
|
3
2
|
import { readFile } from "fs/promises";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
4
|
import type { DirectoryResult } from "tmp-promise";
|
|
5
5
|
import tmp from "tmp-promise";
|
|
6
6
|
import type { CfPreviewToken } from "./api/preview";
|
|
@@ -9,7 +9,7 @@ import React, { useState, useEffect, useRef } from "react";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import open from "open";
|
|
11
11
|
import { DtInspector } from "./api/inspect";
|
|
12
|
-
import type { CfModule
|
|
12
|
+
import type { CfModule } from "./api/worker";
|
|
13
13
|
import { createWorker } from "./api/worker";
|
|
14
14
|
import type { CfWorkerInit } from "./api/worker";
|
|
15
15
|
import { spawn } from "child_process";
|
|
@@ -17,12 +17,15 @@ import onExit from "signal-exit";
|
|
|
17
17
|
import { syncAssets } from "./sites";
|
|
18
18
|
import clipboardy from "clipboardy";
|
|
19
19
|
import http from "node:http";
|
|
20
|
-
import serveStatic from "serve-static";
|
|
21
20
|
import commandExists from "command-exists";
|
|
22
21
|
import assert from "assert";
|
|
23
22
|
import { getAPIToken } from "./user";
|
|
24
23
|
import fetch from "node-fetch";
|
|
25
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";
|
|
26
29
|
|
|
27
30
|
type CfScriptFormat = void | "modules" | "service-worker";
|
|
28
31
|
|
|
@@ -35,15 +38,20 @@ type Props = {
|
|
|
35
38
|
initialMode: "local" | "remote";
|
|
36
39
|
jsxFactory: void | string;
|
|
37
40
|
jsxFragment: void | string;
|
|
38
|
-
|
|
41
|
+
bindings: CfWorkerInit["bindings"];
|
|
39
42
|
public: void | string;
|
|
40
43
|
site: void | string;
|
|
41
44
|
compatibilityDate: void | string;
|
|
42
45
|
compatibilityFlags: void | string[];
|
|
43
46
|
usageModel: void | "bundled" | "unbound";
|
|
47
|
+
buildCommand: {
|
|
48
|
+
command?: undefined | string;
|
|
49
|
+
cwd?: undefined | string;
|
|
50
|
+
watch_dir?: undefined | string;
|
|
51
|
+
};
|
|
44
52
|
};
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
function Dev(props: Props): JSX.Element {
|
|
47
55
|
if (props.public && props.format === "service-worker") {
|
|
48
56
|
throw new Error(
|
|
49
57
|
"You cannot use the service worker format with a `public` directory."
|
|
@@ -53,8 +61,13 @@ export function Dev(props: Props): JSX.Element {
|
|
|
53
61
|
const apiToken = getAPIToken();
|
|
54
62
|
const directory = useTmpDir();
|
|
55
63
|
|
|
64
|
+
// if there isn't a build command, we just return the entry immediately
|
|
65
|
+
// ideally there would be a conditional here, but the rules of hooks
|
|
66
|
+
// kinda forbid that, so we thread the entry through useCustomBuild
|
|
67
|
+
const entry = useCustomBuild(props.entry, props.buildCommand);
|
|
68
|
+
|
|
56
69
|
const bundle = useEsbuild({
|
|
57
|
-
entry
|
|
70
|
+
entry,
|
|
58
71
|
destination: directory,
|
|
59
72
|
staticRoot: props.public,
|
|
60
73
|
jsxFactory: props.jsxFactory,
|
|
@@ -86,7 +99,7 @@ export function Dev(props: Props): JSX.Element {
|
|
|
86
99
|
name={props.name}
|
|
87
100
|
bundle={bundle}
|
|
88
101
|
format={props.format}
|
|
89
|
-
|
|
102
|
+
bindings={props.bindings}
|
|
90
103
|
site={props.site}
|
|
91
104
|
public={props.public}
|
|
92
105
|
port={props.port}
|
|
@@ -98,7 +111,7 @@ export function Dev(props: Props): JSX.Element {
|
|
|
98
111
|
format={props.format}
|
|
99
112
|
accountId={props.accountId}
|
|
100
113
|
apiToken={apiToken}
|
|
101
|
-
|
|
114
|
+
bindings={props.bindings}
|
|
102
115
|
site={props.site}
|
|
103
116
|
public={props.public}
|
|
104
117
|
port={props.port}
|
|
@@ -159,7 +172,7 @@ function Remote(props: {
|
|
|
159
172
|
port: number;
|
|
160
173
|
accountId: void | string;
|
|
161
174
|
apiToken: void | string;
|
|
162
|
-
|
|
175
|
+
bindings: CfWorkerInit["bindings"];
|
|
163
176
|
compatibilityDate: string | void;
|
|
164
177
|
compatibilityFlags: void | string[];
|
|
165
178
|
usageModel: void | "bundled" | "unbound";
|
|
@@ -173,7 +186,7 @@ function Remote(props: {
|
|
|
173
186
|
modules: props.bundle ? props.bundle.modules : [],
|
|
174
187
|
accountId: props.accountId,
|
|
175
188
|
apiToken: props.apiToken,
|
|
176
|
-
|
|
189
|
+
bindings: props.bindings,
|
|
177
190
|
sitesFolder: props.site,
|
|
178
191
|
port: props.port,
|
|
179
192
|
compatibilityDate: props.compatibilityDate,
|
|
@@ -190,7 +203,7 @@ function Local(props: {
|
|
|
190
203
|
name: void | string;
|
|
191
204
|
bundle: EsbuildBundle | void;
|
|
192
205
|
format: CfScriptFormat;
|
|
193
|
-
|
|
206
|
+
bindings: CfWorkerInit["bindings"];
|
|
194
207
|
public: void | string;
|
|
195
208
|
site: void | string;
|
|
196
209
|
port: number;
|
|
@@ -199,7 +212,7 @@ function Local(props: {
|
|
|
199
212
|
name: props.name,
|
|
200
213
|
bundle: props.bundle,
|
|
201
214
|
format: props.format,
|
|
202
|
-
|
|
215
|
+
bindings: props.bindings,
|
|
203
216
|
port: props.port,
|
|
204
217
|
});
|
|
205
218
|
useInspector(inspectorUrl);
|
|
@@ -210,11 +223,11 @@ function useLocalWorker(props: {
|
|
|
210
223
|
name: void | string;
|
|
211
224
|
bundle: EsbuildBundle | void;
|
|
212
225
|
format: CfScriptFormat;
|
|
213
|
-
|
|
226
|
+
bindings: CfWorkerInit["bindings"];
|
|
214
227
|
port: number;
|
|
215
228
|
}) {
|
|
216
229
|
// TODO: pass vars via command line
|
|
217
|
-
const { bundle, format,
|
|
230
|
+
const { bundle, format, bindings, port } = props;
|
|
218
231
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
219
232
|
const removeSignalExitListener = useRef<() => void>();
|
|
220
233
|
const [inspectorUrl, setInspectorUrl] = useState<string | void>();
|
|
@@ -233,6 +246,7 @@ function useLocalWorker(props: {
|
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
console.log("⎔ Starting a local server...");
|
|
249
|
+
// TODO: just use execa for this
|
|
236
250
|
local.current = spawn("node", [
|
|
237
251
|
"--experimental-vm-modules",
|
|
238
252
|
"--inspect",
|
|
@@ -250,20 +264,17 @@ function useLocalWorker(props: {
|
|
|
250
264
|
"--kv-persist",
|
|
251
265
|
"--cache-persist",
|
|
252
266
|
"--do-persist",
|
|
253
|
-
...Object.entries(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
.filter(Boolean),
|
|
267
|
+
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
268
|
+
return ["--binding", `${key}=${value}`];
|
|
269
|
+
}),
|
|
270
|
+
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
271
|
+
return ["--kv", binding];
|
|
272
|
+
}),
|
|
273
|
+
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
274
|
+
({ name, class_name }) => {
|
|
275
|
+
return ["--do", `${name}=${class_name}`];
|
|
276
|
+
}
|
|
277
|
+
),
|
|
267
278
|
"--modules",
|
|
268
279
|
format ||
|
|
269
280
|
(bundle.type === "esm" ? "modules" : "service-worker") === "modules"
|
|
@@ -278,21 +289,32 @@ function useLocalWorker(props: {
|
|
|
278
289
|
}
|
|
279
290
|
});
|
|
280
291
|
|
|
281
|
-
local.current.stdout.on("data", (
|
|
282
|
-
|
|
292
|
+
local.current.stdout.on("data", (data: Buffer) => {
|
|
293
|
+
console.log(`${data.toString()}`);
|
|
283
294
|
});
|
|
284
295
|
|
|
285
|
-
local.current.stderr.on("data", (data:
|
|
286
|
-
|
|
296
|
+
local.current.stderr.on("data", (data: Buffer) => {
|
|
297
|
+
console.error(`${data.toString()}`);
|
|
287
298
|
const matches =
|
|
288
299
|
/Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
|
|
289
|
-
data
|
|
300
|
+
data.toString()
|
|
290
301
|
);
|
|
291
302
|
if (matches) {
|
|
292
303
|
setInspectorUrl(matches[1]);
|
|
293
304
|
}
|
|
294
305
|
});
|
|
295
306
|
|
|
307
|
+
local.current.on("exit", (code) => {
|
|
308
|
+
if (code !== 0) {
|
|
309
|
+
console.error(`miniflare process exited with code ${code}`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
local.current.on("error", (error: Error) => {
|
|
314
|
+
console.error(`miniflare process failed to spawn`);
|
|
315
|
+
console.error(error);
|
|
316
|
+
});
|
|
317
|
+
|
|
296
318
|
removeSignalExitListener.current = onExit((_code, _signal) => {
|
|
297
319
|
console.log("⎔ Shutting down local server.");
|
|
298
320
|
local.current?.kill();
|
|
@@ -300,7 +322,9 @@ function useLocalWorker(props: {
|
|
|
300
322
|
});
|
|
301
323
|
}
|
|
302
324
|
|
|
303
|
-
startLocalWorker()
|
|
325
|
+
startLocalWorker().catch((err) => {
|
|
326
|
+
console.error("local worker:", err);
|
|
327
|
+
});
|
|
304
328
|
|
|
305
329
|
return () => {
|
|
306
330
|
if (local.current) {
|
|
@@ -311,12 +335,20 @@ function useLocalWorker(props: {
|
|
|
311
335
|
removeSignalExitListener.current = undefined;
|
|
312
336
|
}
|
|
313
337
|
};
|
|
314
|
-
}, [
|
|
338
|
+
}, [
|
|
339
|
+
bundle,
|
|
340
|
+
format,
|
|
341
|
+
port,
|
|
342
|
+
bindings.durable_objects?.bindings,
|
|
343
|
+
bindings.kv_namespaces,
|
|
344
|
+
bindings.vars,
|
|
345
|
+
]);
|
|
315
346
|
return { inspectorUrl };
|
|
316
347
|
}
|
|
317
348
|
|
|
318
349
|
function useTmpDir(): string | void {
|
|
319
350
|
const [directory, setDirectory] = useState<DirectoryResult>();
|
|
351
|
+
const handleError = useErrorHandler();
|
|
320
352
|
useEffect(() => {
|
|
321
353
|
let dir: DirectoryResult;
|
|
322
354
|
async function create() {
|
|
@@ -326,18 +358,94 @@ function useTmpDir(): string | void {
|
|
|
326
358
|
return;
|
|
327
359
|
} catch (err) {
|
|
328
360
|
console.error("failed to create tmp dir");
|
|
329
|
-
console.error(err);
|
|
330
361
|
throw err;
|
|
331
362
|
}
|
|
332
363
|
}
|
|
333
|
-
create()
|
|
364
|
+
create().catch((err) => {
|
|
365
|
+
// we want to break here
|
|
366
|
+
// we can't do much without a temp dir anyway
|
|
367
|
+
handleError(err);
|
|
368
|
+
});
|
|
334
369
|
return () => {
|
|
335
|
-
dir.cleanup()
|
|
370
|
+
dir.cleanup().catch(() => {
|
|
371
|
+
// extremely unlikely,
|
|
372
|
+
// but it's 2021 after all
|
|
373
|
+
console.error("failed to cleanup tmp dir");
|
|
374
|
+
});
|
|
336
375
|
};
|
|
337
|
-
}, []);
|
|
376
|
+
}, [handleError]);
|
|
338
377
|
return directory?.path;
|
|
339
378
|
}
|
|
340
379
|
|
|
380
|
+
function useCustomBuild(
|
|
381
|
+
expectedEntry: string,
|
|
382
|
+
props: {
|
|
383
|
+
command?: undefined | string;
|
|
384
|
+
cwd?: undefined | string;
|
|
385
|
+
watch_dir?: undefined | string;
|
|
386
|
+
}
|
|
387
|
+
): void | string {
|
|
388
|
+
const [entry, setEntry] = useState<string | void>(
|
|
389
|
+
// if there's no build command, just return the expected entry
|
|
390
|
+
props.command ? null : expectedEntry
|
|
391
|
+
);
|
|
392
|
+
const { command, cwd, watch_dir } = props;
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (!command) return;
|
|
395
|
+
let cmd, interval;
|
|
396
|
+
console.log("running:", command);
|
|
397
|
+
const commandPieces = command.split(" ");
|
|
398
|
+
cmd = execa(commandPieces[0], commandPieces.slice(1), {
|
|
399
|
+
...(cwd && { cwd }),
|
|
400
|
+
stderr: "inherit",
|
|
401
|
+
stdout: "inherit",
|
|
402
|
+
});
|
|
403
|
+
if (watch_dir) {
|
|
404
|
+
watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
|
|
405
|
+
"all",
|
|
406
|
+
(_event, _path) => {
|
|
407
|
+
console.log(`The file ${path} changed, restarting build...`);
|
|
408
|
+
cmd.kill();
|
|
409
|
+
cmd = execa(commandPieces[0], commandPieces.slice(1), {
|
|
410
|
+
...(cwd && { cwd }),
|
|
411
|
+
stderr: "inherit",
|
|
412
|
+
stdout: "inherit",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// check every so often whether `expectedEntry` exists
|
|
419
|
+
// if it does, we're done
|
|
420
|
+
const startedAt = Date.now();
|
|
421
|
+
interval = setInterval(() => {
|
|
422
|
+
if (existsSync(expectedEntry)) {
|
|
423
|
+
clearInterval(interval);
|
|
424
|
+
setEntry(expectedEntry);
|
|
425
|
+
} else {
|
|
426
|
+
const elapsed = Date.now() - startedAt;
|
|
427
|
+
// timeout after 30 seconds of waiting
|
|
428
|
+
if (elapsed > 1000 * 60 * 30) {
|
|
429
|
+
console.error("⎔ Build timed out.");
|
|
430
|
+
clearInterval(interval);
|
|
431
|
+
cmd.kill();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}, 200);
|
|
435
|
+
// TODO: we could probably timeout here after a while
|
|
436
|
+
|
|
437
|
+
return () => {
|
|
438
|
+
if (cmd) {
|
|
439
|
+
cmd.kill();
|
|
440
|
+
cmd = undefined;
|
|
441
|
+
}
|
|
442
|
+
clearInterval(interval);
|
|
443
|
+
interval = undefined;
|
|
444
|
+
};
|
|
445
|
+
}, [command, cwd, expectedEntry, watch_dir]);
|
|
446
|
+
return entry;
|
|
447
|
+
}
|
|
448
|
+
|
|
341
449
|
type EsbuildBundle = {
|
|
342
450
|
id: number;
|
|
343
451
|
path: string;
|
|
@@ -348,7 +456,7 @@ type EsbuildBundle = {
|
|
|
348
456
|
};
|
|
349
457
|
|
|
350
458
|
function useEsbuild(props: {
|
|
351
|
-
entry: string;
|
|
459
|
+
entry: void | string;
|
|
352
460
|
destination: string | void;
|
|
353
461
|
staticRoot: void | string;
|
|
354
462
|
jsxFactory: string | void;
|
|
@@ -359,7 +467,7 @@ function useEsbuild(props: {
|
|
|
359
467
|
useEffect(() => {
|
|
360
468
|
let result: esbuild.BuildResult;
|
|
361
469
|
async function build() {
|
|
362
|
-
if (!destination) return;
|
|
470
|
+
if (!destination || !entry) return;
|
|
363
471
|
const moduleCollector = makeModuleCollector();
|
|
364
472
|
result = await esbuild.build({
|
|
365
473
|
entryPoints: [entry],
|
|
@@ -383,7 +491,10 @@ function useEsbuild(props: {
|
|
|
383
491
|
else {
|
|
384
492
|
// nothing really changes here, so let's increment the id
|
|
385
493
|
// to change the return object's identity
|
|
386
|
-
setBundle((
|
|
494
|
+
setBundle((previousBundle) => ({
|
|
495
|
+
...previousBundle,
|
|
496
|
+
id: previousBundle.id + 1,
|
|
497
|
+
}));
|
|
387
498
|
}
|
|
388
499
|
},
|
|
389
500
|
},
|
|
@@ -402,7 +513,12 @@ function useEsbuild(props: {
|
|
|
402
513
|
modules: moduleCollector.modules,
|
|
403
514
|
});
|
|
404
515
|
}
|
|
405
|
-
build()
|
|
516
|
+
build().catch((_err) => {
|
|
517
|
+
// esbuild already logs errors to stderr
|
|
518
|
+
// and we don't want to end the process
|
|
519
|
+
// on build errors anyway
|
|
520
|
+
// so this is a no-op error handler
|
|
521
|
+
});
|
|
406
522
|
return () => {
|
|
407
523
|
result?.stop();
|
|
408
524
|
};
|
|
@@ -417,7 +533,7 @@ function useWorker(props: {
|
|
|
417
533
|
modules: CfModule[];
|
|
418
534
|
accountId: string;
|
|
419
535
|
apiToken: string;
|
|
420
|
-
|
|
536
|
+
bindings: CfWorkerInit["bindings"];
|
|
421
537
|
sitesFolder: void | string;
|
|
422
538
|
port: number;
|
|
423
539
|
compatibilityDate: string | void;
|
|
@@ -431,7 +547,7 @@ function useWorker(props: {
|
|
|
431
547
|
modules,
|
|
432
548
|
accountId,
|
|
433
549
|
apiToken,
|
|
434
|
-
|
|
550
|
+
bindings,
|
|
435
551
|
sitesFolder,
|
|
436
552
|
compatibilityDate,
|
|
437
553
|
compatibilityFlags,
|
|
@@ -439,6 +555,12 @@ function useWorker(props: {
|
|
|
439
555
|
port,
|
|
440
556
|
} = props;
|
|
441
557
|
const [token, setToken] = useState<CfPreviewToken>();
|
|
558
|
+
|
|
559
|
+
// This is the most reliable way to detect whether
|
|
560
|
+
// something's "happened" in our system; We make a ref and
|
|
561
|
+
// mark it once we log our initial message. Refs are vars!
|
|
562
|
+
const startedRef = useRef(false);
|
|
563
|
+
|
|
442
564
|
useEffect(() => {
|
|
443
565
|
async function start() {
|
|
444
566
|
if (!bundle) return;
|
|
@@ -453,10 +575,11 @@ function useWorker(props: {
|
|
|
453
575
|
return;
|
|
454
576
|
}
|
|
455
577
|
|
|
456
|
-
if (
|
|
457
|
-
console.log("⎔ Detected changes, restarting server...");
|
|
458
|
-
} else {
|
|
578
|
+
if (!startedRef.current) {
|
|
459
579
|
console.log("⎔ Starting server...");
|
|
580
|
+
startedRef.current = true;
|
|
581
|
+
} else {
|
|
582
|
+
console.log("⎔ Detected changes, restarting server...");
|
|
460
583
|
}
|
|
461
584
|
|
|
462
585
|
const assets = sitesFolder
|
|
@@ -480,19 +603,23 @@ function useWorker(props: {
|
|
|
480
603
|
type: format || bundle.type === "esm" ? "esm" : "commonjs",
|
|
481
604
|
content,
|
|
482
605
|
},
|
|
483
|
-
modules:
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
606
|
+
modules: modules.concat(
|
|
607
|
+
assets.manifest
|
|
608
|
+
? {
|
|
609
|
+
name: "__STATIC_CONTENT_MANIFEST",
|
|
610
|
+
content: JSON.stringify(assets.manifest),
|
|
611
|
+
type: "text",
|
|
612
|
+
}
|
|
613
|
+
: []
|
|
614
|
+
),
|
|
615
|
+
bindings: {
|
|
616
|
+
...bindings,
|
|
617
|
+
kv_namespaces: (bindings.kv_namespaces || []).concat(
|
|
618
|
+
assets.namespace
|
|
619
|
+
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
620
|
+
: []
|
|
621
|
+
),
|
|
622
|
+
},
|
|
496
623
|
migrations: undefined, // no migrations in dev
|
|
497
624
|
compatibility_date: compatibilityDate,
|
|
498
625
|
compatibility_flags: compatibilityFlags,
|
|
@@ -504,9 +631,12 @@ function useWorker(props: {
|
|
|
504
631
|
apiToken,
|
|
505
632
|
})
|
|
506
633
|
);
|
|
507
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
508
634
|
}
|
|
509
|
-
start()
|
|
635
|
+
start().catch((err) => {
|
|
636
|
+
// we want to log the error, but not end the process
|
|
637
|
+
// since it could recover after the developer fixes whatever's wrong
|
|
638
|
+
console.error("remote worker:", err);
|
|
639
|
+
});
|
|
510
640
|
}, [
|
|
511
641
|
name,
|
|
512
642
|
bundle,
|
|
@@ -518,6 +648,8 @@ function useWorker(props: {
|
|
|
518
648
|
compatibilityDate,
|
|
519
649
|
compatibilityFlags,
|
|
520
650
|
usageModel,
|
|
651
|
+
bindings,
|
|
652
|
+
modules,
|
|
521
653
|
]);
|
|
522
654
|
return token;
|
|
523
655
|
}
|
|
@@ -533,43 +665,53 @@ function useProxy({
|
|
|
533
665
|
}) {
|
|
534
666
|
useEffect(() => {
|
|
535
667
|
if (!token) return;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
+
}
|
|
541
686
|
},
|
|
542
|
-
target: `https://${token.host}`,
|
|
543
|
-
// TODO: log websockets too? validate durables, etc
|
|
544
687
|
});
|
|
545
688
|
|
|
546
|
-
|
|
547
|
-
publicRoot &&
|
|
548
|
-
serveStatic(publicRoot, {
|
|
549
|
-
cacheControl: false,
|
|
550
|
-
});
|
|
551
|
-
const server = http
|
|
552
|
-
.createServer((req, res) => {
|
|
553
|
-
if (publicRoot) {
|
|
554
|
-
servePublic(req, res, () => {
|
|
555
|
-
proxy.web(req, res);
|
|
556
|
-
});
|
|
557
|
-
} else {
|
|
558
|
-
proxy.web(req, res);
|
|
559
|
-
}
|
|
560
|
-
})
|
|
561
|
-
.listen(port); // TODO: custom port
|
|
689
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
562
690
|
|
|
563
|
-
proxy.
|
|
691
|
+
const server = proxy.listen(port);
|
|
692
|
+
|
|
693
|
+
// TODO(soon): refactor logging format into its own function
|
|
694
|
+
proxy.on("request", function (req, res) {
|
|
564
695
|
// log all requests
|
|
565
696
|
console.log(
|
|
566
697
|
new Date().toLocaleTimeString(),
|
|
567
698
|
req.method,
|
|
568
699
|
req.url,
|
|
569
|
-
res.statusCode
|
|
700
|
+
res.statusCode
|
|
570
701
|
);
|
|
571
702
|
});
|
|
572
|
-
|
|
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
|
+
});
|
|
573
715
|
|
|
574
716
|
return () => {
|
|
575
717
|
proxy.close();
|
|
@@ -650,12 +792,14 @@ function useTunnel(toggle: boolean) {
|
|
|
650
792
|
});
|
|
651
793
|
|
|
652
794
|
const hostName = await findTunnelHostname();
|
|
653
|
-
clipboardy.write(hostName);
|
|
795
|
+
await clipboardy.write(hostName);
|
|
654
796
|
console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
|
|
655
797
|
}
|
|
656
798
|
}
|
|
657
799
|
|
|
658
|
-
startTunnel()
|
|
800
|
+
startTunnel().catch((err) => {
|
|
801
|
+
console.error("tunnel:", err);
|
|
802
|
+
});
|
|
659
803
|
|
|
660
804
|
return () => {
|
|
661
805
|
if (tunnel.current) {
|
|
@@ -677,38 +821,31 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
677
821
|
// UGH, we should put port in context instead
|
|
678
822
|
const [toggles, setToggles] = useState(initial);
|
|
679
823
|
useInput(
|
|
680
|
-
(
|
|
824
|
+
async (
|
|
681
825
|
input,
|
|
682
826
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
683
827
|
key
|
|
684
828
|
) => {
|
|
685
829
|
switch (input) {
|
|
686
830
|
case "b": // open browser
|
|
687
|
-
open(
|
|
688
|
-
`http://localhost:${port}/`
|
|
689
|
-
// {
|
|
690
|
-
// app: {
|
|
691
|
-
// name: open.apps.chrome, // TODO: fallback on other browsers
|
|
692
|
-
// },
|
|
693
|
-
// }
|
|
694
|
-
);
|
|
831
|
+
await open(`http://localhost:${port}/`);
|
|
695
832
|
break;
|
|
696
833
|
case "d": // toggle inspector
|
|
697
|
-
open(
|
|
834
|
+
await open(
|
|
698
835
|
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws`
|
|
699
|
-
// {
|
|
700
|
-
// app: {
|
|
701
|
-
// name: open.apps.chrome,
|
|
702
|
-
// // todo - add firefox and edge fallbacks
|
|
703
|
-
// },
|
|
704
|
-
// }
|
|
705
836
|
);
|
|
706
837
|
break;
|
|
707
838
|
case "s": // toggle tunnel
|
|
708
|
-
setToggles((
|
|
839
|
+
setToggles((previousToggles) => ({
|
|
840
|
+
...previousToggles,
|
|
841
|
+
tunnel: !previousToggles.tunnel,
|
|
842
|
+
}));
|
|
709
843
|
break;
|
|
710
844
|
case "l": // toggle local
|
|
711
|
-
setToggles((
|
|
845
|
+
setToggles((previousToggles) => ({
|
|
846
|
+
...previousToggles,
|
|
847
|
+
local: !previousToggles.local,
|
|
848
|
+
}));
|
|
712
849
|
break;
|
|
713
850
|
case "q": // shut down
|
|
714
851
|
case "x": // shut down
|
|
@@ -722,3 +859,21 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
722
859
|
);
|
|
723
860
|
return toggles;
|
|
724
861
|
}
|
|
862
|
+
|
|
863
|
+
function ErrorFallback(props: {
|
|
864
|
+
error: Error;
|
|
865
|
+
resetErrorBoundary: () => void;
|
|
866
|
+
}) {
|
|
867
|
+
useEffect(() => {
|
|
868
|
+
console.error(props.error);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
});
|
|
871
|
+
return (
|
|
872
|
+
<Box>
|
|
873
|
+
<Text>Something went wrong:</Text>
|
|
874
|
+
<Text>{props.error.message}</Text>
|
|
875
|
+
</Box>
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export default withErrorBoundary(Dev, { FallbackComponent: ErrorFallback });
|
package/src/dialogs.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Box, Text, useInput, render } from "ink";
|
|
|
4
4
|
import TextInput from "ink-text-input";
|
|
5
5
|
type ConfirmProps = {
|
|
6
6
|
text: string;
|
|
7
|
-
onConfirm: (answer: boolean) => void
|
|
7
|
+
onConfirm: (answer: boolean) => void;
|
|
8
8
|
};
|
|
9
9
|
function Confirm(props: ConfirmProps) {
|
|
10
10
|
useInput((input: string) => {
|
|
@@ -67,19 +67,11 @@ export async function prompt(text: string, type: "text" | "password" = "text") {
|
|
|
67
67
|
<Prompt
|
|
68
68
|
text={text}
|
|
69
69
|
type={type}
|
|
70
|
-
onSubmit={(
|
|
70
|
+
onSubmit={(inputText) => {
|
|
71
71
|
unmount();
|
|
72
|
-
resolve(
|
|
72
|
+
resolve(inputText);
|
|
73
73
|
}}
|
|
74
74
|
/>
|
|
75
75
|
);
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
// this one feels a little non "standard"
|
|
80
|
-
// export async function select(
|
|
81
|
-
// title: string,
|
|
82
|
-
// choices: { label: string; value: string }[]
|
|
83
|
-
// ): Promise<{ label: string; value: string }>{
|
|
84
|
-
|
|
85
|
-
// }
|