wrangler 0.0.0-ece06ea → 0.0.0-eebdb6c
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 +22 -15
- package/pages/functions/buildWorker.ts +57 -0
- package/pages/functions/filepath-routing.test.ts +32 -0
- package/pages/functions/filepath-routing.ts +225 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +135 -0
- package/src/__tests__/index.test.ts +254 -120
- package/src/__tests__/jest.setup.ts +22 -0
- package/src/__tests__/kv.test.ts +1098 -0
- package/src/__tests__/mock-cfetch.ts +171 -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 +70 -97
- package/src/api/inspect.ts +0 -11
- package/src/api/preview.ts +29 -24
- package/src/api/worker.ts +34 -40
- package/src/cfetch/index.ts +103 -0
- package/src/cfetch/internal.ts +69 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +14 -7
- package/src/dev.tsx +274 -131
- package/src/dialogs.tsx +4 -12
- package/src/index.tsx +304 -245
- package/src/kv.tsx +101 -45
- package/src/module-collection.ts +64 -0
- package/src/pages.tsx +681 -104
- package/src/proxy.ts +103 -0
- package/src/publish.ts +54 -58
- package/src/sites.tsx +5 -6
- package/src/tail.tsx +5 -3
- package/src/user.tsx +9 -16
- package/wrangler-dist/cli.js +36633 -49770
- 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/cfetch.ts +0 -72
- package/src/util/fetch.ts +0 -74
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,13 +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
|
-
import
|
|
26
|
-
import
|
|
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";
|
|
27
29
|
|
|
28
30
|
type CfScriptFormat = void | "modules" | "service-worker";
|
|
29
31
|
|
|
@@ -36,16 +38,20 @@ type Props = {
|
|
|
36
38
|
initialMode: "local" | "remote";
|
|
37
39
|
jsxFactory: void | string;
|
|
38
40
|
jsxFragment: void | string;
|
|
39
|
-
|
|
41
|
+
bindings: CfWorkerInit["bindings"];
|
|
40
42
|
public: void | string;
|
|
41
43
|
site: void | string;
|
|
42
44
|
compatibilityDate: void | string;
|
|
43
45
|
compatibilityFlags: void | string[];
|
|
44
46
|
usageModel: void | "bundled" | "unbound";
|
|
45
|
-
|
|
47
|
+
buildCommand: {
|
|
48
|
+
command?: undefined | string;
|
|
49
|
+
cwd?: undefined | string;
|
|
50
|
+
watch_dir?: undefined | string;
|
|
51
|
+
};
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
function Dev(props: Props): JSX.Element {
|
|
49
55
|
if (props.public && props.format === "service-worker") {
|
|
50
56
|
throw new Error(
|
|
51
57
|
"You cannot use the service worker format with a `public` directory."
|
|
@@ -55,13 +61,17 @@ export function Dev(props: Props): JSX.Element {
|
|
|
55
61
|
const apiToken = getAPIToken();
|
|
56
62
|
const directory = useTmpDir();
|
|
57
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
|
+
|
|
58
69
|
const bundle = useEsbuild({
|
|
59
|
-
entry
|
|
70
|
+
entry,
|
|
60
71
|
destination: directory,
|
|
61
72
|
staticRoot: props.public,
|
|
62
73
|
jsxFactory: props.jsxFactory,
|
|
63
74
|
jsxFragment: props.jsxFragment,
|
|
64
|
-
polyfillNode: props.polyfillNode,
|
|
65
75
|
});
|
|
66
76
|
if (bundle && bundle.type === "commonjs" && !props.format && props.public) {
|
|
67
77
|
throw new Error(
|
|
@@ -89,7 +99,7 @@ export function Dev(props: Props): JSX.Element {
|
|
|
89
99
|
name={props.name}
|
|
90
100
|
bundle={bundle}
|
|
91
101
|
format={props.format}
|
|
92
|
-
|
|
102
|
+
bindings={props.bindings}
|
|
93
103
|
site={props.site}
|
|
94
104
|
public={props.public}
|
|
95
105
|
port={props.port}
|
|
@@ -101,7 +111,7 @@ export function Dev(props: Props): JSX.Element {
|
|
|
101
111
|
format={props.format}
|
|
102
112
|
accountId={props.accountId}
|
|
103
113
|
apiToken={apiToken}
|
|
104
|
-
|
|
114
|
+
bindings={props.bindings}
|
|
105
115
|
site={props.site}
|
|
106
116
|
public={props.public}
|
|
107
117
|
port={props.port}
|
|
@@ -162,7 +172,7 @@ function Remote(props: {
|
|
|
162
172
|
port: number;
|
|
163
173
|
accountId: void | string;
|
|
164
174
|
apiToken: void | string;
|
|
165
|
-
|
|
175
|
+
bindings: CfWorkerInit["bindings"];
|
|
166
176
|
compatibilityDate: string | void;
|
|
167
177
|
compatibilityFlags: void | string[];
|
|
168
178
|
usageModel: void | "bundled" | "unbound";
|
|
@@ -173,10 +183,10 @@ function Remote(props: {
|
|
|
173
183
|
name: props.name,
|
|
174
184
|
bundle: props.bundle,
|
|
175
185
|
format: props.format,
|
|
176
|
-
modules: [],
|
|
186
|
+
modules: props.bundle ? props.bundle.modules : [],
|
|
177
187
|
accountId: props.accountId,
|
|
178
188
|
apiToken: props.apiToken,
|
|
179
|
-
|
|
189
|
+
bindings: props.bindings,
|
|
180
190
|
sitesFolder: props.site,
|
|
181
191
|
port: props.port,
|
|
182
192
|
compatibilityDate: props.compatibilityDate,
|
|
@@ -193,7 +203,7 @@ function Local(props: {
|
|
|
193
203
|
name: void | string;
|
|
194
204
|
bundle: EsbuildBundle | void;
|
|
195
205
|
format: CfScriptFormat;
|
|
196
|
-
|
|
206
|
+
bindings: CfWorkerInit["bindings"];
|
|
197
207
|
public: void | string;
|
|
198
208
|
site: void | string;
|
|
199
209
|
port: number;
|
|
@@ -202,7 +212,7 @@ function Local(props: {
|
|
|
202
212
|
name: props.name,
|
|
203
213
|
bundle: props.bundle,
|
|
204
214
|
format: props.format,
|
|
205
|
-
|
|
215
|
+
bindings: props.bindings,
|
|
206
216
|
port: props.port,
|
|
207
217
|
});
|
|
208
218
|
useInspector(inspectorUrl);
|
|
@@ -213,11 +223,11 @@ function useLocalWorker(props: {
|
|
|
213
223
|
name: void | string;
|
|
214
224
|
bundle: EsbuildBundle | void;
|
|
215
225
|
format: CfScriptFormat;
|
|
216
|
-
|
|
226
|
+
bindings: CfWorkerInit["bindings"];
|
|
217
227
|
port: number;
|
|
218
228
|
}) {
|
|
219
229
|
// TODO: pass vars via command line
|
|
220
|
-
const { bundle, format,
|
|
230
|
+
const { bundle, format, bindings, port } = props;
|
|
221
231
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
222
232
|
const removeSignalExitListener = useRef<() => void>();
|
|
223
233
|
const [inspectorUrl, setInspectorUrl] = useState<string | void>();
|
|
@@ -236,6 +246,7 @@ function useLocalWorker(props: {
|
|
|
236
246
|
}
|
|
237
247
|
|
|
238
248
|
console.log("⎔ Starting a local server...");
|
|
249
|
+
// TODO: just use execa for this
|
|
239
250
|
local.current = spawn("node", [
|
|
240
251
|
"--experimental-vm-modules",
|
|
241
252
|
"--inspect",
|
|
@@ -253,20 +264,17 @@ function useLocalWorker(props: {
|
|
|
253
264
|
"--kv-persist",
|
|
254
265
|
"--cache-persist",
|
|
255
266
|
"--do-persist",
|
|
256
|
-
...Object.entries(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
.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
|
+
),
|
|
270
278
|
"--modules",
|
|
271
279
|
format ||
|
|
272
280
|
(bundle.type === "esm" ? "modules" : "service-worker") === "modules"
|
|
@@ -281,21 +289,32 @@ function useLocalWorker(props: {
|
|
|
281
289
|
}
|
|
282
290
|
});
|
|
283
291
|
|
|
284
|
-
local.current.stdout.on("data", (
|
|
285
|
-
|
|
292
|
+
local.current.stdout.on("data", (data: Buffer) => {
|
|
293
|
+
console.log(`${data.toString()}`);
|
|
286
294
|
});
|
|
287
295
|
|
|
288
|
-
local.current.stderr.on("data", (data:
|
|
289
|
-
|
|
296
|
+
local.current.stderr.on("data", (data: Buffer) => {
|
|
297
|
+
console.error(`${data.toString()}`);
|
|
290
298
|
const matches =
|
|
291
299
|
/Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
|
|
292
|
-
data
|
|
300
|
+
data.toString()
|
|
293
301
|
);
|
|
294
302
|
if (matches) {
|
|
295
303
|
setInspectorUrl(matches[1]);
|
|
296
304
|
}
|
|
297
305
|
});
|
|
298
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
|
+
|
|
299
318
|
removeSignalExitListener.current = onExit((_code, _signal) => {
|
|
300
319
|
console.log("⎔ Shutting down local server.");
|
|
301
320
|
local.current?.kill();
|
|
@@ -303,7 +322,9 @@ function useLocalWorker(props: {
|
|
|
303
322
|
});
|
|
304
323
|
}
|
|
305
324
|
|
|
306
|
-
startLocalWorker()
|
|
325
|
+
startLocalWorker().catch((err) => {
|
|
326
|
+
console.error("local worker:", err);
|
|
327
|
+
});
|
|
307
328
|
|
|
308
329
|
return () => {
|
|
309
330
|
if (local.current) {
|
|
@@ -314,12 +335,20 @@ function useLocalWorker(props: {
|
|
|
314
335
|
removeSignalExitListener.current = undefined;
|
|
315
336
|
}
|
|
316
337
|
};
|
|
317
|
-
}, [
|
|
338
|
+
}, [
|
|
339
|
+
bundle,
|
|
340
|
+
format,
|
|
341
|
+
port,
|
|
342
|
+
bindings.durable_objects?.bindings,
|
|
343
|
+
bindings.kv_namespaces,
|
|
344
|
+
bindings.vars,
|
|
345
|
+
]);
|
|
318
346
|
return { inspectorUrl };
|
|
319
347
|
}
|
|
320
348
|
|
|
321
349
|
function useTmpDir(): string | void {
|
|
322
350
|
const [directory, setDirectory] = useState<DirectoryResult>();
|
|
351
|
+
const handleError = useErrorHandler();
|
|
323
352
|
useEffect(() => {
|
|
324
353
|
let dir: DirectoryResult;
|
|
325
354
|
async function create() {
|
|
@@ -329,52 +358,119 @@ function useTmpDir(): string | void {
|
|
|
329
358
|
return;
|
|
330
359
|
} catch (err) {
|
|
331
360
|
console.error("failed to create tmp dir");
|
|
332
|
-
console.error(err);
|
|
333
361
|
throw err;
|
|
334
362
|
}
|
|
335
363
|
}
|
|
336
|
-
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
|
+
});
|
|
337
369
|
return () => {
|
|
338
|
-
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
|
+
});
|
|
339
375
|
};
|
|
340
|
-
}, []);
|
|
376
|
+
}, [handleError]);
|
|
341
377
|
return directory?.path;
|
|
342
378
|
}
|
|
343
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
|
+
|
|
344
449
|
type EsbuildBundle = {
|
|
345
450
|
id: number;
|
|
346
451
|
path: string;
|
|
347
452
|
entry: string;
|
|
348
453
|
type: "esm" | "commonjs";
|
|
349
454
|
exports: string[];
|
|
455
|
+
modules: CfModule[];
|
|
350
456
|
};
|
|
351
457
|
|
|
352
458
|
function useEsbuild(props: {
|
|
353
|
-
entry: string;
|
|
459
|
+
entry: void | string;
|
|
354
460
|
destination: string | void;
|
|
355
461
|
staticRoot: void | string;
|
|
356
462
|
jsxFactory: string | void;
|
|
357
463
|
jsxFragment: string | void;
|
|
358
|
-
polyfillNode: boolean | void;
|
|
359
464
|
}): EsbuildBundle | void {
|
|
360
|
-
const {
|
|
361
|
-
entry,
|
|
362
|
-
destination,
|
|
363
|
-
staticRoot,
|
|
364
|
-
jsxFactory,
|
|
365
|
-
jsxFragment,
|
|
366
|
-
polyfillNode,
|
|
367
|
-
} = props;
|
|
465
|
+
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
368
466
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
369
467
|
useEffect(() => {
|
|
370
468
|
let result: esbuild.BuildResult;
|
|
371
469
|
async function build() {
|
|
372
|
-
if (!destination) return;
|
|
470
|
+
if (!destination || !entry) return;
|
|
471
|
+
const moduleCollector = makeModuleCollector();
|
|
373
472
|
result = await esbuild.build({
|
|
374
473
|
entryPoints: [entry],
|
|
375
|
-
define: {
|
|
376
|
-
...(polyfillNode && { global: "globalThis" }),
|
|
377
|
-
},
|
|
378
474
|
bundle: true,
|
|
379
475
|
outdir: destination,
|
|
380
476
|
metafile: true,
|
|
@@ -383,12 +479,11 @@ function useEsbuild(props: {
|
|
|
383
479
|
loader: {
|
|
384
480
|
".js": "jsx",
|
|
385
481
|
},
|
|
386
|
-
plugins: polyfillNode
|
|
387
|
-
? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
|
|
388
|
-
: undefined,
|
|
389
482
|
...(jsxFactory && { jsxFactory }),
|
|
390
483
|
...(jsxFragment && { jsxFragment }),
|
|
391
484
|
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
485
|
+
conditions: ["worker", "browser"],
|
|
486
|
+
plugins: [moduleCollector.plugin],
|
|
392
487
|
// TODO: import.meta.url
|
|
393
488
|
watch: {
|
|
394
489
|
async onRebuild(error) {
|
|
@@ -396,7 +491,10 @@ function useEsbuild(props: {
|
|
|
396
491
|
else {
|
|
397
492
|
// nothing really changes here, so let's increment the id
|
|
398
493
|
// to change the return object's identity
|
|
399
|
-
setBundle((
|
|
494
|
+
setBundle((previousBundle) => ({
|
|
495
|
+
...previousBundle,
|
|
496
|
+
id: previousBundle.id + 1,
|
|
497
|
+
}));
|
|
400
498
|
}
|
|
401
499
|
},
|
|
402
500
|
},
|
|
@@ -412,13 +510,19 @@ function useEsbuild(props: {
|
|
|
412
510
|
path: chunks[0],
|
|
413
511
|
type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
|
|
414
512
|
exports: chunks[1].exports,
|
|
513
|
+
modules: moduleCollector.modules,
|
|
415
514
|
});
|
|
416
515
|
}
|
|
417
|
-
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
|
+
});
|
|
418
522
|
return () => {
|
|
419
523
|
result?.stop();
|
|
420
524
|
};
|
|
421
|
-
}, [entry, destination, staticRoot, jsxFactory, jsxFragment
|
|
525
|
+
}, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
|
|
422
526
|
return bundle;
|
|
423
527
|
}
|
|
424
528
|
|
|
@@ -429,7 +533,7 @@ function useWorker(props: {
|
|
|
429
533
|
modules: CfModule[];
|
|
430
534
|
accountId: string;
|
|
431
535
|
apiToken: string;
|
|
432
|
-
|
|
536
|
+
bindings: CfWorkerInit["bindings"];
|
|
433
537
|
sitesFolder: void | string;
|
|
434
538
|
port: number;
|
|
435
539
|
compatibilityDate: string | void;
|
|
@@ -443,7 +547,7 @@ function useWorker(props: {
|
|
|
443
547
|
modules,
|
|
444
548
|
accountId,
|
|
445
549
|
apiToken,
|
|
446
|
-
|
|
550
|
+
bindings,
|
|
447
551
|
sitesFolder,
|
|
448
552
|
compatibilityDate,
|
|
449
553
|
compatibilityFlags,
|
|
@@ -451,6 +555,12 @@ function useWorker(props: {
|
|
|
451
555
|
port,
|
|
452
556
|
} = props;
|
|
453
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
|
+
|
|
454
564
|
useEffect(() => {
|
|
455
565
|
async function start() {
|
|
456
566
|
if (!bundle) return;
|
|
@@ -465,10 +575,11 @@ function useWorker(props: {
|
|
|
465
575
|
return;
|
|
466
576
|
}
|
|
467
577
|
|
|
468
|
-
if (
|
|
469
|
-
console.log("⎔ Detected changes, restarting server...");
|
|
470
|
-
} else {
|
|
578
|
+
if (!startedRef.current) {
|
|
471
579
|
console.log("⎔ Starting server...");
|
|
580
|
+
startedRef.current = true;
|
|
581
|
+
} else {
|
|
582
|
+
console.log("⎔ Detected changes, restarting server...");
|
|
472
583
|
}
|
|
473
584
|
|
|
474
585
|
const assets = sitesFolder
|
|
@@ -486,25 +597,29 @@ function useWorker(props: {
|
|
|
486
597
|
|
|
487
598
|
const content = await readFile(bundle.path, "utf-8");
|
|
488
599
|
const init: CfWorkerInit = {
|
|
489
|
-
name
|
|
600
|
+
name,
|
|
490
601
|
main: {
|
|
491
602
|
name: path.basename(bundle.path),
|
|
492
603
|
type: format || bundle.type === "esm" ? "esm" : "commonjs",
|
|
493
604
|
content,
|
|
494
605
|
},
|
|
495
|
-
modules:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
},
|
|
508
623
|
migrations: undefined, // no migrations in dev
|
|
509
624
|
compatibility_date: compatibilityDate,
|
|
510
625
|
compatibility_flags: compatibilityFlags,
|
|
@@ -516,9 +631,12 @@ function useWorker(props: {
|
|
|
516
631
|
apiToken,
|
|
517
632
|
})
|
|
518
633
|
);
|
|
519
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
520
634
|
}
|
|
521
|
-
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
|
+
});
|
|
522
640
|
}, [
|
|
523
641
|
name,
|
|
524
642
|
bundle,
|
|
@@ -530,6 +648,8 @@ function useWorker(props: {
|
|
|
530
648
|
compatibilityDate,
|
|
531
649
|
compatibilityFlags,
|
|
532
650
|
usageModel,
|
|
651
|
+
bindings,
|
|
652
|
+
modules,
|
|
533
653
|
]);
|
|
534
654
|
return token;
|
|
535
655
|
}
|
|
@@ -545,43 +665,53 @@ function useProxy({
|
|
|
545
665
|
}) {
|
|
546
666
|
useEffect(() => {
|
|
547
667
|
if (!token) return;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
}
|
|
553
686
|
},
|
|
554
|
-
target: `https://${token.host}`,
|
|
555
|
-
// TODO: log websockets too? validate durables, etc
|
|
556
687
|
});
|
|
557
688
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
cacheControl: false,
|
|
562
|
-
});
|
|
563
|
-
const server = http
|
|
564
|
-
.createServer((req, res) => {
|
|
565
|
-
if (publicRoot) {
|
|
566
|
-
servePublic(req, res, () => {
|
|
567
|
-
proxy.web(req, res);
|
|
568
|
-
});
|
|
569
|
-
} else {
|
|
570
|
-
proxy.web(req, res);
|
|
571
|
-
}
|
|
572
|
-
})
|
|
573
|
-
.listen(port); // TODO: custom port
|
|
689
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
690
|
+
|
|
691
|
+
const server = proxy.listen(port);
|
|
574
692
|
|
|
575
|
-
|
|
693
|
+
// TODO(soon): refactor logging format into its own function
|
|
694
|
+
proxy.on("request", function (req, res) {
|
|
576
695
|
// log all requests
|
|
577
696
|
console.log(
|
|
578
697
|
new Date().toLocaleTimeString(),
|
|
579
698
|
req.method,
|
|
580
699
|
req.url,
|
|
581
|
-
res.statusCode
|
|
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)"
|
|
582
710
|
);
|
|
583
711
|
});
|
|
584
|
-
|
|
712
|
+
proxy.on("error", (err) => {
|
|
713
|
+
console.error(new Date().toLocaleTimeString(), err);
|
|
714
|
+
});
|
|
585
715
|
|
|
586
716
|
return () => {
|
|
587
717
|
proxy.close();
|
|
@@ -662,12 +792,14 @@ function useTunnel(toggle: boolean) {
|
|
|
662
792
|
});
|
|
663
793
|
|
|
664
794
|
const hostName = await findTunnelHostname();
|
|
665
|
-
clipboardy.write(hostName);
|
|
795
|
+
await clipboardy.write(hostName);
|
|
666
796
|
console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
|
|
667
797
|
}
|
|
668
798
|
}
|
|
669
799
|
|
|
670
|
-
startTunnel()
|
|
800
|
+
startTunnel().catch((err) => {
|
|
801
|
+
console.error("tunnel:", err);
|
|
802
|
+
});
|
|
671
803
|
|
|
672
804
|
return () => {
|
|
673
805
|
if (tunnel.current) {
|
|
@@ -689,38 +821,31 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
689
821
|
// UGH, we should put port in context instead
|
|
690
822
|
const [toggles, setToggles] = useState(initial);
|
|
691
823
|
useInput(
|
|
692
|
-
(
|
|
824
|
+
async (
|
|
693
825
|
input,
|
|
694
826
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
695
827
|
key
|
|
696
828
|
) => {
|
|
697
829
|
switch (input) {
|
|
698
830
|
case "b": // open browser
|
|
699
|
-
open(
|
|
700
|
-
`http://localhost:${port}/`
|
|
701
|
-
// {
|
|
702
|
-
// app: {
|
|
703
|
-
// name: open.apps.chrome, // TODO: fallback on other browsers
|
|
704
|
-
// },
|
|
705
|
-
// }
|
|
706
|
-
);
|
|
831
|
+
await open(`http://localhost:${port}/`);
|
|
707
832
|
break;
|
|
708
833
|
case "d": // toggle inspector
|
|
709
|
-
open(
|
|
834
|
+
await open(
|
|
710
835
|
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws`
|
|
711
|
-
// {
|
|
712
|
-
// app: {
|
|
713
|
-
// name: open.apps.chrome,
|
|
714
|
-
// // todo - add firefox and edge fallbacks
|
|
715
|
-
// },
|
|
716
|
-
// }
|
|
717
836
|
);
|
|
718
837
|
break;
|
|
719
838
|
case "s": // toggle tunnel
|
|
720
|
-
setToggles((
|
|
839
|
+
setToggles((previousToggles) => ({
|
|
840
|
+
...previousToggles,
|
|
841
|
+
tunnel: !previousToggles.tunnel,
|
|
842
|
+
}));
|
|
721
843
|
break;
|
|
722
844
|
case "l": // toggle local
|
|
723
|
-
setToggles((
|
|
845
|
+
setToggles((previousToggles) => ({
|
|
846
|
+
...previousToggles,
|
|
847
|
+
local: !previousToggles.local,
|
|
848
|
+
}));
|
|
724
849
|
break;
|
|
725
850
|
case "q": // shut down
|
|
726
851
|
case "x": // shut down
|
|
@@ -734,3 +859,21 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
734
859
|
);
|
|
735
860
|
return toggles;
|
|
736
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 });
|