wrangler 0.0.0-e14e589 → 0.0.0-e1c615f
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/README.md +51 -0
- package/package.json +39 -26
- package/pages/functions/buildWorker.ts +57 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +221 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +137 -0
- package/src/__tests__/dev.test.tsx +66 -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 +78 -72
- package/src/api/inspect.ts +0 -11
- package/src/api/preview.ts +29 -24
- package/src/api/worker.ts +46 -31
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +69 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +39 -4
- package/src/dev.tsx +332 -128
- package/src/dialogs.tsx +4 -12
- package/src/index.tsx +397 -222
- package/src/kv.tsx +101 -45
- package/src/module-collection.ts +64 -0
- package/src/pages.tsx +839 -211
- package/src/proxy.ts +103 -0
- package/src/publish.ts +117 -45
- package/src/sites.tsx +6 -7
- package/src/tail.tsx +6 -4
- package/src/user.tsx +9 -16
- package/wrangler-dist/cli.js +123794 -528
- 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,15 +1,15 @@
|
|
|
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";
|
|
7
|
-
import { Box, Text, useInput } from "ink";
|
|
7
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
8
8
|
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,27 +17,41 @@ 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";
|
|
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";
|
|
25
29
|
|
|
26
30
|
type CfScriptFormat = void | "modules" | "service-worker";
|
|
27
31
|
|
|
28
|
-
type
|
|
32
|
+
export type DevProps = {
|
|
29
33
|
name?: string;
|
|
30
34
|
entry: string;
|
|
31
35
|
port?: number;
|
|
32
36
|
format: CfScriptFormat;
|
|
33
37
|
accountId: void | string;
|
|
34
38
|
initialMode: "local" | "remote";
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
jsxFactory: void | string;
|
|
40
|
+
jsxFragment: void | string;
|
|
41
|
+
bindings: CfWorkerInit["bindings"];
|
|
42
|
+
public: void | string;
|
|
43
|
+
site: void | string;
|
|
44
|
+
compatibilityDate: void | string;
|
|
45
|
+
compatibilityFlags: void | string[];
|
|
46
|
+
usageModel: void | "bundled" | "unbound";
|
|
47
|
+
buildCommand: {
|
|
48
|
+
command?: undefined | string;
|
|
49
|
+
cwd?: undefined | string;
|
|
50
|
+
watch_dir?: undefined | string;
|
|
51
|
+
};
|
|
38
52
|
};
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
function Dev(props: DevProps): JSX.Element {
|
|
41
55
|
if (props.public && props.format === "service-worker") {
|
|
42
56
|
throw new Error(
|
|
43
57
|
"You cannot use the service worker format with a `public` directory."
|
|
@@ -47,7 +61,18 @@ export function Dev(props: Props): JSX.Element {
|
|
|
47
61
|
const apiToken = getAPIToken();
|
|
48
62
|
const directory = useTmpDir();
|
|
49
63
|
|
|
50
|
-
|
|
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
|
+
|
|
69
|
+
const bundle = useEsbuild({
|
|
70
|
+
entry,
|
|
71
|
+
destination: directory,
|
|
72
|
+
staticRoot: props.public,
|
|
73
|
+
jsxFactory: props.jsxFactory,
|
|
74
|
+
jsxFragment: props.jsxFragment,
|
|
75
|
+
});
|
|
51
76
|
if (bundle && bundle.type === "commonjs" && !props.format && props.public) {
|
|
52
77
|
throw new Error(
|
|
53
78
|
"You cannot use the service worker format with a `public` directory."
|
|
@@ -74,7 +99,7 @@ export function Dev(props: Props): JSX.Element {
|
|
|
74
99
|
name={props.name}
|
|
75
100
|
bundle={bundle}
|
|
76
101
|
format={props.format}
|
|
77
|
-
|
|
102
|
+
bindings={props.bindings}
|
|
78
103
|
site={props.site}
|
|
79
104
|
public={props.public}
|
|
80
105
|
port={props.port}
|
|
@@ -86,10 +111,13 @@ export function Dev(props: Props): JSX.Element {
|
|
|
86
111
|
format={props.format}
|
|
87
112
|
accountId={props.accountId}
|
|
88
113
|
apiToken={apiToken}
|
|
89
|
-
|
|
114
|
+
bindings={props.bindings}
|
|
90
115
|
site={props.site}
|
|
91
116
|
public={props.public}
|
|
92
117
|
port={props.port}
|
|
118
|
+
compatibilityDate={props.compatibilityDate}
|
|
119
|
+
compatibilityFlags={props.compatibilityFlags}
|
|
120
|
+
usageModel={props.usageModel}
|
|
93
121
|
/>
|
|
94
122
|
)}
|
|
95
123
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
@@ -144,7 +172,10 @@ function Remote(props: {
|
|
|
144
172
|
port: number;
|
|
145
173
|
accountId: void | string;
|
|
146
174
|
apiToken: void | string;
|
|
147
|
-
|
|
175
|
+
bindings: CfWorkerInit["bindings"];
|
|
176
|
+
compatibilityDate: string | void;
|
|
177
|
+
compatibilityFlags: void | string[];
|
|
178
|
+
usageModel: void | "bundled" | "unbound";
|
|
148
179
|
}) {
|
|
149
180
|
assert(props.accountId, "accountId is required");
|
|
150
181
|
assert(props.apiToken, "apiToken is required");
|
|
@@ -152,12 +183,15 @@ function Remote(props: {
|
|
|
152
183
|
name: props.name,
|
|
153
184
|
bundle: props.bundle,
|
|
154
185
|
format: props.format,
|
|
155
|
-
modules: [],
|
|
186
|
+
modules: props.bundle ? props.bundle.modules : [],
|
|
156
187
|
accountId: props.accountId,
|
|
157
188
|
apiToken: props.apiToken,
|
|
158
|
-
|
|
189
|
+
bindings: props.bindings,
|
|
159
190
|
sitesFolder: props.site,
|
|
160
191
|
port: props.port,
|
|
192
|
+
compatibilityDate: props.compatibilityDate,
|
|
193
|
+
compatibilityFlags: props.compatibilityFlags,
|
|
194
|
+
usageModel: props.usageModel,
|
|
161
195
|
});
|
|
162
196
|
|
|
163
197
|
useProxy({ token, publicRoot: props.public, port: props.port });
|
|
@@ -169,7 +203,7 @@ function Local(props: {
|
|
|
169
203
|
name: void | string;
|
|
170
204
|
bundle: EsbuildBundle | void;
|
|
171
205
|
format: CfScriptFormat;
|
|
172
|
-
|
|
206
|
+
bindings: CfWorkerInit["bindings"];
|
|
173
207
|
public: void | string;
|
|
174
208
|
site: void | string;
|
|
175
209
|
port: number;
|
|
@@ -178,7 +212,7 @@ function Local(props: {
|
|
|
178
212
|
name: props.name,
|
|
179
213
|
bundle: props.bundle,
|
|
180
214
|
format: props.format,
|
|
181
|
-
|
|
215
|
+
bindings: props.bindings,
|
|
182
216
|
port: props.port,
|
|
183
217
|
});
|
|
184
218
|
useInspector(inspectorUrl);
|
|
@@ -189,11 +223,11 @@ function useLocalWorker(props: {
|
|
|
189
223
|
name: void | string;
|
|
190
224
|
bundle: EsbuildBundle | void;
|
|
191
225
|
format: CfScriptFormat;
|
|
192
|
-
|
|
226
|
+
bindings: CfWorkerInit["bindings"];
|
|
193
227
|
port: number;
|
|
194
228
|
}) {
|
|
195
229
|
// TODO: pass vars via command line
|
|
196
|
-
const { bundle, format,
|
|
230
|
+
const { bundle, format, bindings, port } = props;
|
|
197
231
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
198
232
|
const removeSignalExitListener = useRef<() => void>();
|
|
199
233
|
const [inspectorUrl, setInspectorUrl] = useState<string | void>();
|
|
@@ -212,6 +246,7 @@ function useLocalWorker(props: {
|
|
|
212
246
|
}
|
|
213
247
|
|
|
214
248
|
console.log("⎔ Starting a local server...");
|
|
249
|
+
// TODO: just use execa for this
|
|
215
250
|
local.current = spawn("node", [
|
|
216
251
|
"--experimental-vm-modules",
|
|
217
252
|
"--inspect",
|
|
@@ -228,18 +263,18 @@ function useLocalWorker(props: {
|
|
|
228
263
|
port.toString(),
|
|
229
264
|
"--kv-persist",
|
|
230
265
|
"--cache-persist",
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
266
|
+
"--do-persist",
|
|
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
|
+
),
|
|
243
278
|
"--modules",
|
|
244
279
|
format ||
|
|
245
280
|
(bundle.type === "esm" ? "modules" : "service-worker") === "modules"
|
|
@@ -254,29 +289,42 @@ function useLocalWorker(props: {
|
|
|
254
289
|
}
|
|
255
290
|
});
|
|
256
291
|
|
|
257
|
-
local.current.stdout.on("data", (data:
|
|
258
|
-
|
|
292
|
+
local.current.stdout.on("data", (data: Buffer) => {
|
|
293
|
+
console.log(`${data.toString()}`);
|
|
259
294
|
});
|
|
260
295
|
|
|
261
|
-
local.current.stderr.on("data", (data:
|
|
262
|
-
|
|
296
|
+
local.current.stderr.on("data", (data: Buffer) => {
|
|
297
|
+
console.error(`${data.toString()}`);
|
|
263
298
|
const matches =
|
|
264
299
|
/Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
|
|
265
|
-
data
|
|
300
|
+
data.toString()
|
|
266
301
|
);
|
|
267
302
|
if (matches) {
|
|
268
303
|
setInspectorUrl(matches[1]);
|
|
269
304
|
}
|
|
270
305
|
});
|
|
271
306
|
|
|
272
|
-
|
|
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
|
+
|
|
318
|
+
removeSignalExitListener.current = onExit((_code, _signal) => {
|
|
273
319
|
console.log("⎔ Shutting down local server.");
|
|
274
320
|
local.current?.kill();
|
|
275
321
|
local.current = undefined;
|
|
276
322
|
});
|
|
277
323
|
}
|
|
278
324
|
|
|
279
|
-
startLocalWorker()
|
|
325
|
+
startLocalWorker().catch((err) => {
|
|
326
|
+
console.error("local worker:", err);
|
|
327
|
+
});
|
|
280
328
|
|
|
281
329
|
return () => {
|
|
282
330
|
if (local.current) {
|
|
@@ -287,12 +335,20 @@ function useLocalWorker(props: {
|
|
|
287
335
|
removeSignalExitListener.current = undefined;
|
|
288
336
|
}
|
|
289
337
|
};
|
|
290
|
-
}, [
|
|
338
|
+
}, [
|
|
339
|
+
bundle,
|
|
340
|
+
format,
|
|
341
|
+
port,
|
|
342
|
+
bindings.durable_objects?.bindings,
|
|
343
|
+
bindings.kv_namespaces,
|
|
344
|
+
bindings.vars,
|
|
345
|
+
]);
|
|
291
346
|
return { inspectorUrl };
|
|
292
347
|
}
|
|
293
348
|
|
|
294
349
|
function useTmpDir(): string | void {
|
|
295
350
|
const [directory, setDirectory] = useState<DirectoryResult>();
|
|
351
|
+
const handleError = useErrorHandler();
|
|
296
352
|
useEffect(() => {
|
|
297
353
|
let dir: DirectoryResult;
|
|
298
354
|
async function create() {
|
|
@@ -302,36 +358,117 @@ function useTmpDir(): string | void {
|
|
|
302
358
|
return;
|
|
303
359
|
} catch (err) {
|
|
304
360
|
console.error("failed to create tmp dir");
|
|
305
|
-
console.error(err);
|
|
306
361
|
throw err;
|
|
307
362
|
}
|
|
308
363
|
}
|
|
309
|
-
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
|
+
});
|
|
310
369
|
return () => {
|
|
311
|
-
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
|
+
});
|
|
312
375
|
};
|
|
313
|
-
}, []);
|
|
376
|
+
}, [handleError]);
|
|
314
377
|
return directory?.path;
|
|
315
378
|
}
|
|
316
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
|
+
|
|
317
449
|
type EsbuildBundle = {
|
|
318
450
|
id: number;
|
|
319
451
|
path: string;
|
|
320
452
|
entry: string;
|
|
321
453
|
type: "esm" | "commonjs";
|
|
322
454
|
exports: string[];
|
|
455
|
+
modules: CfModule[];
|
|
323
456
|
};
|
|
324
457
|
|
|
325
|
-
function useEsbuild(
|
|
326
|
-
entry: string
|
|
327
|
-
destination: string | void
|
|
328
|
-
staticRoot: void | string
|
|
329
|
-
|
|
458
|
+
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 {
|
|
465
|
+
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
330
466
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
331
467
|
useEffect(() => {
|
|
332
468
|
let result: esbuild.BuildResult;
|
|
333
469
|
async function build() {
|
|
334
|
-
if (!destination) return;
|
|
470
|
+
if (!destination || !entry) return;
|
|
471
|
+
const moduleCollector = makeModuleCollector();
|
|
335
472
|
result = await esbuild.build({
|
|
336
473
|
entryPoints: [entry],
|
|
337
474
|
bundle: true,
|
|
@@ -339,7 +476,14 @@ function useEsbuild(
|
|
|
339
476
|
metafile: true,
|
|
340
477
|
format: "esm",
|
|
341
478
|
sourcemap: true,
|
|
479
|
+
loader: {
|
|
480
|
+
".js": "jsx",
|
|
481
|
+
},
|
|
482
|
+
...(jsxFactory && { jsxFactory }),
|
|
483
|
+
...(jsxFragment && { jsxFragment }),
|
|
342
484
|
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
485
|
+
conditions: ["worker", "browser"],
|
|
486
|
+
plugins: [moduleCollector.plugin],
|
|
343
487
|
// TODO: import.meta.url
|
|
344
488
|
watch: {
|
|
345
489
|
async onRebuild(error) {
|
|
@@ -347,7 +491,10 @@ function useEsbuild(
|
|
|
347
491
|
else {
|
|
348
492
|
// nothing really changes here, so let's increment the id
|
|
349
493
|
// to change the return object's identity
|
|
350
|
-
setBundle((
|
|
494
|
+
setBundle((previousBundle) => ({
|
|
495
|
+
...previousBundle,
|
|
496
|
+
id: previousBundle.id + 1,
|
|
497
|
+
}));
|
|
351
498
|
}
|
|
352
499
|
},
|
|
353
500
|
},
|
|
@@ -363,13 +510,19 @@ function useEsbuild(
|
|
|
363
510
|
path: chunks[0],
|
|
364
511
|
type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
|
|
365
512
|
exports: chunks[1].exports,
|
|
513
|
+
modules: moduleCollector.modules,
|
|
366
514
|
});
|
|
367
515
|
}
|
|
368
|
-
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
|
+
});
|
|
369
522
|
return () => {
|
|
370
523
|
result?.stop();
|
|
371
524
|
};
|
|
372
|
-
}, [entry, destination, staticRoot]);
|
|
525
|
+
}, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
|
|
373
526
|
return bundle;
|
|
374
527
|
}
|
|
375
528
|
|
|
@@ -380,9 +533,12 @@ function useWorker(props: {
|
|
|
380
533
|
modules: CfModule[];
|
|
381
534
|
accountId: string;
|
|
382
535
|
apiToken: string;
|
|
383
|
-
|
|
536
|
+
bindings: CfWorkerInit["bindings"];
|
|
384
537
|
sitesFolder: void | string;
|
|
385
538
|
port: number;
|
|
539
|
+
compatibilityDate: string | void;
|
|
540
|
+
compatibilityFlags: string[] | void;
|
|
541
|
+
usageModel: void | "bundled" | "unbound";
|
|
386
542
|
}): CfPreviewToken | void {
|
|
387
543
|
const {
|
|
388
544
|
name,
|
|
@@ -391,11 +547,20 @@ function useWorker(props: {
|
|
|
391
547
|
modules,
|
|
392
548
|
accountId,
|
|
393
549
|
apiToken,
|
|
394
|
-
|
|
550
|
+
bindings,
|
|
395
551
|
sitesFolder,
|
|
552
|
+
compatibilityDate,
|
|
553
|
+
compatibilityFlags,
|
|
554
|
+
usageModel,
|
|
396
555
|
port,
|
|
397
556
|
} = props;
|
|
398
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
|
+
|
|
399
564
|
useEffect(() => {
|
|
400
565
|
async function start() {
|
|
401
566
|
if (!bundle) return;
|
|
@@ -410,17 +575,17 @@ function useWorker(props: {
|
|
|
410
575
|
return;
|
|
411
576
|
}
|
|
412
577
|
|
|
413
|
-
if (
|
|
414
|
-
console.log("⎔ Detected changes, restarting server...");
|
|
415
|
-
} else {
|
|
578
|
+
if (!startedRef.current) {
|
|
416
579
|
console.log("⎔ Starting server...");
|
|
580
|
+
startedRef.current = true;
|
|
581
|
+
} else {
|
|
582
|
+
console.log("⎔ Detected changes, restarting server...");
|
|
417
583
|
}
|
|
418
|
-
const scriptName = path.basename(bundle.path);
|
|
419
584
|
|
|
420
585
|
const assets = sitesFolder
|
|
421
586
|
? await syncAssets(
|
|
422
587
|
accountId,
|
|
423
|
-
|
|
588
|
+
path.basename(bundle.path),
|
|
424
589
|
sitesFolder,
|
|
425
590
|
true,
|
|
426
591
|
undefined // TODO: env
|
|
@@ -432,28 +597,33 @@ function useWorker(props: {
|
|
|
432
597
|
|
|
433
598
|
const content = await readFile(bundle.path, "utf-8");
|
|
434
599
|
const init: CfWorkerInit = {
|
|
600
|
+
name,
|
|
435
601
|
main: {
|
|
436
|
-
name:
|
|
437
|
-
type:
|
|
438
|
-
format ||
|
|
439
|
-
(bundle.type === "esm" ? "modules" : "service-worker") === "modules"
|
|
440
|
-
? "esm"
|
|
441
|
-
: "commonjs",
|
|
602
|
+
name: path.basename(bundle.path),
|
|
603
|
+
type: format || bundle.type === "esm" ? "esm" : "commonjs",
|
|
442
604
|
content,
|
|
443
605
|
},
|
|
444
|
-
modules:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
},
|
|
623
|
+
migrations: undefined, // no migrations in dev
|
|
624
|
+
compatibility_date: compatibilityDate,
|
|
625
|
+
compatibility_flags: compatibilityFlags,
|
|
626
|
+
usage_model: usageModel,
|
|
457
627
|
};
|
|
458
628
|
setToken(
|
|
459
629
|
await createWorker(init, {
|
|
@@ -461,10 +631,26 @@ function useWorker(props: {
|
|
|
461
631
|
apiToken,
|
|
462
632
|
})
|
|
463
633
|
);
|
|
464
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
465
634
|
}
|
|
466
|
-
start()
|
|
467
|
-
|
|
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
|
+
});
|
|
640
|
+
}, [
|
|
641
|
+
name,
|
|
642
|
+
bundle,
|
|
643
|
+
format,
|
|
644
|
+
accountId,
|
|
645
|
+
apiToken,
|
|
646
|
+
port,
|
|
647
|
+
sitesFolder,
|
|
648
|
+
compatibilityDate,
|
|
649
|
+
compatibilityFlags,
|
|
650
|
+
usageModel,
|
|
651
|
+
bindings,
|
|
652
|
+
modules,
|
|
653
|
+
]);
|
|
468
654
|
return token;
|
|
469
655
|
}
|
|
470
656
|
|
|
@@ -479,43 +665,53 @@ function useProxy({
|
|
|
479
665
|
}) {
|
|
480
666
|
useEffect(() => {
|
|
481
667
|
if (!token) return;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
}
|
|
487
686
|
},
|
|
488
|
-
target: `https://${token.host}`,
|
|
489
|
-
// TODO: log websockets too? validate durables, etc
|
|
490
687
|
});
|
|
491
688
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
cacheControl: false,
|
|
496
|
-
});
|
|
497
|
-
const server = http
|
|
498
|
-
.createServer((req, res) => {
|
|
499
|
-
if (publicRoot) {
|
|
500
|
-
servePublic(req, res, () => {
|
|
501
|
-
proxy.web(req, res);
|
|
502
|
-
});
|
|
503
|
-
} else {
|
|
504
|
-
proxy.web(req, res);
|
|
505
|
-
}
|
|
506
|
-
})
|
|
507
|
-
.listen(port); // TODO: custom port
|
|
689
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
690
|
+
|
|
691
|
+
const server = proxy.listen(port);
|
|
508
692
|
|
|
509
|
-
|
|
693
|
+
// TODO(soon): refactor logging format into its own function
|
|
694
|
+
proxy.on("request", function (req, res) {
|
|
510
695
|
// log all requests
|
|
511
696
|
console.log(
|
|
512
697
|
new Date().toLocaleTimeString(),
|
|
513
698
|
req.method,
|
|
514
699
|
req.url,
|
|
515
|
-
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)"
|
|
516
710
|
);
|
|
517
711
|
});
|
|
518
|
-
|
|
712
|
+
proxy.on("error", (err) => {
|
|
713
|
+
console.error(new Date().toLocaleTimeString(), err);
|
|
714
|
+
});
|
|
519
715
|
|
|
520
716
|
return () => {
|
|
521
717
|
proxy.close();
|
|
@@ -589,19 +785,21 @@ function useTunnel(toggle: boolean) {
|
|
|
589
785
|
}
|
|
590
786
|
});
|
|
591
787
|
|
|
592
|
-
removeSignalExitListener.current = onExit((
|
|
788
|
+
removeSignalExitListener.current = onExit((_code, _signal) => {
|
|
593
789
|
console.log("⎔ Shutting down local tunnel.");
|
|
594
790
|
tunnel.current?.kill();
|
|
595
791
|
tunnel.current = undefined;
|
|
596
792
|
});
|
|
597
793
|
|
|
598
794
|
const hostName = await findTunnelHostname();
|
|
599
|
-
clipboardy.write(hostName);
|
|
795
|
+
await clipboardy.write(hostName);
|
|
600
796
|
console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
|
|
601
797
|
}
|
|
602
798
|
}
|
|
603
799
|
|
|
604
|
-
startTunnel()
|
|
800
|
+
startTunnel().catch((err) => {
|
|
801
|
+
console.error("tunnel:", err);
|
|
802
|
+
});
|
|
605
803
|
|
|
606
804
|
return () => {
|
|
607
805
|
if (tunnel.current) {
|
|
@@ -623,38 +821,31 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
623
821
|
// UGH, we should put port in context instead
|
|
624
822
|
const [toggles, setToggles] = useState(initial);
|
|
625
823
|
useInput(
|
|
626
|
-
(
|
|
824
|
+
async (
|
|
627
825
|
input,
|
|
628
826
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
629
827
|
key
|
|
630
828
|
) => {
|
|
631
829
|
switch (input) {
|
|
632
830
|
case "b": // open browser
|
|
633
|
-
open(
|
|
634
|
-
`http://localhost:${port}/`
|
|
635
|
-
// {
|
|
636
|
-
// app: {
|
|
637
|
-
// name: open.apps.chrome, // TODO: fallback on other browsers
|
|
638
|
-
// },
|
|
639
|
-
// }
|
|
640
|
-
);
|
|
831
|
+
await open(`http://localhost:${port}/`);
|
|
641
832
|
break;
|
|
642
833
|
case "d": // toggle inspector
|
|
643
|
-
open(
|
|
834
|
+
await open(
|
|
644
835
|
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws`
|
|
645
|
-
// {
|
|
646
|
-
// app: {
|
|
647
|
-
// name: open.apps.chrome,
|
|
648
|
-
// // todo - add firefox and edge fallbacks
|
|
649
|
-
// },
|
|
650
|
-
// }
|
|
651
836
|
);
|
|
652
837
|
break;
|
|
653
838
|
case "s": // toggle tunnel
|
|
654
|
-
setToggles((
|
|
839
|
+
setToggles((previousToggles) => ({
|
|
840
|
+
...previousToggles,
|
|
841
|
+
tunnel: !previousToggles.tunnel,
|
|
842
|
+
}));
|
|
655
843
|
break;
|
|
656
844
|
case "l": // toggle local
|
|
657
|
-
setToggles((
|
|
845
|
+
setToggles((previousToggles) => ({
|
|
846
|
+
...previousToggles,
|
|
847
|
+
local: !previousToggles.local,
|
|
848
|
+
}));
|
|
658
849
|
break;
|
|
659
850
|
case "q": // shut down
|
|
660
851
|
case "x": // shut down
|
|
@@ -668,3 +859,16 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
668
859
|
);
|
|
669
860
|
return toggles;
|
|
670
861
|
}
|
|
862
|
+
|
|
863
|
+
function ErrorFallback(props: { error: Error }) {
|
|
864
|
+
const { exit } = useApp();
|
|
865
|
+
useEffect(() => exit(new Error()));
|
|
866
|
+
return (
|
|
867
|
+
<>
|
|
868
|
+
<Text>Something went wrong:</Text>
|
|
869
|
+
<Text>{props.error.message}</Text>
|
|
870
|
+
</>
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
export default withErrorBoundary(Dev, { FallbackComponent: ErrorFallback });
|