wrangler 0.0.13 → 0.0.17
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 +20 -11
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +112 -28
- package/pages/functions/filepath-routing.ts +44 -51
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/dev.test.tsx +42 -5
- package/src/__tests__/guess-worker-format.test.ts +66 -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/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +43 -9
- package/src/__tests__/helpers/mock-console.ts +62 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/mock-web-socket.ts +37 -0
- package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +1 -1
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +20 -0
- package/src/__tests__/index.test.ts +418 -71
- package/src/__tests__/jest.setup.ts +30 -2
- package/src/__tests__/kv.test.ts +147 -252
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1136 -291
- package/src/__tests__/r2.test.ts +206 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +246 -0
- package/src/__tests__/whoami.test.tsx +6 -47
- package/src/api/form_data.ts +75 -25
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +34 -15
- package/src/bundle.ts +127 -0
- package/src/cfetch/index.ts +7 -15
- package/src/cfetch/internal.ts +41 -6
- package/src/cli.ts +10 -0
- package/src/config.ts +125 -95
- package/src/dev.tsx +300 -193
- package/src/dialogs.tsx +2 -2
- package/src/guess-worker-format.ts +68 -0
- package/src/index.tsx +578 -192
- package/src/inspect.ts +29 -10
- package/src/kv.tsx +23 -17
- package/src/module-collection.ts +32 -12
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +28 -23
- package/src/paths.ts +26 -0
- package/src/proxy.ts +88 -14
- package/src/publish.ts +260 -297
- package/src/r2.ts +50 -0
- package/src/reporting.ts +115 -0
- package/src/sites.tsx +28 -27
- package/src/tail.tsx +178 -9
- package/src/user.tsx +58 -44
- 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 +124315 -104677
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-console.ts +0 -34
- package/src/__tests__/run-wrangler.ts +0 -8
package/src/dev.tsx
CHANGED
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
import esbuild from "esbuild";
|
|
2
1
|
import assert from "node:assert";
|
|
3
2
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
3
|
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
6
5
|
import path from "node:path";
|
|
7
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
8
6
|
import { watch } from "chokidar";
|
|
9
7
|
import clipboardy from "clipboardy";
|
|
10
8
|
import commandExists from "command-exists";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import open from "open";
|
|
9
|
+
import { execaCommand } from "execa";
|
|
10
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
14
11
|
import React, { useState, useEffect, useRef } from "react";
|
|
15
12
|
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
16
13
|
import onExit from "signal-exit";
|
|
17
14
|
import tmp from "tmp-promise";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
import type { CfPreviewToken } from "./api/preview";
|
|
21
|
-
import type { CfModule } from "./api/worker";
|
|
15
|
+
import { fetch } from "undici";
|
|
22
16
|
import { createWorker } from "./api/worker";
|
|
23
|
-
import
|
|
24
|
-
|
|
17
|
+
import { bundleWorker } from "./bundle";
|
|
18
|
+
import guessWorkerFormat from "./guess-worker-format";
|
|
25
19
|
import useInspector from "./inspect";
|
|
26
|
-
import
|
|
27
|
-
import { usePreviewServer } from "./proxy";
|
|
28
|
-
import type { AssetPaths } from "./sites";
|
|
20
|
+
import openInBrowser from "./open-in-browser";
|
|
21
|
+
import { usePreviewServer, waitForPortToBeAvailable } from "./proxy";
|
|
29
22
|
import { syncAssets } from "./sites";
|
|
30
23
|
import { getAPIToken } from "./user";
|
|
31
|
-
|
|
32
|
-
type
|
|
24
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
25
|
+
import type { CfModule, CfWorkerInit, CfScriptFormat } from "./api/worker";
|
|
26
|
+
import type { Entry } from "./bundle";
|
|
27
|
+
import type { AssetPaths } from "./sites";
|
|
28
|
+
import type { WatchMode } from "esbuild";
|
|
29
|
+
import type { ExecaChildProcess } from "execa";
|
|
30
|
+
import type { DirectoryResult } from "tmp-promise";
|
|
33
31
|
|
|
34
32
|
export type DevProps = {
|
|
35
33
|
name?: string;
|
|
36
|
-
entry:
|
|
34
|
+
entry: Entry;
|
|
37
35
|
port?: number;
|
|
38
|
-
format: CfScriptFormat;
|
|
36
|
+
format: CfScriptFormat | undefined;
|
|
39
37
|
accountId: undefined | string;
|
|
40
38
|
initialMode: "local" | "remote";
|
|
41
39
|
jsxFactory: undefined | string;
|
|
42
40
|
jsxFragment: undefined | string;
|
|
41
|
+
enableLocalPersistence: boolean;
|
|
43
42
|
bindings: CfWorkerInit["bindings"];
|
|
44
43
|
public: undefined | string;
|
|
45
44
|
assetPaths: undefined | AssetPaths;
|
|
@@ -51,16 +50,12 @@ export type DevProps = {
|
|
|
51
50
|
cwd?: undefined | string;
|
|
52
51
|
watch_dir?: undefined | string;
|
|
53
52
|
};
|
|
53
|
+
env: string | undefined;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
function Dev(props: DevProps): JSX.Element {
|
|
57
|
-
if (props.public && props.format === "service-worker") {
|
|
58
|
-
throw new Error(
|
|
59
|
-
"You cannot use the service worker format with a `public` directory."
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
57
|
const port = props.port ?? 8787;
|
|
63
|
-
const apiToken = getAPIToken();
|
|
58
|
+
const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
|
|
64
59
|
const directory = useTmpDir();
|
|
65
60
|
|
|
66
61
|
// if there isn't a build command, we just return the entry immediately
|
|
@@ -68,18 +63,28 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
68
63
|
// kinda forbid that, so we thread the entry through useCustomBuild
|
|
69
64
|
const entry = useCustomBuild(props.entry, props.buildCommand);
|
|
70
65
|
|
|
66
|
+
const format = useWorkerFormat({ entry: props.entry, format: props.format });
|
|
67
|
+
if (format && props.public && format === "service-worker") {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"You cannot use the service worker format with a `public` directory."
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (props.bindings.wasm_modules && format === "modules") {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
const bundle = useEsbuild({
|
|
72
80
|
entry,
|
|
81
|
+
format,
|
|
73
82
|
destination: directory,
|
|
74
83
|
staticRoot: props.public,
|
|
75
84
|
jsxFactory: props.jsxFactory,
|
|
76
85
|
jsxFragment: props.jsxFragment,
|
|
86
|
+
serveAssetsFromWorker: !!props.public,
|
|
77
87
|
});
|
|
78
|
-
if (bundle && bundle.type === "commonjs" && !props.format && props.public) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"You cannot use the service worker format with a `public` directory."
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
88
|
|
|
84
89
|
const toggles = useHotkeys(
|
|
85
90
|
{
|
|
@@ -97,17 +102,18 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
97
102
|
<Local
|
|
98
103
|
name={props.name}
|
|
99
104
|
bundle={bundle}
|
|
100
|
-
format={
|
|
105
|
+
format={format}
|
|
101
106
|
bindings={props.bindings}
|
|
102
|
-
|
|
107
|
+
assetPaths={props.assetPaths}
|
|
103
108
|
public={props.public}
|
|
104
109
|
port={port}
|
|
110
|
+
enableLocalPersistence={props.enableLocalPersistence}
|
|
105
111
|
/>
|
|
106
112
|
) : (
|
|
107
113
|
<Remote
|
|
108
114
|
name={props.name}
|
|
109
115
|
bundle={bundle}
|
|
110
|
-
format={
|
|
116
|
+
format={format}
|
|
111
117
|
accountId={props.accountId}
|
|
112
118
|
apiToken={apiToken}
|
|
113
119
|
bindings={props.bindings}
|
|
@@ -117,6 +123,7 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
117
123
|
compatibilityDate={props.compatibilityDate}
|
|
118
124
|
compatibilityFlags={props.compatibilityFlags}
|
|
119
125
|
usageModel={props.usageModel}
|
|
126
|
+
env={props.env}
|
|
120
127
|
/>
|
|
121
128
|
)}
|
|
122
129
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
@@ -132,10 +139,29 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
132
139
|
);
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
function useWorkerFormat(props: {
|
|
143
|
+
entry: Entry | undefined;
|
|
144
|
+
format: undefined | CfScriptFormat;
|
|
145
|
+
}): CfScriptFormat | undefined {
|
|
146
|
+
const [format, setFormat] = useState<CfScriptFormat | undefined>();
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
async function validateFormat() {
|
|
149
|
+
if (!props.entry || format) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setFormat(await guessWorkerFormat(props.entry, props.format));
|
|
153
|
+
}
|
|
154
|
+
validateFormat().catch((err) => {
|
|
155
|
+
console.error("Failed to validate worker format:", err);
|
|
156
|
+
});
|
|
157
|
+
}, [props.entry, props.format, format]);
|
|
158
|
+
return format;
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
function Remote(props: {
|
|
136
162
|
name: undefined | string;
|
|
137
163
|
bundle: EsbuildBundle | undefined;
|
|
138
|
-
format: CfScriptFormat;
|
|
164
|
+
format: CfScriptFormat | undefined;
|
|
139
165
|
public: undefined | string;
|
|
140
166
|
assetPaths: undefined | AssetPaths;
|
|
141
167
|
port: number;
|
|
@@ -145,6 +171,7 @@ function Remote(props: {
|
|
|
145
171
|
compatibilityDate: string | undefined;
|
|
146
172
|
compatibilityFlags: undefined | string[];
|
|
147
173
|
usageModel: undefined | "bundled" | "unbound";
|
|
174
|
+
env: string | undefined;
|
|
148
175
|
}) {
|
|
149
176
|
assert(props.accountId, "accountId is required");
|
|
150
177
|
assert(props.apiToken, "apiToken is required");
|
|
@@ -161,6 +188,7 @@ function Remote(props: {
|
|
|
161
188
|
compatibilityDate: props.compatibilityDate,
|
|
162
189
|
compatibilityFlags: props.compatibilityFlags,
|
|
163
190
|
usageModel: props.usageModel,
|
|
191
|
+
env: props.env,
|
|
164
192
|
});
|
|
165
193
|
|
|
166
194
|
usePreviewServer({
|
|
@@ -179,18 +207,22 @@ function Remote(props: {
|
|
|
179
207
|
function Local(props: {
|
|
180
208
|
name: undefined | string;
|
|
181
209
|
bundle: EsbuildBundle | undefined;
|
|
182
|
-
format: CfScriptFormat;
|
|
210
|
+
format: CfScriptFormat | undefined;
|
|
183
211
|
bindings: CfWorkerInit["bindings"];
|
|
212
|
+
assetPaths: undefined | AssetPaths;
|
|
184
213
|
public: undefined | string;
|
|
185
|
-
site: undefined | AssetPaths;
|
|
186
214
|
port: number;
|
|
215
|
+
enableLocalPersistence: boolean;
|
|
187
216
|
}) {
|
|
188
217
|
const { inspectorUrl } = useLocalWorker({
|
|
189
218
|
name: props.name,
|
|
190
219
|
bundle: props.bundle,
|
|
191
220
|
format: props.format,
|
|
192
221
|
bindings: props.bindings,
|
|
222
|
+
assetPaths: props.assetPaths,
|
|
223
|
+
public: props.public,
|
|
193
224
|
port: props.port,
|
|
225
|
+
enableLocalPersistence: props.enableLocalPersistence,
|
|
194
226
|
});
|
|
195
227
|
useInspector({ inspectorUrl, port: 9229, logToTerminal: false });
|
|
196
228
|
return null;
|
|
@@ -199,65 +231,115 @@ function Local(props: {
|
|
|
199
231
|
function useLocalWorker(props: {
|
|
200
232
|
name: undefined | string;
|
|
201
233
|
bundle: EsbuildBundle | undefined;
|
|
202
|
-
format: CfScriptFormat;
|
|
234
|
+
format: CfScriptFormat | undefined;
|
|
203
235
|
bindings: CfWorkerInit["bindings"];
|
|
236
|
+
assetPaths: undefined | AssetPaths;
|
|
237
|
+
public: undefined | string;
|
|
204
238
|
port: number;
|
|
239
|
+
enableLocalPersistence: boolean;
|
|
205
240
|
}) {
|
|
206
241
|
// TODO: pass vars via command line
|
|
207
|
-
const { bundle, format, bindings, port } = props;
|
|
242
|
+
const { bundle, format, bindings, port, assetPaths } = props;
|
|
208
243
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
209
244
|
const removeSignalExitListener = useRef<() => void>();
|
|
210
245
|
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
211
246
|
useEffect(() => {
|
|
212
247
|
async function startLocalWorker() {
|
|
213
|
-
if (!bundle) return;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
if (!bundle || !format) return;
|
|
249
|
+
|
|
250
|
+
await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
|
|
251
|
+
if (props.public) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
'⎔ A "public" folder is not yet supported in local mode.'
|
|
254
|
+
);
|
|
218
255
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
256
|
+
|
|
257
|
+
// In local mode, we want to copy all referenced modules into
|
|
258
|
+
// the output bundle directory before starting up
|
|
259
|
+
for (const module of bundle.modules) {
|
|
260
|
+
await writeFile(
|
|
261
|
+
path.join(path.dirname(bundle.path), module.name),
|
|
262
|
+
module.content
|
|
263
|
+
);
|
|
223
264
|
}
|
|
224
265
|
|
|
225
266
|
console.log("⎔ Starting a local server...");
|
|
226
267
|
// TODO: just use execa for this
|
|
227
|
-
local.current = spawn(
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
local.current = spawn(
|
|
269
|
+
"node",
|
|
270
|
+
[
|
|
271
|
+
"--experimental-vm-modules",
|
|
272
|
+
"--inspect",
|
|
273
|
+
require.resolve("miniflare/cli"),
|
|
274
|
+
bundle.path,
|
|
275
|
+
"--watch",
|
|
276
|
+
"--wrangler-config",
|
|
277
|
+
path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
|
|
278
|
+
"--env",
|
|
279
|
+
path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
|
|
280
|
+
"--package",
|
|
281
|
+
path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
|
|
282
|
+
"--port",
|
|
283
|
+
port.toString(),
|
|
284
|
+
...(assetPaths
|
|
285
|
+
? [
|
|
286
|
+
"--site",
|
|
287
|
+
path.join(process.cwd(), assetPaths.baseDirectory),
|
|
288
|
+
...assetPaths.includePatterns.map((pattern) => [
|
|
289
|
+
"--site-include",
|
|
290
|
+
pattern,
|
|
291
|
+
]),
|
|
292
|
+
...assetPaths.excludePatterns.map((pattern) => [
|
|
293
|
+
"--site-exclude",
|
|
294
|
+
pattern,
|
|
295
|
+
]),
|
|
296
|
+
].flatMap((x) => x)
|
|
297
|
+
: []),
|
|
298
|
+
...(props.enableLocalPersistence
|
|
299
|
+
? ["--kv-persist", "--cache-persist", "--do-persist"]
|
|
300
|
+
: []),
|
|
301
|
+
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
302
|
+
return ["--binding", `${key}=${value}`];
|
|
303
|
+
}),
|
|
304
|
+
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
305
|
+
return ["--kv", binding];
|
|
306
|
+
}),
|
|
307
|
+
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
308
|
+
({ name, class_name }) => {
|
|
309
|
+
return ["--do", `${name}=${class_name}`];
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
...Object.entries(bindings.wasm_modules || {}).flatMap(
|
|
313
|
+
([name, filePath]) => {
|
|
314
|
+
return [
|
|
315
|
+
"--wasm",
|
|
316
|
+
`${name}=${path.join(process.cwd(), filePath)}`,
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
...bundle.modules.reduce<string[]>((cmd, { name }) => {
|
|
321
|
+
if (format === "service-worker") {
|
|
322
|
+
if (name.endsWith(".wasm")) {
|
|
323
|
+
// In service-worker format, .wasm modules are referenced
|
|
324
|
+
// by global identifiers, so we convert it here.
|
|
325
|
+
// This identifier has to be a valid JS identifier,
|
|
326
|
+
// so we replace all non alphanumeric characters
|
|
327
|
+
// with an underscore.
|
|
328
|
+
const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
329
|
+
return cmd.concat([`--wasm`, `${identifier}=${name}`]);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return cmd;
|
|
333
|
+
}, []),
|
|
334
|
+
"--modules",
|
|
335
|
+
String(format === "modules"),
|
|
336
|
+
"--modules-rule",
|
|
337
|
+
"CompiledWasm=**/*.wasm",
|
|
338
|
+
],
|
|
339
|
+
{
|
|
340
|
+
cwd: path.dirname(bundle.path),
|
|
341
|
+
}
|
|
342
|
+
);
|
|
261
343
|
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
262
344
|
|
|
263
345
|
local.current.on("close", (code) => {
|
|
@@ -319,6 +401,10 @@ function useLocalWorker(props: {
|
|
|
319
401
|
bindings.durable_objects?.bindings,
|
|
320
402
|
bindings.kv_namespaces,
|
|
321
403
|
bindings.vars,
|
|
404
|
+
props.enableLocalPersistence,
|
|
405
|
+
assetPaths,
|
|
406
|
+
props.public,
|
|
407
|
+
bindings.wasm_modules,
|
|
322
408
|
]);
|
|
323
409
|
return { inspectorUrl };
|
|
324
410
|
}
|
|
@@ -346,7 +432,7 @@ function useTmpDir(): string | undefined {
|
|
|
346
432
|
return () => {
|
|
347
433
|
dir.cleanup().catch(() => {
|
|
348
434
|
// extremely unlikely,
|
|
349
|
-
// but it's
|
|
435
|
+
// but it's 2022 after all
|
|
350
436
|
console.error("failed to cleanup tmp dir");
|
|
351
437
|
});
|
|
352
438
|
};
|
|
@@ -355,36 +441,38 @@ function useTmpDir(): string | undefined {
|
|
|
355
441
|
}
|
|
356
442
|
|
|
357
443
|
function useCustomBuild(
|
|
358
|
-
expectedEntry:
|
|
444
|
+
expectedEntry: Entry,
|
|
359
445
|
props: {
|
|
360
446
|
command?: undefined | string;
|
|
361
447
|
cwd?: undefined | string;
|
|
362
448
|
watch_dir?: undefined | string;
|
|
363
449
|
}
|
|
364
|
-
): undefined |
|
|
365
|
-
const [entry, setEntry] = useState<
|
|
450
|
+
): undefined | Entry {
|
|
451
|
+
const [entry, setEntry] = useState<Entry | undefined>(
|
|
366
452
|
// if there's no build command, just return the expected entry
|
|
367
|
-
props.command
|
|
453
|
+
!props.command ? expectedEntry : undefined
|
|
368
454
|
);
|
|
369
455
|
const { command, cwd, watch_dir } = props;
|
|
370
456
|
useEffect(() => {
|
|
371
457
|
if (!command) return;
|
|
372
|
-
let cmd,
|
|
458
|
+
let cmd: ExecaChildProcess<string> | undefined,
|
|
459
|
+
interval: NodeJS.Timeout | undefined;
|
|
373
460
|
console.log("running:", command);
|
|
374
|
-
|
|
375
|
-
cmd = execa(commandPieces[0], commandPieces.slice(1), {
|
|
461
|
+
cmd = execaCommand(command, {
|
|
376
462
|
...(cwd && { cwd }),
|
|
463
|
+
shell: true,
|
|
377
464
|
stderr: "inherit",
|
|
378
465
|
stdout: "inherit",
|
|
379
466
|
});
|
|
380
467
|
if (watch_dir) {
|
|
381
468
|
watch(watch_dir, { persistent: true, ignoreInitial: true }).on(
|
|
382
469
|
"all",
|
|
383
|
-
(_event,
|
|
384
|
-
console.log(`The file ${
|
|
385
|
-
cmd
|
|
386
|
-
cmd =
|
|
470
|
+
(_event, filePath) => {
|
|
471
|
+
console.log(`The file ${filePath} changed, restarting build...`);
|
|
472
|
+
cmd?.kill();
|
|
473
|
+
cmd = execaCommand(command, {
|
|
387
474
|
...(cwd && { cwd }),
|
|
475
|
+
shell: true,
|
|
388
476
|
stderr: "inherit",
|
|
389
477
|
stdout: "inherit",
|
|
390
478
|
});
|
|
@@ -396,27 +484,39 @@ function useCustomBuild(
|
|
|
396
484
|
// if it does, we're done
|
|
397
485
|
const startedAt = Date.now();
|
|
398
486
|
interval = setInterval(() => {
|
|
399
|
-
|
|
400
|
-
|
|
487
|
+
let fileExists = false;
|
|
488
|
+
try {
|
|
489
|
+
// Use require.resolve to use node's resolution algorithm,
|
|
490
|
+
// this lets us use paths without explicit .js extension
|
|
491
|
+
// TODO: we should probably remove this, because it doesn't
|
|
492
|
+
// take into consideration other extensions like .tsx, .ts, .jsx, etc
|
|
493
|
+
fileExists = existsSync(require.resolve(expectedEntry.file));
|
|
494
|
+
} catch (e) {
|
|
495
|
+
// fail silently, usually means require.resolve threw MODULE_NOT_FOUND
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (fileExists === true) {
|
|
499
|
+
interval && clearInterval(interval);
|
|
401
500
|
setEntry(expectedEntry);
|
|
402
501
|
} else {
|
|
403
502
|
const elapsed = Date.now() - startedAt;
|
|
404
503
|
// timeout after 30 seconds of waiting
|
|
405
|
-
if (elapsed > 1000 *
|
|
406
|
-
console.error(
|
|
407
|
-
|
|
408
|
-
|
|
504
|
+
if (elapsed > 1000 * 30) {
|
|
505
|
+
console.error(
|
|
506
|
+
`⎔ Build timed out, Could not resolve ${expectedEntry.file}`
|
|
507
|
+
);
|
|
508
|
+
interval && clearInterval(interval);
|
|
509
|
+
cmd?.kill();
|
|
409
510
|
}
|
|
410
511
|
}
|
|
411
512
|
}, 200);
|
|
412
|
-
// TODO: we could probably timeout here after a while
|
|
413
513
|
|
|
414
514
|
return () => {
|
|
415
515
|
if (cmd) {
|
|
416
516
|
cmd.kill();
|
|
417
517
|
cmd = undefined;
|
|
418
518
|
}
|
|
419
|
-
clearInterval(interval);
|
|
519
|
+
interval && clearInterval(interval);
|
|
420
520
|
interval = undefined;
|
|
421
521
|
};
|
|
422
522
|
}, [command, cwd, expectedEntry, watch_dir]);
|
|
@@ -426,99 +526,100 @@ function useCustomBuild(
|
|
|
426
526
|
type EsbuildBundle = {
|
|
427
527
|
id: number;
|
|
428
528
|
path: string;
|
|
429
|
-
entry:
|
|
529
|
+
entry: Entry;
|
|
430
530
|
type: "esm" | "commonjs";
|
|
431
|
-
exports: string[];
|
|
432
531
|
modules: CfModule[];
|
|
532
|
+
serveAssetsFromWorker: boolean;
|
|
433
533
|
};
|
|
434
534
|
|
|
435
|
-
function useEsbuild(
|
|
436
|
-
entry
|
|
535
|
+
function useEsbuild({
|
|
536
|
+
entry,
|
|
537
|
+
destination,
|
|
538
|
+
staticRoot,
|
|
539
|
+
jsxFactory,
|
|
540
|
+
jsxFragment,
|
|
541
|
+
format,
|
|
542
|
+
serveAssetsFromWorker,
|
|
543
|
+
}: {
|
|
544
|
+
entry: undefined | Entry;
|
|
437
545
|
destination: string | undefined;
|
|
546
|
+
format: CfScriptFormat | undefined;
|
|
438
547
|
staticRoot: undefined | string;
|
|
439
548
|
jsxFactory: string | undefined;
|
|
440
549
|
jsxFragment: string | undefined;
|
|
550
|
+
serveAssetsFromWorker: boolean;
|
|
441
551
|
}): EsbuildBundle | undefined {
|
|
442
|
-
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
443
552
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
444
553
|
useEffect(() => {
|
|
445
|
-
let
|
|
554
|
+
let stopWatching: (() => void) | undefined = undefined;
|
|
555
|
+
|
|
556
|
+
const watchMode: WatchMode = {
|
|
557
|
+
async onRebuild(error) {
|
|
558
|
+
if (error) console.error("watch build failed:", error);
|
|
559
|
+
else {
|
|
560
|
+
// nothing really changes here, so let's increment the id
|
|
561
|
+
// to change the return object's identity
|
|
562
|
+
setBundle((previousBundle) => {
|
|
563
|
+
assert(
|
|
564
|
+
previousBundle,
|
|
565
|
+
"Rebuild triggered with no previous build available"
|
|
566
|
+
);
|
|
567
|
+
return { ...previousBundle, id: previousBundle.id + 1 };
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
446
573
|
async function build() {
|
|
447
|
-
if (!destination || !entry) return;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
...(jsxFragment && { jsxFragment }),
|
|
461
|
-
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
462
|
-
conditions: ["worker", "browser"],
|
|
463
|
-
plugins: [moduleCollector.plugin],
|
|
464
|
-
// TODO: import.meta.url
|
|
465
|
-
watch: {
|
|
466
|
-
async onRebuild(error) {
|
|
467
|
-
if (error) console.error("watch build failed:", error);
|
|
468
|
-
else {
|
|
469
|
-
// nothing really changes here, so let's increment the id
|
|
470
|
-
// to change the return object's identity
|
|
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
|
-
});
|
|
479
|
-
}
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
|
-
});
|
|
574
|
+
if (!destination || !entry || !format) return;
|
|
575
|
+
|
|
576
|
+
const { resolvedEntryPointPath, bundleType, modules, stop } =
|
|
577
|
+
await bundleWorker(
|
|
578
|
+
entry,
|
|
579
|
+
// In dev, we server assets from the local proxy before we send the request to the worker.
|
|
580
|
+
/* serveAssetsFromWorker */ false,
|
|
581
|
+
destination,
|
|
582
|
+
jsxFactory,
|
|
583
|
+
jsxFragment,
|
|
584
|
+
format,
|
|
585
|
+
watchMode
|
|
586
|
+
);
|
|
483
587
|
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
const metafile = result.metafile!;
|
|
487
|
-
const outputEntry = Object.entries(metafile.outputs).find(
|
|
488
|
-
([_path, { entryPoint }]) => entryPoint === entry
|
|
489
|
-
); // assumedly only one entry point
|
|
588
|
+
// Capture the `stop()` method to use as the `useEffect()` destructor.
|
|
589
|
+
stopWatching = stop;
|
|
490
590
|
|
|
491
|
-
if (outputEntry === undefined) {
|
|
492
|
-
throw new Error(
|
|
493
|
-
`Cannot find entry-point "${entry}" in generated bundle.`
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
591
|
setBundle({
|
|
497
592
|
id: 0,
|
|
498
593
|
entry,
|
|
499
|
-
path:
|
|
500
|
-
type:
|
|
501
|
-
|
|
502
|
-
|
|
594
|
+
path: resolvedEntryPointPath,
|
|
595
|
+
type: bundleType,
|
|
596
|
+
modules,
|
|
597
|
+
serveAssetsFromWorker,
|
|
503
598
|
});
|
|
504
599
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// and we don't want to end the process
|
|
508
|
-
// on build errors anyway
|
|
509
|
-
// so this is a no-op error handler
|
|
600
|
+
|
|
601
|
+
build().catch(() => {
|
|
602
|
+
// esbuild already logs errors to stderr and we don't want to end the process
|
|
603
|
+
// on build errors anyway so this is a no-op error handler
|
|
510
604
|
});
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
605
|
+
|
|
606
|
+
return stopWatching;
|
|
607
|
+
}, [
|
|
608
|
+
entry,
|
|
609
|
+
destination,
|
|
610
|
+
staticRoot,
|
|
611
|
+
jsxFactory,
|
|
612
|
+
jsxFragment,
|
|
613
|
+
format,
|
|
614
|
+
serveAssetsFromWorker,
|
|
615
|
+
]);
|
|
515
616
|
return bundle;
|
|
516
617
|
}
|
|
517
618
|
|
|
518
619
|
function useWorker(props: {
|
|
519
620
|
name: undefined | string;
|
|
520
621
|
bundle: EsbuildBundle | undefined;
|
|
521
|
-
format: CfScriptFormat;
|
|
622
|
+
format: CfScriptFormat | undefined;
|
|
522
623
|
modules: CfModule[];
|
|
523
624
|
accountId: string;
|
|
524
625
|
apiToken: string;
|
|
@@ -528,6 +629,7 @@ function useWorker(props: {
|
|
|
528
629
|
compatibilityDate: string | undefined;
|
|
529
630
|
compatibilityFlags: string[] | undefined;
|
|
530
631
|
usageModel: undefined | "bundled" | "unbound";
|
|
632
|
+
env: string | undefined;
|
|
531
633
|
}): CfPreviewToken | undefined {
|
|
532
634
|
const {
|
|
533
635
|
name,
|
|
@@ -554,17 +656,7 @@ function useWorker(props: {
|
|
|
554
656
|
async function start() {
|
|
555
657
|
setToken(undefined); // reset token in case we're re-running
|
|
556
658
|
|
|
557
|
-
if (!bundle) return;
|
|
558
|
-
if (format === "modules" && bundle.type === "commonjs") {
|
|
559
|
-
console.error("⎔ Cannot use modules with a commonjs bundle.");
|
|
560
|
-
// TODO: a much better error message here, with what to do next
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (format === "service-worker" && bundle.type !== "esm") {
|
|
564
|
-
console.error("⎔ Cannot use service-worker with a esm bundle.");
|
|
565
|
-
// TODO: a much better error message here, with what to do next
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
659
|
+
if (!bundle || !format) return;
|
|
568
660
|
|
|
569
661
|
if (!startedRef.current) {
|
|
570
662
|
startedRef.current = true;
|
|
@@ -574,21 +666,28 @@ function useWorker(props: {
|
|
|
574
666
|
|
|
575
667
|
const assets = await syncAssets(
|
|
576
668
|
accountId,
|
|
577
|
-
path.basename(bundle.path),
|
|
669
|
+
name || path.basename(bundle.path),
|
|
578
670
|
assetPaths,
|
|
579
|
-
true
|
|
671
|
+
true,
|
|
672
|
+
props.env
|
|
580
673
|
); // TODO: cancellable?
|
|
581
674
|
|
|
582
|
-
|
|
675
|
+
let content = await readFile(bundle.path, "utf-8");
|
|
676
|
+
if (format === "service-worker" && assets.manifest) {
|
|
677
|
+
content = `const __STATIC_CONTENT_MANIFEST = ${JSON.stringify(
|
|
678
|
+
assets.manifest
|
|
679
|
+
)};\n${content}`;
|
|
680
|
+
}
|
|
681
|
+
|
|
583
682
|
const init: CfWorkerInit = {
|
|
584
683
|
name,
|
|
585
684
|
main: {
|
|
586
685
|
name: path.basename(bundle.path),
|
|
587
|
-
type: format
|
|
686
|
+
type: format === "modules" ? "esm" : "commonjs",
|
|
588
687
|
content,
|
|
589
688
|
},
|
|
590
689
|
modules: modules.concat(
|
|
591
|
-
assets.manifest
|
|
690
|
+
assets.manifest && format === "modules"
|
|
592
691
|
? {
|
|
593
692
|
name: "__STATIC_CONTENT_MANIFEST",
|
|
594
693
|
content: JSON.stringify(assets.manifest),
|
|
@@ -634,6 +733,7 @@ function useWorker(props: {
|
|
|
634
733
|
usageModel,
|
|
635
734
|
bindings,
|
|
636
735
|
modules,
|
|
736
|
+
props.env,
|
|
637
737
|
]);
|
|
638
738
|
return token;
|
|
639
739
|
}
|
|
@@ -731,29 +831,36 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) {
|
|
|
731
831
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
732
832
|
key
|
|
733
833
|
) => {
|
|
734
|
-
switch (input) {
|
|
735
|
-
|
|
736
|
-
|
|
834
|
+
switch (input.toLowerCase()) {
|
|
835
|
+
// open browser
|
|
836
|
+
case "b": {
|
|
837
|
+
await openInBrowser(`http://localhost:${port}`);
|
|
737
838
|
break;
|
|
738
|
-
|
|
739
|
-
|
|
839
|
+
}
|
|
840
|
+
// toggle inspector
|
|
841
|
+
case "d": {
|
|
842
|
+
await openInBrowser(
|
|
740
843
|
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws`
|
|
741
844
|
);
|
|
742
845
|
break;
|
|
743
|
-
|
|
846
|
+
}
|
|
847
|
+
// toggle tunnel
|
|
848
|
+
case "s":
|
|
744
849
|
setToggles((previousToggles) => ({
|
|
745
850
|
...previousToggles,
|
|
746
851
|
tunnel: !previousToggles.tunnel,
|
|
747
852
|
}));
|
|
748
853
|
break;
|
|
749
|
-
|
|
854
|
+
// toggle local
|
|
855
|
+
case "l":
|
|
750
856
|
setToggles((previousToggles) => ({
|
|
751
857
|
...previousToggles,
|
|
752
858
|
local: !previousToggles.local,
|
|
753
859
|
}));
|
|
754
860
|
break;
|
|
755
|
-
|
|
756
|
-
case "
|
|
861
|
+
// shut down
|
|
862
|
+
case "q":
|
|
863
|
+
case "x":
|
|
757
864
|
process.exit(0);
|
|
758
865
|
break;
|
|
759
866
|
default:
|