wrangler 0.0.0-fb43821 → 0.0.0-fe5dcfd
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 +25 -10
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +228 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +136 -0
- package/src/__tests__/dev.test.tsx +67 -0
- package/src/__tests__/index.test.ts +42 -175
- package/src/__tests__/jest.setup.ts +22 -0
- package/src/__tests__/kv.test.ts +1199 -0
- package/src/__tests__/mock-cfetch.ts +173 -0
- package/src/__tests__/mock-dialogs.ts +65 -0
- package/src/__tests__/publish.test.ts +737 -0
- package/src/__tests__/run-in-tmp.ts +19 -0
- package/src/__tests__/run-wrangler.ts +32 -0
- package/src/__tests__/whoami.test.tsx +130 -0
- package/src/api/form_data.ts +66 -76
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +38 -44
- package/src/cfetch/index.ts +110 -0
- package/src/cfetch/internal.ts +71 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +634 -110
- package/src/dev.tsx +275 -272
- package/src/dialogs.tsx +2 -2
- package/src/index.tsx +504 -403
- package/src/inspect.ts +543 -0
- package/src/kv.tsx +109 -44
- package/src/pages.tsx +444 -222
- package/src/proxy.ts +302 -0
- package/src/publish.ts +123 -87
- package/src/sites.tsx +169 -41
- package/src/tail.tsx +5 -3
- package/src/user.tsx +64 -74
- package/src/whoami.tsx +69 -0
- package/wrangler-dist/cli.js +42792 -42619
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-cfetch.js +0 -42
- package/src/api/inspect.ts +0 -430
- package/src/cfetch.ts +0 -72
package/src/dev.tsx
CHANGED
|
@@ -1,61 +1,75 @@
|
|
|
1
1
|
import esbuild from "esbuild";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
8
|
+
import { watch } from "chokidar";
|
|
9
|
+
import clipboardy from "clipboardy";
|
|
10
|
+
import commandExists from "command-exists";
|
|
11
|
+
import { execa } from "execa";
|
|
12
|
+
import fetch from "node-fetch";
|
|
13
|
+
import open from "open";
|
|
14
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
15
|
+
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
16
|
+
import onExit from "signal-exit";
|
|
5
17
|
import tmp from "tmp-promise";
|
|
18
|
+
import type { DirectoryResult } from "tmp-promise";
|
|
19
|
+
|
|
6
20
|
import type { CfPreviewToken } from "./api/preview";
|
|
7
|
-
import {
|
|
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";
|
|
21
|
+
import type { CfModule } from "./api/worker";
|
|
13
22
|
import { createWorker } from "./api/worker";
|
|
14
23
|
import type { CfWorkerInit } from "./api/worker";
|
|
15
|
-
|
|
16
|
-
import
|
|
24
|
+
|
|
25
|
+
import useInspector from "./inspect";
|
|
26
|
+
import makeModuleCollector from "./module-collection";
|
|
27
|
+
import { usePreviewServer } from "./proxy";
|
|
28
|
+
import type { AssetPaths } from "./sites";
|
|
17
29
|
import { syncAssets } from "./sites";
|
|
18
|
-
import clipboardy from "clipboardy";
|
|
19
|
-
import http from "node:http";
|
|
20
|
-
import serveStatic from "serve-static";
|
|
21
|
-
import commandExists from "command-exists";
|
|
22
|
-
import assert from "assert";
|
|
23
30
|
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
31
|
|
|
28
|
-
type CfScriptFormat =
|
|
32
|
+
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
29
33
|
|
|
30
|
-
type
|
|
34
|
+
export type DevProps = {
|
|
31
35
|
name?: string;
|
|
32
36
|
entry: string;
|
|
33
37
|
port?: number;
|
|
34
38
|
format: CfScriptFormat;
|
|
35
|
-
accountId:
|
|
39
|
+
accountId: undefined | string;
|
|
36
40
|
initialMode: "local" | "remote";
|
|
37
|
-
jsxFactory:
|
|
38
|
-
jsxFragment:
|
|
39
|
-
|
|
40
|
-
public:
|
|
41
|
-
|
|
42
|
-
compatibilityDate:
|
|
43
|
-
compatibilityFlags:
|
|
44
|
-
usageModel:
|
|
41
|
+
jsxFactory: undefined | string;
|
|
42
|
+
jsxFragment: undefined | string;
|
|
43
|
+
bindings: CfWorkerInit["bindings"];
|
|
44
|
+
public: undefined | string;
|
|
45
|
+
assetPaths: undefined | AssetPaths;
|
|
46
|
+
compatibilityDate: undefined | string;
|
|
47
|
+
compatibilityFlags: undefined | string[];
|
|
48
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
49
|
+
buildCommand: {
|
|
50
|
+
command?: undefined | string;
|
|
51
|
+
cwd?: undefined | string;
|
|
52
|
+
watch_dir?: undefined | string;
|
|
53
|
+
};
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
function Dev(props:
|
|
56
|
+
function Dev(props: DevProps): JSX.Element {
|
|
48
57
|
if (props.public && props.format === "service-worker") {
|
|
49
58
|
throw new Error(
|
|
50
59
|
"You cannot use the service worker format with a `public` directory."
|
|
51
60
|
);
|
|
52
61
|
}
|
|
53
|
-
const port = props.port
|
|
62
|
+
const port = props.port ?? 8787;
|
|
54
63
|
const apiToken = getAPIToken();
|
|
55
64
|
const directory = useTmpDir();
|
|
56
65
|
|
|
66
|
+
// if there isn't a build command, we just return the entry immediately
|
|
67
|
+
// ideally there would be a conditional here, but the rules of hooks
|
|
68
|
+
// kinda forbid that, so we thread the entry through useCustomBuild
|
|
69
|
+
const entry = useCustomBuild(props.entry, props.buildCommand);
|
|
70
|
+
|
|
57
71
|
const bundle = useEsbuild({
|
|
58
|
-
entry
|
|
72
|
+
entry,
|
|
59
73
|
destination: directory,
|
|
60
74
|
staticRoot: props.public,
|
|
61
75
|
jsxFactory: props.jsxFactory,
|
|
@@ -67,9 +81,6 @@ function Dev(props: Props): JSX.Element {
|
|
|
67
81
|
);
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
// @ts-expect-error whack
|
|
71
|
-
useDevtoolsRefresh(bundle?.id ?? 0);
|
|
72
|
-
|
|
73
84
|
const toggles = useHotkeys(
|
|
74
85
|
{
|
|
75
86
|
local: props.initialMode === "local",
|
|
@@ -87,10 +98,10 @@ function Dev(props: Props): JSX.Element {
|
|
|
87
98
|
name={props.name}
|
|
88
99
|
bundle={bundle}
|
|
89
100
|
format={props.format}
|
|
90
|
-
|
|
91
|
-
site={props.
|
|
101
|
+
bindings={props.bindings}
|
|
102
|
+
site={props.assetPaths}
|
|
92
103
|
public={props.public}
|
|
93
|
-
port={
|
|
104
|
+
port={port}
|
|
94
105
|
/>
|
|
95
106
|
) : (
|
|
96
107
|
<Remote
|
|
@@ -99,10 +110,10 @@ function Dev(props: Props): JSX.Element {
|
|
|
99
110
|
format={props.format}
|
|
100
111
|
accountId={props.accountId}
|
|
101
112
|
apiToken={apiToken}
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
bindings={props.bindings}
|
|
114
|
+
assetPaths={props.assetPaths}
|
|
104
115
|
public={props.public}
|
|
105
|
-
port={
|
|
116
|
+
port={port}
|
|
106
117
|
compatibilityDate={props.compatibilityDate}
|
|
107
118
|
compatibilityFlags={props.compatibilityFlags}
|
|
108
119
|
usageModel={props.usageModel}
|
|
@@ -121,104 +132,82 @@ function Dev(props: Props): JSX.Element {
|
|
|
121
132
|
);
|
|
122
133
|
}
|
|
123
134
|
|
|
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
135
|
function Remote(props: {
|
|
155
|
-
name:
|
|
156
|
-
bundle: EsbuildBundle |
|
|
136
|
+
name: undefined | string;
|
|
137
|
+
bundle: EsbuildBundle | undefined;
|
|
157
138
|
format: CfScriptFormat;
|
|
158
|
-
public:
|
|
159
|
-
|
|
139
|
+
public: undefined | string;
|
|
140
|
+
assetPaths: undefined | AssetPaths;
|
|
160
141
|
port: number;
|
|
161
|
-
accountId:
|
|
162
|
-
apiToken:
|
|
163
|
-
|
|
164
|
-
compatibilityDate: string |
|
|
165
|
-
compatibilityFlags:
|
|
166
|
-
usageModel:
|
|
142
|
+
accountId: undefined | string;
|
|
143
|
+
apiToken: undefined | string;
|
|
144
|
+
bindings: CfWorkerInit["bindings"];
|
|
145
|
+
compatibilityDate: string | undefined;
|
|
146
|
+
compatibilityFlags: undefined | string[];
|
|
147
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
167
148
|
}) {
|
|
168
149
|
assert(props.accountId, "accountId is required");
|
|
169
150
|
assert(props.apiToken, "apiToken is required");
|
|
170
|
-
const
|
|
151
|
+
const previewToken = useWorker({
|
|
171
152
|
name: props.name,
|
|
172
153
|
bundle: props.bundle,
|
|
173
154
|
format: props.format,
|
|
174
155
|
modules: props.bundle ? props.bundle.modules : [],
|
|
175
156
|
accountId: props.accountId,
|
|
176
157
|
apiToken: props.apiToken,
|
|
177
|
-
|
|
178
|
-
|
|
158
|
+
bindings: props.bindings,
|
|
159
|
+
assetPaths: props.assetPaths,
|
|
179
160
|
port: props.port,
|
|
180
161
|
compatibilityDate: props.compatibilityDate,
|
|
181
162
|
compatibilityFlags: props.compatibilityFlags,
|
|
182
163
|
usageModel: props.usageModel,
|
|
183
164
|
});
|
|
184
165
|
|
|
185
|
-
|
|
166
|
+
usePreviewServer({
|
|
167
|
+
previewToken,
|
|
168
|
+
publicRoot: props.public,
|
|
169
|
+
port: props.port,
|
|
170
|
+
});
|
|
186
171
|
|
|
187
|
-
useInspector(
|
|
172
|
+
useInspector({
|
|
173
|
+
inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
|
|
174
|
+
port: 9229,
|
|
175
|
+
logToTerminal: true,
|
|
176
|
+
});
|
|
188
177
|
return null;
|
|
189
178
|
}
|
|
190
179
|
function Local(props: {
|
|
191
|
-
name:
|
|
192
|
-
bundle: EsbuildBundle |
|
|
180
|
+
name: undefined | string;
|
|
181
|
+
bundle: EsbuildBundle | undefined;
|
|
193
182
|
format: CfScriptFormat;
|
|
194
|
-
|
|
195
|
-
public:
|
|
196
|
-
site:
|
|
183
|
+
bindings: CfWorkerInit["bindings"];
|
|
184
|
+
public: undefined | string;
|
|
185
|
+
site: undefined | AssetPaths;
|
|
197
186
|
port: number;
|
|
198
187
|
}) {
|
|
199
188
|
const { inspectorUrl } = useLocalWorker({
|
|
200
189
|
name: props.name,
|
|
201
190
|
bundle: props.bundle,
|
|
202
191
|
format: props.format,
|
|
203
|
-
|
|
192
|
+
bindings: props.bindings,
|
|
204
193
|
port: props.port,
|
|
205
194
|
});
|
|
206
|
-
useInspector(inspectorUrl);
|
|
195
|
+
useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
|
|
207
196
|
return null;
|
|
208
197
|
}
|
|
209
198
|
|
|
210
199
|
function useLocalWorker(props: {
|
|
211
|
-
name:
|
|
212
|
-
bundle: EsbuildBundle |
|
|
200
|
+
name: undefined | string;
|
|
201
|
+
bundle: EsbuildBundle | undefined;
|
|
213
202
|
format: CfScriptFormat;
|
|
214
|
-
|
|
203
|
+
bindings: CfWorkerInit["bindings"];
|
|
215
204
|
port: number;
|
|
216
205
|
}) {
|
|
217
206
|
// TODO: pass vars via command line
|
|
218
|
-
const { bundle, format,
|
|
207
|
+
const { bundle, format, bindings, port } = props;
|
|
219
208
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
220
209
|
const removeSignalExitListener = useRef<() => void>();
|
|
221
|
-
const [inspectorUrl, setInspectorUrl] = useState<string |
|
|
210
|
+
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
222
211
|
useEffect(() => {
|
|
223
212
|
async function startLocalWorker() {
|
|
224
213
|
if (!bundle) return;
|
|
@@ -252,20 +241,17 @@ function useLocalWorker(props: {
|
|
|
252
241
|
"--kv-persist",
|
|
253
242
|
"--cache-persist",
|
|
254
243
|
"--do-persist",
|
|
255
|
-
...Object.entries(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
.filter(Boolean),
|
|
244
|
+
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
245
|
+
return ["--binding", `${key}=${value}`];
|
|
246
|
+
}),
|
|
247
|
+
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
248
|
+
return ["--kv", binding];
|
|
249
|
+
}),
|
|
250
|
+
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
251
|
+
({ name, class_name }) => {
|
|
252
|
+
return ["--do", `${name}=${class_name}`];
|
|
253
|
+
}
|
|
254
|
+
),
|
|
269
255
|
"--modules",
|
|
270
256
|
format ||
|
|
271
257
|
(bundle.type === "esm" ? "modules" : "service-worker") === "modules"
|
|
@@ -280,15 +266,15 @@ function useLocalWorker(props: {
|
|
|
280
266
|
}
|
|
281
267
|
});
|
|
282
268
|
|
|
283
|
-
local.current.stdout
|
|
284
|
-
|
|
269
|
+
local.current.stdout?.on("data", (data: Buffer) => {
|
|
270
|
+
console.log(`${data.toString()}`);
|
|
285
271
|
});
|
|
286
272
|
|
|
287
|
-
local.current.stderr
|
|
288
|
-
|
|
273
|
+
local.current.stderr?.on("data", (data: Buffer) => {
|
|
274
|
+
console.error(`${data.toString()}`);
|
|
289
275
|
const matches =
|
|
290
276
|
/Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
|
|
291
|
-
data
|
|
277
|
+
data.toString()
|
|
292
278
|
);
|
|
293
279
|
if (matches) {
|
|
294
280
|
setInspectorUrl(matches[1]);
|
|
@@ -326,11 +312,18 @@ function useLocalWorker(props: {
|
|
|
326
312
|
removeSignalExitListener.current = undefined;
|
|
327
313
|
}
|
|
328
314
|
};
|
|
329
|
-
}, [
|
|
315
|
+
}, [
|
|
316
|
+
bundle,
|
|
317
|
+
format,
|
|
318
|
+
port,
|
|
319
|
+
bindings.durable_objects?.bindings,
|
|
320
|
+
bindings.kv_namespaces,
|
|
321
|
+
bindings.vars,
|
|
322
|
+
]);
|
|
330
323
|
return { inspectorUrl };
|
|
331
324
|
}
|
|
332
325
|
|
|
333
|
-
function useTmpDir(): string |
|
|
326
|
+
function useTmpDir(): string | undefined {
|
|
334
327
|
const [directory, setDirectory] = useState<DirectoryResult>();
|
|
335
328
|
const handleError = useErrorHandler();
|
|
336
329
|
useEffect(() => {
|
|
@@ -361,6 +354,75 @@ function useTmpDir(): string | void {
|
|
|
361
354
|
return directory?.path;
|
|
362
355
|
}
|
|
363
356
|
|
|
357
|
+
function useCustomBuild(
|
|
358
|
+
expectedEntry: string,
|
|
359
|
+
props: {
|
|
360
|
+
command?: undefined | string;
|
|
361
|
+
cwd?: undefined | string;
|
|
362
|
+
watch_dir?: undefined | string;
|
|
363
|
+
}
|
|
364
|
+
): undefined | string {
|
|
365
|
+
const [entry, setEntry] = useState<string | undefined>(
|
|
366
|
+
// if there's no build command, just return the expected entry
|
|
367
|
+
props.command || expectedEntry
|
|
368
|
+
);
|
|
369
|
+
const { command, cwd, watch_dir } = props;
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
if (!command) return;
|
|
372
|
+
let cmd, interval;
|
|
373
|
+
console.log("running:", command);
|
|
374
|
+
const commandPieces = command.split(" ");
|
|
375
|
+
cmd = execa(commandPieces[0], commandPieces.slice(1), {
|
|
376
|
+
...(cwd && { cwd }),
|
|
377
|
+
stderr: "inherit",
|
|
378
|
+
stdout: "inherit",
|
|
379
|
+
});
|
|
380
|
+
if (watch_dir) {
|
|
381
|
+
watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
|
|
382
|
+
"all",
|
|
383
|
+
(_event, _path) => {
|
|
384
|
+
console.log(`The file ${path} changed, restarting build...`);
|
|
385
|
+
cmd.kill();
|
|
386
|
+
cmd = execa(commandPieces[0], commandPieces.slice(1), {
|
|
387
|
+
...(cwd && { cwd }),
|
|
388
|
+
stderr: "inherit",
|
|
389
|
+
stdout: "inherit",
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// check every so often whether `expectedEntry` exists
|
|
396
|
+
// if it does, we're done
|
|
397
|
+
const startedAt = Date.now();
|
|
398
|
+
interval = setInterval(() => {
|
|
399
|
+
if (existsSync(expectedEntry)) {
|
|
400
|
+
clearInterval(interval);
|
|
401
|
+
setEntry(expectedEntry);
|
|
402
|
+
} else {
|
|
403
|
+
const elapsed = Date.now() - startedAt;
|
|
404
|
+
// timeout after 30 seconds of waiting
|
|
405
|
+
if (elapsed > 1000 * 60 * 30) {
|
|
406
|
+
console.error("⎔ Build timed out.");
|
|
407
|
+
clearInterval(interval);
|
|
408
|
+
cmd.kill();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}, 200);
|
|
412
|
+
// TODO: we could probably timeout here after a while
|
|
413
|
+
|
|
414
|
+
return () => {
|
|
415
|
+
if (cmd) {
|
|
416
|
+
cmd.kill();
|
|
417
|
+
cmd = undefined;
|
|
418
|
+
}
|
|
419
|
+
clearInterval(interval);
|
|
420
|
+
interval = undefined;
|
|
421
|
+
};
|
|
422
|
+
}, [command, cwd, expectedEntry, watch_dir]);
|
|
423
|
+
return entry;
|
|
424
|
+
}
|
|
425
|
+
|
|
364
426
|
type EsbuildBundle = {
|
|
365
427
|
id: number;
|
|
366
428
|
path: string;
|
|
@@ -371,18 +433,18 @@ type EsbuildBundle = {
|
|
|
371
433
|
};
|
|
372
434
|
|
|
373
435
|
function useEsbuild(props: {
|
|
374
|
-
entry: string;
|
|
375
|
-
destination: string |
|
|
376
|
-
staticRoot:
|
|
377
|
-
jsxFactory: string |
|
|
378
|
-
jsxFragment: string |
|
|
379
|
-
}): EsbuildBundle |
|
|
436
|
+
entry: undefined | string;
|
|
437
|
+
destination: string | undefined;
|
|
438
|
+
staticRoot: undefined | string;
|
|
439
|
+
jsxFactory: string | undefined;
|
|
440
|
+
jsxFragment: string | undefined;
|
|
441
|
+
}): EsbuildBundle | undefined {
|
|
380
442
|
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
381
443
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
382
444
|
useEffect(() => {
|
|
383
|
-
let result: esbuild.BuildResult;
|
|
445
|
+
let result: esbuild.BuildResult | undefined;
|
|
384
446
|
async function build() {
|
|
385
|
-
if (!destination) return;
|
|
447
|
+
if (!destination || !entry) return;
|
|
386
448
|
const moduleCollector = makeModuleCollector();
|
|
387
449
|
result = await esbuild.build({
|
|
388
450
|
entryPoints: [entry],
|
|
@@ -406,22 +468,37 @@ function useEsbuild(props: {
|
|
|
406
468
|
else {
|
|
407
469
|
// nothing really changes here, so let's increment the id
|
|
408
470
|
// to change the return object's identity
|
|
409
|
-
setBundle((
|
|
471
|
+
setBundle((previousBundle) => {
|
|
472
|
+
if (previousBundle === undefined) {
|
|
473
|
+
assert.fail(
|
|
474
|
+
"Rebuild triggered with no previous build available"
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return { ...previousBundle, id: previousBundle.id + 1 };
|
|
478
|
+
});
|
|
410
479
|
}
|
|
411
480
|
},
|
|
412
481
|
},
|
|
413
482
|
});
|
|
414
483
|
|
|
415
|
-
|
|
484
|
+
// result.metafile is defined because of the `metafile: true` option above.
|
|
485
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
486
|
+
const metafile = result.metafile!;
|
|
487
|
+
const outputEntry = Object.entries(metafile.outputs).find(
|
|
416
488
|
([_path, { entryPoint }]) => entryPoint === entry
|
|
417
489
|
); // assumedly only one entry point
|
|
418
490
|
|
|
491
|
+
if (outputEntry === undefined) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`Cannot find entry-point "${entry}" in generated bundle.`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
419
496
|
setBundle({
|
|
420
497
|
id: 0,
|
|
421
498
|
entry,
|
|
422
|
-
path:
|
|
423
|
-
type:
|
|
424
|
-
exports:
|
|
499
|
+
path: outputEntry[0],
|
|
500
|
+
type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs",
|
|
501
|
+
exports: outputEntry[1].exports,
|
|
425
502
|
modules: moduleCollector.modules,
|
|
426
503
|
});
|
|
427
504
|
}
|
|
@@ -432,26 +509,26 @@ function useEsbuild(props: {
|
|
|
432
509
|
// so this is a no-op error handler
|
|
433
510
|
});
|
|
434
511
|
return () => {
|
|
435
|
-
result?.stop();
|
|
512
|
+
result?.stop?.();
|
|
436
513
|
};
|
|
437
514
|
}, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
|
|
438
515
|
return bundle;
|
|
439
516
|
}
|
|
440
517
|
|
|
441
518
|
function useWorker(props: {
|
|
442
|
-
name:
|
|
443
|
-
bundle: EsbuildBundle |
|
|
519
|
+
name: undefined | string;
|
|
520
|
+
bundle: EsbuildBundle | undefined;
|
|
444
521
|
format: CfScriptFormat;
|
|
445
522
|
modules: CfModule[];
|
|
446
523
|
accountId: string;
|
|
447
524
|
apiToken: string;
|
|
448
|
-
|
|
449
|
-
|
|
525
|
+
bindings: CfWorkerInit["bindings"];
|
|
526
|
+
assetPaths: undefined | AssetPaths;
|
|
450
527
|
port: number;
|
|
451
|
-
compatibilityDate: string |
|
|
452
|
-
compatibilityFlags: string[] |
|
|
453
|
-
usageModel:
|
|
454
|
-
}): CfPreviewToken |
|
|
528
|
+
compatibilityDate: string | undefined;
|
|
529
|
+
compatibilityFlags: string[] | undefined;
|
|
530
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
531
|
+
}): CfPreviewToken | undefined {
|
|
455
532
|
const {
|
|
456
533
|
name,
|
|
457
534
|
bundle,
|
|
@@ -459,16 +536,24 @@ function useWorker(props: {
|
|
|
459
536
|
modules,
|
|
460
537
|
accountId,
|
|
461
538
|
apiToken,
|
|
462
|
-
|
|
463
|
-
|
|
539
|
+
bindings,
|
|
540
|
+
assetPaths,
|
|
464
541
|
compatibilityDate,
|
|
465
542
|
compatibilityFlags,
|
|
466
543
|
usageModel,
|
|
467
544
|
port,
|
|
468
545
|
} = props;
|
|
469
|
-
const [token, setToken] = useState<CfPreviewToken>();
|
|
546
|
+
const [token, setToken] = useState<CfPreviewToken | undefined>();
|
|
547
|
+
|
|
548
|
+
// This is the most reliable way to detect whether
|
|
549
|
+
// something's "happened" in our system; We make a ref and
|
|
550
|
+
// mark it once we log our initial message. Refs are vars!
|
|
551
|
+
const startedRef = useRef(false);
|
|
552
|
+
|
|
470
553
|
useEffect(() => {
|
|
471
554
|
async function start() {
|
|
555
|
+
setToken(undefined); // reset token in case we're re-running
|
|
556
|
+
|
|
472
557
|
if (!bundle) return;
|
|
473
558
|
if (format === "modules" && bundle.type === "commonjs") {
|
|
474
559
|
console.error("⎔ Cannot use modules with a commonjs bundle.");
|
|
@@ -481,24 +566,18 @@ function useWorker(props: {
|
|
|
481
566
|
return;
|
|
482
567
|
}
|
|
483
568
|
|
|
484
|
-
if (
|
|
485
|
-
|
|
569
|
+
if (!startedRef.current) {
|
|
570
|
+
startedRef.current = true;
|
|
486
571
|
} else {
|
|
487
|
-
console.log("⎔
|
|
572
|
+
console.log("⎔ Detected changes, restarting server...");
|
|
488
573
|
}
|
|
489
574
|
|
|
490
|
-
const assets =
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
undefined // TODO: env
|
|
497
|
-
)
|
|
498
|
-
: {
|
|
499
|
-
manifest: undefined,
|
|
500
|
-
namespace: undefined,
|
|
501
|
-
}; // TODO: cancellable?
|
|
575
|
+
const assets = await syncAssets(
|
|
576
|
+
accountId,
|
|
577
|
+
path.basename(bundle.path),
|
|
578
|
+
assetPaths,
|
|
579
|
+
true
|
|
580
|
+
); // TODO: cancellable?
|
|
502
581
|
|
|
503
582
|
const content = await readFile(bundle.path, "utf-8");
|
|
504
583
|
const init: CfWorkerInit = {
|
|
@@ -508,19 +587,23 @@ function useWorker(props: {
|
|
|
508
587
|
type: format || bundle.type === "esm" ? "esm" : "commonjs",
|
|
509
588
|
content,
|
|
510
589
|
},
|
|
511
|
-
modules:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
590
|
+
modules: modules.concat(
|
|
591
|
+
assets.manifest
|
|
592
|
+
? {
|
|
593
|
+
name: "__STATIC_CONTENT_MANIFEST",
|
|
594
|
+
content: JSON.stringify(assets.manifest),
|
|
595
|
+
type: "text",
|
|
596
|
+
}
|
|
597
|
+
: []
|
|
598
|
+
),
|
|
599
|
+
bindings: {
|
|
600
|
+
...bindings,
|
|
601
|
+
kv_namespaces: (bindings.kv_namespaces || []).concat(
|
|
602
|
+
assets.namespace
|
|
603
|
+
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
604
|
+
: []
|
|
605
|
+
),
|
|
606
|
+
},
|
|
524
607
|
migrations: undefined, // no migrations in dev
|
|
525
608
|
compatibility_date: compatibilityDate,
|
|
526
609
|
compatibility_flags: compatibilityFlags,
|
|
@@ -532,7 +615,6 @@ function useWorker(props: {
|
|
|
532
615
|
apiToken,
|
|
533
616
|
})
|
|
534
617
|
);
|
|
535
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
536
618
|
}
|
|
537
619
|
start().catch((err) => {
|
|
538
620
|
// we want to log the error, but not end the process
|
|
@@ -546,83 +628,16 @@ function useWorker(props: {
|
|
|
546
628
|
accountId,
|
|
547
629
|
apiToken,
|
|
548
630
|
port,
|
|
549
|
-
|
|
631
|
+
assetPaths,
|
|
550
632
|
compatibilityDate,
|
|
551
633
|
compatibilityFlags,
|
|
552
634
|
usageModel,
|
|
635
|
+
bindings,
|
|
636
|
+
modules,
|
|
553
637
|
]);
|
|
554
638
|
return token;
|
|
555
639
|
}
|
|
556
640
|
|
|
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
641
|
function sleep(period: number) {
|
|
627
642
|
return new Promise((resolve) => setTimeout(resolve, period));
|
|
628
643
|
}
|
|
@@ -630,7 +645,7 @@ const SLEEP_DURATION = 2000;
|
|
|
630
645
|
// really need a first class api for this
|
|
631
646
|
const hostNameRegex = /userHostname="(.*)"/g;
|
|
632
647
|
async function findTunnelHostname() {
|
|
633
|
-
let hostName: string;
|
|
648
|
+
let hostName: string | undefined;
|
|
634
649
|
while (!hostName) {
|
|
635
650
|
try {
|
|
636
651
|
const resp = await fetch("http://localhost:8789/metrics");
|
|
@@ -718,31 +733,24 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
718
733
|
) => {
|
|
719
734
|
switch (input) {
|
|
720
735
|
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
|
-
);
|
|
736
|
+
await open(`http://localhost:${port}/`);
|
|
729
737
|
break;
|
|
730
738
|
case "d": // toggle inspector
|
|
731
739
|
await open(
|
|
732
740
|
`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
741
|
);
|
|
740
742
|
break;
|
|
741
743
|
case "s": // toggle tunnel
|
|
742
|
-
setToggles((
|
|
744
|
+
setToggles((previousToggles) => ({
|
|
745
|
+
...previousToggles,
|
|
746
|
+
tunnel: !previousToggles.tunnel,
|
|
747
|
+
}));
|
|
743
748
|
break;
|
|
744
749
|
case "l": // toggle local
|
|
745
|
-
setToggles((
|
|
750
|
+
setToggles((previousToggles) => ({
|
|
751
|
+
...previousToggles,
|
|
752
|
+
local: !previousToggles.local,
|
|
753
|
+
}));
|
|
746
754
|
break;
|
|
747
755
|
case "q": // shut down
|
|
748
756
|
case "x": // shut down
|
|
@@ -757,19 +765,14 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
757
765
|
return toggles;
|
|
758
766
|
}
|
|
759
767
|
|
|
760
|
-
function ErrorFallback(props: {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}) {
|
|
764
|
-
useEffect(() => {
|
|
765
|
-
console.error(props.error);
|
|
766
|
-
process.exit(1);
|
|
767
|
-
});
|
|
768
|
+
function ErrorFallback(props: { error: Error }) {
|
|
769
|
+
const { exit } = useApp();
|
|
770
|
+
useEffect(() => exit(new Error()));
|
|
768
771
|
return (
|
|
769
|
-
|
|
772
|
+
<>
|
|
770
773
|
<Text>Something went wrong:</Text>
|
|
771
|
-
<Text>{props.error.
|
|
772
|
-
|
|
774
|
+
<Text>{props.error.stack}</Text>
|
|
775
|
+
</>
|
|
773
776
|
);
|
|
774
777
|
}
|
|
775
778
|
|