wrangler 0.0.0-ece06ea → 0.0.0-ef13735
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/bin/wrangler.js +2 -2
- package/package.json +35 -21
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +66 -0
- package/pages/functions/filepath-routing.ts +219 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +151 -0
- package/pages/functions/template-worker.ts +130 -0
- package/src/__tests__/dev.test.tsx +73 -0
- package/src/__tests__/guess-worker-format.test.ts +51 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +33 -0
- package/src/__tests__/helpers/mock-cfetch.ts +207 -0
- package/src/__tests__/helpers/mock-console.ts +56 -0
- package/src/__tests__/helpers/mock-dialogs.ts +65 -0
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/run-in-tmp.ts +29 -0
- package/src/__tests__/helpers/run-wrangler.ts +14 -0
- package/src/__tests__/index.test.ts +715 -119
- package/src/__tests__/jest.setup.ts +37 -0
- package/src/__tests__/kv.test.ts +1139 -0
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1381 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/whoami.test.tsx +86 -0
- package/src/api/form_data.ts +93 -100
- package/src/api/preview.ts +29 -24
- package/src/api/worker.ts +55 -47
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +101 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +641 -112
- package/src/dev.tsx +476 -355
- package/src/dialogs.tsx +6 -14
- package/src/guess-worker-format.ts +66 -0
- package/src/index.tsx +828 -496
- package/src/inspect.ts +596 -0
- package/src/kv.tsx +118 -45
- package/src/module-collection.ts +63 -0
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +915 -215
- package/src/paths.ts +26 -0
- package/src/proxy.ts +370 -0
- package/src/publish.ts +349 -290
- package/src/sites.tsx +184 -55
- package/src/tail.tsx +5 -3
- package/src/user.tsx +81 -91
- package/src/whoami.tsx +69 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/wrangler-dist/cli.js +119231 -130682
- 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/api/inspect.ts +0 -441
- package/src/cfetch.ts +0 -72
- package/src/util/fetch.ts +0 -74
package/src/dev.tsx
CHANGED
|
@@ -1,76 +1,86 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { watch } from "chokidar";
|
|
7
|
+
import clipboardy from "clipboardy";
|
|
8
|
+
import commandExists from "command-exists";
|
|
9
|
+
import * as esbuild from "esbuild";
|
|
10
|
+
import { execaCommand } from "execa";
|
|
11
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
8
12
|
import React, { useState, useEffect, useRef } from "react";
|
|
9
|
-
import
|
|
10
|
-
import open from "open";
|
|
11
|
-
import { DtInspector } from "./api/inspect";
|
|
12
|
-
import type { CfModule, CfVariable } from "./api/worker";
|
|
13
|
-
import { createWorker } from "./api/worker";
|
|
14
|
-
import type { CfWorkerInit } from "./api/worker";
|
|
15
|
-
import { spawn } from "child_process";
|
|
13
|
+
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
16
14
|
import onExit from "signal-exit";
|
|
15
|
+
import tmp from "tmp-promise";
|
|
16
|
+
import { fetch } from "undici";
|
|
17
|
+
import { createWorker } from "./api/worker";
|
|
18
|
+
import guessWorkerFormat from "./guess-worker-format";
|
|
19
|
+
import useInspector from "./inspect";
|
|
20
|
+
import makeModuleCollector from "./module-collection";
|
|
21
|
+
import openInBrowser from "./open-in-browser";
|
|
22
|
+
import { usePreviewServer, waitForPortToBeAvailable } from "./proxy";
|
|
17
23
|
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
24
|
import { getAPIToken } from "./user";
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
type CfScriptFormat = void | "modules" | "service-worker";
|
|
25
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
26
|
+
import type { CfModule, CfWorkerInit, CfScriptFormat } from "./api/worker";
|
|
27
|
+
import type { AssetPaths } from "./sites";
|
|
28
|
+
import type { DirectoryResult } from "tmp-promise";
|
|
29
29
|
|
|
30
|
-
type
|
|
30
|
+
export type DevProps = {
|
|
31
31
|
name?: string;
|
|
32
32
|
entry: string;
|
|
33
33
|
port?: number;
|
|
34
|
-
format: CfScriptFormat;
|
|
35
|
-
accountId:
|
|
34
|
+
format: CfScriptFormat | undefined;
|
|
35
|
+
accountId: undefined | string;
|
|
36
36
|
initialMode: "local" | "remote";
|
|
37
|
-
jsxFactory:
|
|
38
|
-
jsxFragment:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
jsxFactory: undefined | string;
|
|
38
|
+
jsxFragment: undefined | string;
|
|
39
|
+
enableLocalPersistence: boolean;
|
|
40
|
+
bindings: CfWorkerInit["bindings"];
|
|
41
|
+
public: undefined | string;
|
|
42
|
+
assetPaths: undefined | AssetPaths;
|
|
43
|
+
compatibilityDate: undefined | string;
|
|
44
|
+
compatibilityFlags: undefined | string[];
|
|
45
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
46
|
+
buildCommand: {
|
|
47
|
+
command?: undefined | string;
|
|
48
|
+
cwd?: undefined | string;
|
|
49
|
+
watch_dir?: undefined | string;
|
|
50
|
+
};
|
|
51
|
+
env: string | undefined;
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"You cannot use the service worker format with a `public` directory."
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
const port = props.port || 8787;
|
|
55
|
-
const apiToken = getAPIToken();
|
|
54
|
+
function Dev(props: DevProps): JSX.Element {
|
|
55
|
+
const port = props.port ?? 8787;
|
|
56
|
+
const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
|
|
56
57
|
const directory = useTmpDir();
|
|
57
58
|
|
|
59
|
+
// if there isn't a build command, we just return the entry immediately
|
|
60
|
+
// ideally there would be a conditional here, but the rules of hooks
|
|
61
|
+
// kinda forbid that, so we thread the entry through useCustomBuild
|
|
62
|
+
const entry = useCustomBuild(props.entry, props.buildCommand);
|
|
63
|
+
|
|
58
64
|
const bundle = useEsbuild({
|
|
59
|
-
entry
|
|
65
|
+
entry,
|
|
60
66
|
destination: directory,
|
|
61
67
|
staticRoot: props.public,
|
|
62
68
|
jsxFactory: props.jsxFactory,
|
|
63
69
|
jsxFragment: props.jsxFragment,
|
|
64
|
-
polyfillNode: props.polyfillNode,
|
|
65
70
|
});
|
|
66
|
-
|
|
71
|
+
|
|
72
|
+
const format = useWorkerFormat({ file: entry, format: props.format });
|
|
73
|
+
if (format && props.public && format === "service-worker") {
|
|
67
74
|
throw new Error(
|
|
68
75
|
"You cannot use the service worker format with a `public` directory."
|
|
69
76
|
);
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
if (props.bindings.wasm_modules && format === "modules") {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"You cannot configure [wasm_modules] with an ES Module worker. Instead, import the .wasm module directly in your code"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
74
84
|
|
|
75
85
|
const toggles = useHotkeys(
|
|
76
86
|
{
|
|
@@ -88,26 +98,28 @@ export function Dev(props: Props): JSX.Element {
|
|
|
88
98
|
<Local
|
|
89
99
|
name={props.name}
|
|
90
100
|
bundle={bundle}
|
|
91
|
-
format={
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
format={format}
|
|
102
|
+
bindings={props.bindings}
|
|
103
|
+
assetPaths={props.assetPaths}
|
|
94
104
|
public={props.public}
|
|
95
|
-
port={
|
|
105
|
+
port={port}
|
|
106
|
+
enableLocalPersistence={props.enableLocalPersistence}
|
|
96
107
|
/>
|
|
97
108
|
) : (
|
|
98
109
|
<Remote
|
|
99
110
|
name={props.name}
|
|
100
111
|
bundle={bundle}
|
|
101
|
-
format={
|
|
112
|
+
format={format}
|
|
102
113
|
accountId={props.accountId}
|
|
103
114
|
apiToken={apiToken}
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
bindings={props.bindings}
|
|
116
|
+
assetPaths={props.assetPaths}
|
|
106
117
|
public={props.public}
|
|
107
|
-
port={
|
|
118
|
+
port={port}
|
|
108
119
|
compatibilityDate={props.compatibilityDate}
|
|
109
120
|
compatibilityFlags={props.compatibilityFlags}
|
|
110
121
|
usageModel={props.usageModel}
|
|
122
|
+
env={props.env}
|
|
111
123
|
/>
|
|
112
124
|
)}
|
|
113
125
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
@@ -123,156 +135,193 @@ export function Dev(props: Props): JSX.Element {
|
|
|
123
135
|
);
|
|
124
136
|
}
|
|
125
137
|
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
// @ts-expect-error whack
|
|
132
|
-
ref.current = bundleId;
|
|
133
|
-
|
|
138
|
+
function useWorkerFormat(props: {
|
|
139
|
+
file: string | undefined;
|
|
140
|
+
format: undefined | CfScriptFormat;
|
|
141
|
+
}): CfScriptFormat | undefined {
|
|
142
|
+
const [format, setFormat] = useState<CfScriptFormat | undefined>();
|
|
134
143
|
useEffect(() => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
res.setHeader("Access-Control-Request-Method", "*");
|
|
138
|
-
res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET");
|
|
139
|
-
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
140
|
-
if (req.method === "OPTIONS") {
|
|
141
|
-
res.writeHead(200);
|
|
142
|
-
res.end();
|
|
144
|
+
async function validateFormat() {
|
|
145
|
+
if (!props.file || format) {
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
setFormat(await guessWorkerFormat(props.file, props.format));
|
|
149
|
+
}
|
|
150
|
+
validateFormat().catch((err) => {
|
|
151
|
+
console.error("Failed to validate worker format:", err);
|
|
147
152
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return () => {
|
|
151
|
-
server.close();
|
|
152
|
-
};
|
|
153
|
-
}, []);
|
|
153
|
+
}, [props.file, props.format, format]);
|
|
154
|
+
return format;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
function Remote(props: {
|
|
157
|
-
name:
|
|
158
|
-
bundle: EsbuildBundle |
|
|
159
|
-
format: CfScriptFormat;
|
|
160
|
-
public:
|
|
161
|
-
|
|
158
|
+
name: undefined | string;
|
|
159
|
+
bundle: EsbuildBundle | undefined;
|
|
160
|
+
format: CfScriptFormat | undefined;
|
|
161
|
+
public: undefined | string;
|
|
162
|
+
assetPaths: undefined | AssetPaths;
|
|
162
163
|
port: number;
|
|
163
|
-
accountId:
|
|
164
|
-
apiToken:
|
|
165
|
-
|
|
166
|
-
compatibilityDate: string |
|
|
167
|
-
compatibilityFlags:
|
|
168
|
-
usageModel:
|
|
164
|
+
accountId: undefined | string;
|
|
165
|
+
apiToken: undefined | string;
|
|
166
|
+
bindings: CfWorkerInit["bindings"];
|
|
167
|
+
compatibilityDate: string | undefined;
|
|
168
|
+
compatibilityFlags: undefined | string[];
|
|
169
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
170
|
+
env: string | undefined;
|
|
169
171
|
}) {
|
|
170
172
|
assert(props.accountId, "accountId is required");
|
|
171
173
|
assert(props.apiToken, "apiToken is required");
|
|
172
|
-
const
|
|
174
|
+
const previewToken = useWorker({
|
|
173
175
|
name: props.name,
|
|
174
176
|
bundle: props.bundle,
|
|
175
177
|
format: props.format,
|
|
176
|
-
modules: [],
|
|
178
|
+
modules: props.bundle ? props.bundle.modules : [],
|
|
177
179
|
accountId: props.accountId,
|
|
178
180
|
apiToken: props.apiToken,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
bindings: props.bindings,
|
|
182
|
+
assetPaths: props.assetPaths,
|
|
181
183
|
port: props.port,
|
|
182
184
|
compatibilityDate: props.compatibilityDate,
|
|
183
185
|
compatibilityFlags: props.compatibilityFlags,
|
|
184
186
|
usageModel: props.usageModel,
|
|
187
|
+
env: props.env,
|
|
185
188
|
});
|
|
186
189
|
|
|
187
|
-
|
|
190
|
+
usePreviewServer({
|
|
191
|
+
previewToken,
|
|
192
|
+
publicRoot: props.public,
|
|
193
|
+
port: props.port,
|
|
194
|
+
});
|
|
188
195
|
|
|
189
|
-
useInspector(
|
|
196
|
+
useInspector({
|
|
197
|
+
inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
|
|
198
|
+
port: 9229,
|
|
199
|
+
logToTerminal: true,
|
|
200
|
+
});
|
|
190
201
|
return null;
|
|
191
202
|
}
|
|
192
203
|
function Local(props: {
|
|
193
|
-
name:
|
|
194
|
-
bundle: EsbuildBundle |
|
|
195
|
-
format: CfScriptFormat;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
name: undefined | string;
|
|
205
|
+
bundle: EsbuildBundle | undefined;
|
|
206
|
+
format: CfScriptFormat | undefined;
|
|
207
|
+
bindings: CfWorkerInit["bindings"];
|
|
208
|
+
assetPaths: undefined | AssetPaths;
|
|
209
|
+
public: undefined | string;
|
|
199
210
|
port: number;
|
|
211
|
+
enableLocalPersistence: boolean;
|
|
200
212
|
}) {
|
|
201
213
|
const { inspectorUrl } = useLocalWorker({
|
|
202
214
|
name: props.name,
|
|
203
215
|
bundle: props.bundle,
|
|
204
216
|
format: props.format,
|
|
205
|
-
|
|
217
|
+
bindings: props.bindings,
|
|
218
|
+
assetPaths: props.assetPaths,
|
|
219
|
+
public: props.public,
|
|
206
220
|
port: props.port,
|
|
221
|
+
enableLocalPersistence: props.enableLocalPersistence,
|
|
207
222
|
});
|
|
208
|
-
useInspector(inspectorUrl);
|
|
223
|
+
useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
|
|
209
224
|
return null;
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
function useLocalWorker(props: {
|
|
213
|
-
name:
|
|
214
|
-
bundle: EsbuildBundle |
|
|
215
|
-
format: CfScriptFormat;
|
|
216
|
-
|
|
228
|
+
name: undefined | string;
|
|
229
|
+
bundle: EsbuildBundle | undefined;
|
|
230
|
+
format: CfScriptFormat | undefined;
|
|
231
|
+
bindings: CfWorkerInit["bindings"];
|
|
232
|
+
assetPaths: undefined | AssetPaths;
|
|
233
|
+
public: undefined | string;
|
|
217
234
|
port: number;
|
|
235
|
+
enableLocalPersistence: boolean;
|
|
218
236
|
}) {
|
|
219
237
|
// TODO: pass vars via command line
|
|
220
|
-
const { bundle, format,
|
|
238
|
+
const { bundle, format, bindings, port, assetPaths } = props;
|
|
221
239
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
222
240
|
const removeSignalExitListener = useRef<() => void>();
|
|
223
|
-
const [inspectorUrl, setInspectorUrl] = useState<string |
|
|
241
|
+
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
224
242
|
useEffect(() => {
|
|
225
243
|
async function startLocalWorker() {
|
|
226
|
-
if (!bundle) return;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
if (!bundle || !format) return;
|
|
245
|
+
|
|
246
|
+
await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
|
|
247
|
+
if (props.public) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
'⎔ A "public" folder is not yet supported in local mode.'
|
|
250
|
+
);
|
|
231
251
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
252
|
+
|
|
253
|
+
// In local mode, we want to copy all referenced modules into
|
|
254
|
+
// the output bundle directory before starting up
|
|
255
|
+
for (const module of bundle.modules) {
|
|
256
|
+
await writeFile(
|
|
257
|
+
path.join(path.dirname(bundle.path), module.name),
|
|
258
|
+
module.content
|
|
259
|
+
);
|
|
236
260
|
}
|
|
237
261
|
|
|
238
262
|
console.log("⎔ Starting a local server...");
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
"
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
// TODO: just use execa for this
|
|
264
|
+
local.current = spawn(
|
|
265
|
+
"node",
|
|
266
|
+
[
|
|
267
|
+
"--experimental-vm-modules",
|
|
268
|
+
"--inspect",
|
|
269
|
+
require.resolve("miniflare/cli"),
|
|
270
|
+
bundle.path,
|
|
271
|
+
"--watch",
|
|
272
|
+
"--wrangler-config",
|
|
273
|
+
path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
|
|
274
|
+
"--env",
|
|
275
|
+
path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
|
|
276
|
+
"--package",
|
|
277
|
+
path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
|
|
278
|
+
"--port",
|
|
279
|
+
port.toString(),
|
|
280
|
+
...(assetPaths
|
|
281
|
+
? [
|
|
282
|
+
"--site",
|
|
283
|
+
path.join(process.cwd(), assetPaths.baseDirectory),
|
|
284
|
+
...assetPaths.includePatterns.map((pattern) => [
|
|
285
|
+
"--site-include",
|
|
286
|
+
pattern,
|
|
287
|
+
]),
|
|
288
|
+
...assetPaths.excludePatterns.map((pattern) => [
|
|
289
|
+
"--site-exclude",
|
|
290
|
+
pattern,
|
|
291
|
+
]),
|
|
292
|
+
].flatMap((x) => x)
|
|
293
|
+
: []),
|
|
294
|
+
...(props.enableLocalPersistence
|
|
295
|
+
? ["--kv-persist", "--cache-persist", "--do-persist"]
|
|
296
|
+
: []),
|
|
297
|
+
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
298
|
+
return ["--binding", `${key}=${value}`];
|
|
299
|
+
}),
|
|
300
|
+
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
301
|
+
return ["--kv", binding];
|
|
302
|
+
}),
|
|
303
|
+
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
304
|
+
({ name, class_name }) => {
|
|
305
|
+
return ["--do", `${name}=${class_name}`];
|
|
267
306
|
}
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
307
|
+
),
|
|
308
|
+
...Object.entries(bindings.wasm_modules || {}).flatMap(
|
|
309
|
+
([name, filePath]) => {
|
|
310
|
+
return [
|
|
311
|
+
"--wasm",
|
|
312
|
+
`${name}=${path.join(process.cwd(), filePath)}`,
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
),
|
|
316
|
+
"--modules",
|
|
317
|
+
String(format === "modules"),
|
|
318
|
+
"--modules-rule",
|
|
319
|
+
"CompiledWasm=**/*.wasm",
|
|
320
|
+
],
|
|
321
|
+
{
|
|
322
|
+
cwd: path.dirname(bundle.path),
|
|
323
|
+
}
|
|
324
|
+
);
|
|
276
325
|
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
277
326
|
|
|
278
327
|
local.current.on("close", (code) => {
|
|
@@ -281,21 +330,32 @@ function useLocalWorker(props: {
|
|
|
281
330
|
}
|
|
282
331
|
});
|
|
283
332
|
|
|
284
|
-
local.current.stdout
|
|
285
|
-
|
|
333
|
+
local.current.stdout?.on("data", (data: Buffer) => {
|
|
334
|
+
console.log(`${data.toString()}`);
|
|
286
335
|
});
|
|
287
336
|
|
|
288
|
-
local.current.stderr
|
|
289
|
-
|
|
337
|
+
local.current.stderr?.on("data", (data: Buffer) => {
|
|
338
|
+
console.error(`${data.toString()}`);
|
|
290
339
|
const matches =
|
|
291
340
|
/Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec(
|
|
292
|
-
data
|
|
341
|
+
data.toString()
|
|
293
342
|
);
|
|
294
343
|
if (matches) {
|
|
295
344
|
setInspectorUrl(matches[1]);
|
|
296
345
|
}
|
|
297
346
|
});
|
|
298
347
|
|
|
348
|
+
local.current.on("exit", (code) => {
|
|
349
|
+
if (code !== 0) {
|
|
350
|
+
console.error(`miniflare process exited with code ${code}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
local.current.on("error", (error: Error) => {
|
|
355
|
+
console.error(`miniflare process failed to spawn`);
|
|
356
|
+
console.error(error);
|
|
357
|
+
});
|
|
358
|
+
|
|
299
359
|
removeSignalExitListener.current = onExit((_code, _signal) => {
|
|
300
360
|
console.log("⎔ Shutting down local server.");
|
|
301
361
|
local.current?.kill();
|
|
@@ -303,7 +363,9 @@ function useLocalWorker(props: {
|
|
|
303
363
|
});
|
|
304
364
|
}
|
|
305
365
|
|
|
306
|
-
startLocalWorker()
|
|
366
|
+
startLocalWorker().catch((err) => {
|
|
367
|
+
console.error("local worker:", err);
|
|
368
|
+
});
|
|
307
369
|
|
|
308
370
|
return () => {
|
|
309
371
|
if (local.current) {
|
|
@@ -314,12 +376,24 @@ function useLocalWorker(props: {
|
|
|
314
376
|
removeSignalExitListener.current = undefined;
|
|
315
377
|
}
|
|
316
378
|
};
|
|
317
|
-
}, [
|
|
379
|
+
}, [
|
|
380
|
+
bundle,
|
|
381
|
+
format,
|
|
382
|
+
port,
|
|
383
|
+
bindings.durable_objects?.bindings,
|
|
384
|
+
bindings.kv_namespaces,
|
|
385
|
+
bindings.vars,
|
|
386
|
+
props.enableLocalPersistence,
|
|
387
|
+
assetPaths,
|
|
388
|
+
props.public,
|
|
389
|
+
bindings.wasm_modules,
|
|
390
|
+
]);
|
|
318
391
|
return { inspectorUrl };
|
|
319
392
|
}
|
|
320
393
|
|
|
321
|
-
function useTmpDir(): string |
|
|
394
|
+
function useTmpDir(): string | undefined {
|
|
322
395
|
const [directory, setDirectory] = useState<DirectoryResult>();
|
|
396
|
+
const handleError = useErrorHandler();
|
|
323
397
|
useEffect(() => {
|
|
324
398
|
let dir: DirectoryResult;
|
|
325
399
|
async function create() {
|
|
@@ -329,52 +403,120 @@ function useTmpDir(): string | void {
|
|
|
329
403
|
return;
|
|
330
404
|
} catch (err) {
|
|
331
405
|
console.error("failed to create tmp dir");
|
|
332
|
-
console.error(err);
|
|
333
406
|
throw err;
|
|
334
407
|
}
|
|
335
408
|
}
|
|
336
|
-
create()
|
|
409
|
+
create().catch((err) => {
|
|
410
|
+
// we want to break here
|
|
411
|
+
// we can't do much without a temp dir anyway
|
|
412
|
+
handleError(err);
|
|
413
|
+
});
|
|
337
414
|
return () => {
|
|
338
|
-
dir.cleanup()
|
|
415
|
+
dir.cleanup().catch(() => {
|
|
416
|
+
// extremely unlikely,
|
|
417
|
+
// but it's 2022 after all
|
|
418
|
+
console.error("failed to cleanup tmp dir");
|
|
419
|
+
});
|
|
339
420
|
};
|
|
340
|
-
}, []);
|
|
421
|
+
}, [handleError]);
|
|
341
422
|
return directory?.path;
|
|
342
423
|
}
|
|
343
424
|
|
|
425
|
+
function useCustomBuild(
|
|
426
|
+
expectedEntry: string,
|
|
427
|
+
props: {
|
|
428
|
+
command?: undefined | string;
|
|
429
|
+
cwd?: undefined | string;
|
|
430
|
+
watch_dir?: undefined | string;
|
|
431
|
+
}
|
|
432
|
+
): undefined | string {
|
|
433
|
+
const [entry, setEntry] = useState<string | undefined>(
|
|
434
|
+
// if there's no build command, just return the expected entry
|
|
435
|
+
props.command || expectedEntry
|
|
436
|
+
);
|
|
437
|
+
const { command, cwd, watch_dir } = props;
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
if (!command) return;
|
|
440
|
+
let cmd, interval;
|
|
441
|
+
console.log("running:", command);
|
|
442
|
+
cmd = execaCommand(command, {
|
|
443
|
+
...(cwd && { cwd }),
|
|
444
|
+
shell: true,
|
|
445
|
+
stderr: "inherit",
|
|
446
|
+
stdout: "inherit",
|
|
447
|
+
});
|
|
448
|
+
if (watch_dir) {
|
|
449
|
+
watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
|
|
450
|
+
"all",
|
|
451
|
+
(_event, filePath) => {
|
|
452
|
+
console.log(`The file ${filePath} changed, restarting build...`);
|
|
453
|
+
cmd.kill();
|
|
454
|
+
cmd = execaCommand(command, {
|
|
455
|
+
...(cwd && { cwd }),
|
|
456
|
+
shell: true,
|
|
457
|
+
stderr: "inherit",
|
|
458
|
+
stdout: "inherit",
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// check every so often whether `expectedEntry` exists
|
|
465
|
+
// if it does, we're done
|
|
466
|
+
const startedAt = Date.now();
|
|
467
|
+
interval = setInterval(() => {
|
|
468
|
+
if (existsSync(expectedEntry)) {
|
|
469
|
+
clearInterval(interval);
|
|
470
|
+
setEntry(expectedEntry);
|
|
471
|
+
} else {
|
|
472
|
+
const elapsed = Date.now() - startedAt;
|
|
473
|
+
// timeout after 30 seconds of waiting
|
|
474
|
+
if (elapsed > 1000 * 60 * 30) {
|
|
475
|
+
console.error("⎔ Build timed out.");
|
|
476
|
+
clearInterval(interval);
|
|
477
|
+
cmd.kill();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}, 200);
|
|
481
|
+
// TODO: we could probably timeout here after a while
|
|
482
|
+
|
|
483
|
+
return () => {
|
|
484
|
+
if (cmd) {
|
|
485
|
+
cmd.kill();
|
|
486
|
+
cmd = undefined;
|
|
487
|
+
}
|
|
488
|
+
clearInterval(interval);
|
|
489
|
+
interval = undefined;
|
|
490
|
+
};
|
|
491
|
+
}, [command, cwd, expectedEntry, watch_dir]);
|
|
492
|
+
return entry;
|
|
493
|
+
}
|
|
494
|
+
|
|
344
495
|
type EsbuildBundle = {
|
|
345
496
|
id: number;
|
|
346
497
|
path: string;
|
|
347
498
|
entry: string;
|
|
348
499
|
type: "esm" | "commonjs";
|
|
349
500
|
exports: string[];
|
|
501
|
+
modules: CfModule[];
|
|
350
502
|
};
|
|
351
503
|
|
|
352
504
|
function useEsbuild(props: {
|
|
353
|
-
entry: string;
|
|
354
|
-
destination: string |
|
|
355
|
-
staticRoot:
|
|
356
|
-
jsxFactory: string |
|
|
357
|
-
jsxFragment: string |
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const {
|
|
361
|
-
entry,
|
|
362
|
-
destination,
|
|
363
|
-
staticRoot,
|
|
364
|
-
jsxFactory,
|
|
365
|
-
jsxFragment,
|
|
366
|
-
polyfillNode,
|
|
367
|
-
} = props;
|
|
505
|
+
entry: undefined | string;
|
|
506
|
+
destination: string | undefined;
|
|
507
|
+
staticRoot: undefined | string;
|
|
508
|
+
jsxFactory: string | undefined;
|
|
509
|
+
jsxFragment: string | undefined;
|
|
510
|
+
}): EsbuildBundle | undefined {
|
|
511
|
+
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
368
512
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
369
513
|
useEffect(() => {
|
|
370
|
-
let result: esbuild.BuildResult;
|
|
514
|
+
let result: esbuild.BuildResult | undefined;
|
|
371
515
|
async function build() {
|
|
372
|
-
if (!destination) return;
|
|
516
|
+
if (!destination || !entry) return;
|
|
517
|
+
const moduleCollector = makeModuleCollector();
|
|
373
518
|
result = await esbuild.build({
|
|
374
519
|
entryPoints: [entry],
|
|
375
|
-
define: {
|
|
376
|
-
...(polyfillNode && { global: "globalThis" }),
|
|
377
|
-
},
|
|
378
520
|
bundle: true,
|
|
379
521
|
outdir: destination,
|
|
380
522
|
metafile: true,
|
|
@@ -382,13 +524,15 @@ function useEsbuild(props: {
|
|
|
382
524
|
sourcemap: true,
|
|
383
525
|
loader: {
|
|
384
526
|
".js": "jsx",
|
|
527
|
+
".html": "text",
|
|
528
|
+
".pem": "text",
|
|
529
|
+
".txt": "text",
|
|
385
530
|
},
|
|
386
|
-
plugins: polyfillNode
|
|
387
|
-
? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
|
|
388
|
-
: undefined,
|
|
389
531
|
...(jsxFactory && { jsxFactory }),
|
|
390
532
|
...(jsxFragment && { jsxFragment }),
|
|
391
533
|
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
534
|
+
conditions: ["worker", "browser"],
|
|
535
|
+
plugins: [moduleCollector.plugin],
|
|
392
536
|
// TODO: import.meta.url
|
|
393
537
|
watch: {
|
|
394
538
|
async onRebuild(error) {
|
|
@@ -396,46 +540,68 @@ function useEsbuild(props: {
|
|
|
396
540
|
else {
|
|
397
541
|
// nothing really changes here, so let's increment the id
|
|
398
542
|
// to change the return object's identity
|
|
399
|
-
setBundle((
|
|
543
|
+
setBundle((previousBundle) => {
|
|
544
|
+
if (previousBundle === undefined) {
|
|
545
|
+
assert.fail(
|
|
546
|
+
"Rebuild triggered with no previous build available"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
return { ...previousBundle, id: previousBundle.id + 1 };
|
|
550
|
+
});
|
|
400
551
|
}
|
|
401
552
|
},
|
|
402
553
|
},
|
|
403
554
|
});
|
|
404
555
|
|
|
405
|
-
|
|
556
|
+
// result.metafile is defined because of the `metafile: true` option above.
|
|
557
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
558
|
+
const metafile = result.metafile!;
|
|
559
|
+
const outputEntry = Object.entries(metafile.outputs).find(
|
|
406
560
|
([_path, { entryPoint }]) => entryPoint === entry
|
|
407
561
|
); // assumedly only one entry point
|
|
408
562
|
|
|
563
|
+
if (outputEntry === undefined) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Cannot find entry-point "${entry}" in generated bundle.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
409
568
|
setBundle({
|
|
410
569
|
id: 0,
|
|
411
570
|
entry,
|
|
412
|
-
path:
|
|
413
|
-
type:
|
|
414
|
-
exports:
|
|
571
|
+
path: outputEntry[0],
|
|
572
|
+
type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs",
|
|
573
|
+
exports: outputEntry[1].exports,
|
|
574
|
+
modules: moduleCollector.modules,
|
|
415
575
|
});
|
|
416
576
|
}
|
|
417
|
-
build()
|
|
577
|
+
build().catch((_err) => {
|
|
578
|
+
// esbuild already logs errors to stderr
|
|
579
|
+
// and we don't want to end the process
|
|
580
|
+
// on build errors anyway
|
|
581
|
+
// so this is a no-op error handler
|
|
582
|
+
});
|
|
418
583
|
return () => {
|
|
419
|
-
result?.stop();
|
|
584
|
+
result?.stop?.();
|
|
420
585
|
};
|
|
421
|
-
}, [entry, destination, staticRoot, jsxFactory, jsxFragment
|
|
586
|
+
}, [entry, destination, staticRoot, jsxFactory, jsxFragment]);
|
|
422
587
|
return bundle;
|
|
423
588
|
}
|
|
424
589
|
|
|
425
590
|
function useWorker(props: {
|
|
426
|
-
name:
|
|
427
|
-
bundle: EsbuildBundle |
|
|
428
|
-
format: CfScriptFormat;
|
|
591
|
+
name: undefined | string;
|
|
592
|
+
bundle: EsbuildBundle | undefined;
|
|
593
|
+
format: CfScriptFormat | undefined;
|
|
429
594
|
modules: CfModule[];
|
|
430
595
|
accountId: string;
|
|
431
596
|
apiToken: string;
|
|
432
|
-
|
|
433
|
-
|
|
597
|
+
bindings: CfWorkerInit["bindings"];
|
|
598
|
+
assetPaths: undefined | AssetPaths;
|
|
434
599
|
port: number;
|
|
435
|
-
compatibilityDate: string |
|
|
436
|
-
compatibilityFlags: string[] |
|
|
437
|
-
usageModel:
|
|
438
|
-
|
|
600
|
+
compatibilityDate: string | undefined;
|
|
601
|
+
compatibilityFlags: string[] | undefined;
|
|
602
|
+
usageModel: undefined | "bundled" | "unbound";
|
|
603
|
+
env: string | undefined;
|
|
604
|
+
}): CfPreviewToken | undefined {
|
|
439
605
|
const {
|
|
440
606
|
name,
|
|
441
607
|
bundle,
|
|
@@ -443,68 +609,71 @@ function useWorker(props: {
|
|
|
443
609
|
modules,
|
|
444
610
|
accountId,
|
|
445
611
|
apiToken,
|
|
446
|
-
|
|
447
|
-
|
|
612
|
+
bindings,
|
|
613
|
+
assetPaths,
|
|
448
614
|
compatibilityDate,
|
|
449
615
|
compatibilityFlags,
|
|
450
616
|
usageModel,
|
|
451
617
|
port,
|
|
452
618
|
} = props;
|
|
453
|
-
const [token, setToken] = useState<CfPreviewToken>();
|
|
619
|
+
const [token, setToken] = useState<CfPreviewToken | undefined>();
|
|
620
|
+
|
|
621
|
+
// This is the most reliable way to detect whether
|
|
622
|
+
// something's "happened" in our system; We make a ref and
|
|
623
|
+
// mark it once we log our initial message. Refs are vars!
|
|
624
|
+
const startedRef = useRef(false);
|
|
625
|
+
|
|
454
626
|
useEffect(() => {
|
|
455
627
|
async function start() {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// TODO: a much better error message here, with what to do next
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (format === "service-worker" && bundle.type !== "esm") {
|
|
463
|
-
console.error("⎔ Cannot use service-worker with a esm bundle.");
|
|
464
|
-
// TODO: a much better error message here, with what to do next
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
628
|
+
setToken(undefined); // reset token in case we're re-running
|
|
629
|
+
|
|
630
|
+
if (!bundle || !format) return;
|
|
467
631
|
|
|
468
|
-
if (
|
|
469
|
-
|
|
632
|
+
if (!startedRef.current) {
|
|
633
|
+
startedRef.current = true;
|
|
470
634
|
} else {
|
|
471
|
-
console.log("⎔
|
|
635
|
+
console.log("⎔ Detected changes, restarted server.");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const assets = await syncAssets(
|
|
639
|
+
accountId,
|
|
640
|
+
name || path.basename(bundle.path),
|
|
641
|
+
assetPaths,
|
|
642
|
+
true,
|
|
643
|
+
props.env
|
|
644
|
+
); // TODO: cancellable?
|
|
645
|
+
|
|
646
|
+
let content = await readFile(bundle.path, "utf-8");
|
|
647
|
+
if (format === "service-worker" && assets.manifest) {
|
|
648
|
+
content = `const __STATIC_CONTENT_MANIFEST = ${JSON.stringify(
|
|
649
|
+
assets.manifest
|
|
650
|
+
)};\n${content}`;
|
|
472
651
|
}
|
|
473
652
|
|
|
474
|
-
const assets = sitesFolder
|
|
475
|
-
? await syncAssets(
|
|
476
|
-
accountId,
|
|
477
|
-
path.basename(bundle.path),
|
|
478
|
-
sitesFolder,
|
|
479
|
-
true,
|
|
480
|
-
undefined // TODO: env
|
|
481
|
-
)
|
|
482
|
-
: {
|
|
483
|
-
manifest: undefined,
|
|
484
|
-
namespace: undefined,
|
|
485
|
-
}; // TODO: cancellable?
|
|
486
|
-
|
|
487
|
-
const content = await readFile(bundle.path, "utf-8");
|
|
488
653
|
const init: CfWorkerInit = {
|
|
489
|
-
name
|
|
654
|
+
name,
|
|
490
655
|
main: {
|
|
491
656
|
name: path.basename(bundle.path),
|
|
492
|
-
type: format
|
|
657
|
+
type: format === "modules" ? "esm" : "commonjs",
|
|
493
658
|
content,
|
|
494
659
|
},
|
|
495
|
-
modules:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
660
|
+
modules: modules.concat(
|
|
661
|
+
assets.manifest && format === "modules"
|
|
662
|
+
? {
|
|
663
|
+
name: "__STATIC_CONTENT_MANIFEST",
|
|
664
|
+
content: JSON.stringify(assets.manifest),
|
|
665
|
+
type: "text",
|
|
666
|
+
}
|
|
667
|
+
: []
|
|
668
|
+
),
|
|
669
|
+
bindings: {
|
|
670
|
+
...bindings,
|
|
671
|
+
kv_namespaces: (bindings.kv_namespaces || []).concat(
|
|
672
|
+
assets.namespace
|
|
673
|
+
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
674
|
+
: []
|
|
675
|
+
),
|
|
676
|
+
},
|
|
508
677
|
migrations: undefined, // no migrations in dev
|
|
509
678
|
compatibility_date: compatibilityDate,
|
|
510
679
|
compatibility_flags: compatibilityFlags,
|
|
@@ -516,9 +685,12 @@ function useWorker(props: {
|
|
|
516
685
|
apiToken,
|
|
517
686
|
})
|
|
518
687
|
);
|
|
519
|
-
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
520
688
|
}
|
|
521
|
-
start()
|
|
689
|
+
start().catch((err) => {
|
|
690
|
+
// we want to log the error, but not end the process
|
|
691
|
+
// since it could recover after the developer fixes whatever's wrong
|
|
692
|
+
console.error("remote worker:", err);
|
|
693
|
+
});
|
|
522
694
|
}, [
|
|
523
695
|
name,
|
|
524
696
|
bundle,
|
|
@@ -526,83 +698,17 @@ function useWorker(props: {
|
|
|
526
698
|
accountId,
|
|
527
699
|
apiToken,
|
|
528
700
|
port,
|
|
529
|
-
|
|
701
|
+
assetPaths,
|
|
530
702
|
compatibilityDate,
|
|
531
703
|
compatibilityFlags,
|
|
532
704
|
usageModel,
|
|
705
|
+
bindings,
|
|
706
|
+
modules,
|
|
707
|
+
props.env,
|
|
533
708
|
]);
|
|
534
709
|
return token;
|
|
535
710
|
}
|
|
536
711
|
|
|
537
|
-
function useProxy({
|
|
538
|
-
token,
|
|
539
|
-
publicRoot,
|
|
540
|
-
port,
|
|
541
|
-
}: {
|
|
542
|
-
token: CfPreviewToken | void;
|
|
543
|
-
publicRoot: void | string;
|
|
544
|
-
port: number;
|
|
545
|
-
}) {
|
|
546
|
-
useEffect(() => {
|
|
547
|
-
if (!token) return;
|
|
548
|
-
const proxy = httpProxy.createProxyServer({
|
|
549
|
-
secure: false,
|
|
550
|
-
changeOrigin: true,
|
|
551
|
-
headers: {
|
|
552
|
-
"cf-workers-preview-token": token.value,
|
|
553
|
-
},
|
|
554
|
-
target: `https://${token.host}`,
|
|
555
|
-
// TODO: log websockets too? validate durables, etc
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
const servePublic =
|
|
559
|
-
publicRoot &&
|
|
560
|
-
serveStatic(publicRoot, {
|
|
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
|
|
574
|
-
|
|
575
|
-
proxy.on("proxyRes", function (proxyRes, req, res) {
|
|
576
|
-
// log all requests
|
|
577
|
-
console.log(
|
|
578
|
-
new Date().toLocaleTimeString(),
|
|
579
|
-
req.method,
|
|
580
|
-
req.url,
|
|
581
|
-
res.statusCode // TODO add a status message like Ok etc?
|
|
582
|
-
);
|
|
583
|
-
});
|
|
584
|
-
// TODO: log errors?
|
|
585
|
-
|
|
586
|
-
return () => {
|
|
587
|
-
proxy.close();
|
|
588
|
-
server.close();
|
|
589
|
-
};
|
|
590
|
-
}, [token, publicRoot, port]);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function useInspector(inspectorUrl: string | void) {
|
|
594
|
-
useEffect(() => {
|
|
595
|
-
if (!inspectorUrl) return;
|
|
596
|
-
|
|
597
|
-
const inspector = new DtInspector(inspectorUrl);
|
|
598
|
-
const abortController = inspector.proxyTo(9229);
|
|
599
|
-
return () => {
|
|
600
|
-
inspector.close();
|
|
601
|
-
abortController.abort();
|
|
602
|
-
};
|
|
603
|
-
}, [inspectorUrl]);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
712
|
function sleep(period: number) {
|
|
607
713
|
return new Promise((resolve) => setTimeout(resolve, period));
|
|
608
714
|
}
|
|
@@ -610,7 +716,7 @@ const SLEEP_DURATION = 2000;
|
|
|
610
716
|
// really need a first class api for this
|
|
611
717
|
const hostNameRegex = /userHostname="(.*)"/g;
|
|
612
718
|
async function findTunnelHostname() {
|
|
613
|
-
let hostName: string;
|
|
719
|
+
let hostName: string | undefined;
|
|
614
720
|
while (!hostName) {
|
|
615
721
|
try {
|
|
616
722
|
const resp = await fetch("http://localhost:8789/metrics");
|
|
@@ -662,12 +768,14 @@ function useTunnel(toggle: boolean) {
|
|
|
662
768
|
});
|
|
663
769
|
|
|
664
770
|
const hostName = await findTunnelHostname();
|
|
665
|
-
clipboardy.write(hostName);
|
|
771
|
+
await clipboardy.write(hostName);
|
|
666
772
|
console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
|
|
667
773
|
}
|
|
668
774
|
}
|
|
669
775
|
|
|
670
|
-
startTunnel()
|
|
776
|
+
startTunnel().catch((err) => {
|
|
777
|
+
console.error("tunnel:", err);
|
|
778
|
+
});
|
|
671
779
|
|
|
672
780
|
return () => {
|
|
673
781
|
if (tunnel.current) {
|
|
@@ -689,41 +797,41 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
689
797
|
// UGH, we should put port in context instead
|
|
690
798
|
const [toggles, setToggles] = useState(initial);
|
|
691
799
|
useInput(
|
|
692
|
-
(
|
|
800
|
+
async (
|
|
693
801
|
input,
|
|
694
802
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
695
803
|
key
|
|
696
804
|
) => {
|
|
697
|
-
switch (input) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
// {
|
|
702
|
-
// app: {
|
|
703
|
-
// name: open.apps.chrome, // TODO: fallback on other browsers
|
|
704
|
-
// },
|
|
705
|
-
// }
|
|
706
|
-
);
|
|
805
|
+
switch (input.toLowerCase()) {
|
|
806
|
+
// open browser
|
|
807
|
+
case "b": {
|
|
808
|
+
await openInBrowser(`http://localhost:${port}`);
|
|
707
809
|
break;
|
|
708
|
-
|
|
709
|
-
|
|
810
|
+
}
|
|
811
|
+
// toggle inspector
|
|
812
|
+
case "d": {
|
|
813
|
+
await openInBrowser(
|
|
710
814
|
`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
815
|
);
|
|
718
816
|
break;
|
|
719
|
-
|
|
720
|
-
|
|
817
|
+
}
|
|
818
|
+
// toggle tunnel
|
|
819
|
+
case "s":
|
|
820
|
+
setToggles((previousToggles) => ({
|
|
821
|
+
...previousToggles,
|
|
822
|
+
tunnel: !previousToggles.tunnel,
|
|
823
|
+
}));
|
|
721
824
|
break;
|
|
722
|
-
|
|
723
|
-
|
|
825
|
+
// toggle local
|
|
826
|
+
case "l":
|
|
827
|
+
setToggles((previousToggles) => ({
|
|
828
|
+
...previousToggles,
|
|
829
|
+
local: !previousToggles.local,
|
|
830
|
+
}));
|
|
724
831
|
break;
|
|
725
|
-
|
|
726
|
-
case "
|
|
832
|
+
// shut down
|
|
833
|
+
case "q":
|
|
834
|
+
case "x":
|
|
727
835
|
process.exit(0);
|
|
728
836
|
break;
|
|
729
837
|
default:
|
|
@@ -734,3 +842,16 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
734
842
|
);
|
|
735
843
|
return toggles;
|
|
736
844
|
}
|
|
845
|
+
|
|
846
|
+
function ErrorFallback(props: { error: Error }) {
|
|
847
|
+
const { exit } = useApp();
|
|
848
|
+
useEffect(() => exit(new Error()));
|
|
849
|
+
return (
|
|
850
|
+
<>
|
|
851
|
+
<Text>Something went wrong:</Text>
|
|
852
|
+
<Text>{props.error.stack}</Text>
|
|
853
|
+
</>
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export default withErrorBoundary(Dev, { FallbackComponent: ErrorFallback });
|