wrangler 0.0.0-e2a26a0 → 0.0.0-e3cab74
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 +16 -7
- package/pages/functions/filepath-routing.test.ts +112 -36
- package/pages/functions/filepath-routing.ts +66 -119
- package/pages/functions/routes.ts +8 -17
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/configuration.test.ts +2185 -0
- package/src/__tests__/dev.test.tsx +48 -6
- package/src/__tests__/guess-worker-format.test.ts +66 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +6 -6
- package/src/__tests__/helpers/mock-bin.ts +4 -1
- package/src/__tests__/helpers/mock-cfetch.ts +2 -2
- package/src/__tests__/helpers/mock-console.ts +11 -5
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-wrangler.ts +5 -3
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/index.test.ts +343 -20
- package/src/__tests__/jest.setup.ts +21 -1
- package/src/__tests__/kv.test.ts +82 -80
- package/src/__tests__/package-manager.test.ts +2 -2
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +1428 -244
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/secret.test.ts +176 -60
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +564 -0
- package/src/api/form_data.ts +106 -23
- package/src/api/preview.ts +32 -13
- package/src/api/worker.ts +49 -18
- package/src/bundle.ts +133 -0
- package/src/cfetch/index.ts +8 -6
- package/src/cfetch/internal.ts +49 -17
- package/src/cli.ts +10 -0
- package/src/config/README.md +107 -0
- package/src/config/config.ts +265 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +305 -0
- package/src/config/index.ts +52 -0
- package/src/config/validation-helpers.ts +370 -0
- package/src/config/validation.ts +1017 -0
- package/src/dev.tsx +338 -211
- package/src/entry.ts +57 -0
- package/src/environment-variables.ts +33 -0
- package/src/guess-worker-format.ts +68 -0
- package/src/index.tsx +860 -634
- package/src/inspect.ts +16 -3
- package/src/kv.tsx +20 -29
- package/src/module-collection.ts +141 -34
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +21 -15
- package/src/pages.tsx +9 -10
- package/src/parse.ts +183 -0
- package/src/proxy.ts +11 -5
- package/src/publish.ts +272 -314
- package/src/r2.ts +50 -0
- package/src/reporting.ts +115 -0
- package/src/sites.tsx +16 -15
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +74 -44
- package/templates/new-worker.js +1 -1
- package/templates/new-worker.ts +1 -1
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +51308 -36440
- package/wrangler-dist/cli.js.map +3 -3
- package/src/config.ts +0 -641
- package/src/tail.tsx +0 -73
package/src/dev.tsx
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { watch } from "chokidar";
|
|
7
7
|
import clipboardy from "clipboardy";
|
|
8
8
|
import commandExists from "command-exists";
|
|
9
|
-
import * as esbuild from "esbuild";
|
|
10
9
|
import { execaCommand } from "execa";
|
|
11
10
|
import { Box, Text, useApp, useInput } from "ink";
|
|
12
|
-
import open from "open";
|
|
13
11
|
import React, { useState, useEffect, useRef } from "react";
|
|
14
12
|
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
15
13
|
import onExit from "signal-exit";
|
|
16
14
|
import tmp from "tmp-promise";
|
|
17
15
|
import { fetch } from "undici";
|
|
18
|
-
|
|
19
16
|
import { createWorker } from "./api/worker";
|
|
20
|
-
|
|
17
|
+
import { bundleWorker } from "./bundle";
|
|
18
|
+
import guessWorkerFormat from "./guess-worker-format";
|
|
21
19
|
import useInspector from "./inspect";
|
|
22
|
-
import
|
|
20
|
+
import { DEFAULT_MODULE_RULES } from "./module-collection";
|
|
21
|
+
import openInBrowser from "./open-in-browser";
|
|
23
22
|
import { usePreviewServer, waitForPortToBeAvailable } from "./proxy";
|
|
24
23
|
import { syncAssets } from "./sites";
|
|
25
24
|
import { getAPIToken } from "./user";
|
|
26
25
|
import type { CfPreviewToken } from "./api/preview";
|
|
27
|
-
import type { CfModule, CfWorkerInit } from "./api/worker";
|
|
26
|
+
import type { CfModule, CfWorkerInit, CfScriptFormat } from "./api/worker";
|
|
27
|
+
import type { Config } from "./config";
|
|
28
|
+
import type { Entry } from "./entry";
|
|
28
29
|
import type { AssetPaths } from "./sites";
|
|
30
|
+
import type { WatchMode } from "esbuild";
|
|
31
|
+
import type { ExecaChildProcess } from "execa";
|
|
29
32
|
import type { DirectoryResult } from "tmp-promise";
|
|
30
33
|
|
|
31
|
-
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
32
|
-
|
|
33
34
|
export type DevProps = {
|
|
34
35
|
name?: string;
|
|
35
|
-
entry:
|
|
36
|
+
entry: Entry;
|
|
36
37
|
port?: number;
|
|
37
|
-
|
|
38
|
+
inspectorPort: number;
|
|
39
|
+
format: CfScriptFormat | undefined;
|
|
40
|
+
rules: Config["rules"];
|
|
38
41
|
accountId: undefined | string;
|
|
39
42
|
initialMode: "local" | "remote";
|
|
40
43
|
jsxFactory: undefined | string;
|
|
@@ -52,16 +55,13 @@ export type DevProps = {
|
|
|
52
55
|
watch_dir?: undefined | string;
|
|
53
56
|
};
|
|
54
57
|
env: string | undefined;
|
|
58
|
+
legacyEnv: boolean;
|
|
59
|
+
zone: string | undefined;
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
function Dev(props: DevProps): JSX.Element {
|
|
58
|
-
if (props.public && props.format === "service-worker") {
|
|
59
|
-
throw new Error(
|
|
60
|
-
"You cannot use the service worker format with a `public` directory."
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
63
|
const port = props.port ?? 8787;
|
|
64
|
-
const apiToken = getAPIToken();
|
|
64
|
+
const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
|
|
65
65
|
const directory = useTmpDir();
|
|
66
66
|
|
|
67
67
|
// if there isn't a build command, we just return the entry immediately
|
|
@@ -69,57 +69,81 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
69
69
|
// kinda forbid that, so we thread the entry through useCustomBuild
|
|
70
70
|
const entry = useCustomBuild(props.entry, props.buildCommand);
|
|
71
71
|
|
|
72
|
+
const format = useWorkerFormat({ entry: props.entry, format: props.format });
|
|
73
|
+
if (format && props.public && format === "service-worker") {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"You cannot use the service worker format with a `public` directory."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
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
|
+
}
|
|
84
|
+
|
|
85
|
+
if (props.bindings.text_blobs && format === "modules") {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[build.upload.rules]` in your wrangler.toml"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
const bundle = useEsbuild({
|
|
73
92
|
entry,
|
|
93
|
+
format,
|
|
74
94
|
destination: directory,
|
|
75
95
|
staticRoot: props.public,
|
|
76
96
|
jsxFactory: props.jsxFactory,
|
|
97
|
+
rules: props.rules,
|
|
77
98
|
jsxFragment: props.jsxFragment,
|
|
99
|
+
serveAssetsFromWorker: !!props.public,
|
|
78
100
|
});
|
|
79
|
-
if (bundle && bundle.type === "commonjs" && !props.format && props.public) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"You cannot use the service worker format with a `public` directory."
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
101
|
|
|
85
102
|
const toggles = useHotkeys(
|
|
86
103
|
{
|
|
87
104
|
local: props.initialMode === "local",
|
|
88
105
|
tunnel: false,
|
|
89
106
|
},
|
|
90
|
-
port
|
|
107
|
+
port,
|
|
108
|
+
props.inspectorPort
|
|
91
109
|
);
|
|
92
110
|
|
|
93
111
|
useTunnel(toggles.tunnel);
|
|
112
|
+
const scriptName = props.name || path.basename(props.entry.file);
|
|
94
113
|
|
|
95
114
|
return (
|
|
96
115
|
<>
|
|
97
116
|
{toggles.local ? (
|
|
98
117
|
<Local
|
|
99
|
-
name={
|
|
118
|
+
name={scriptName}
|
|
100
119
|
bundle={bundle}
|
|
101
|
-
format={
|
|
120
|
+
format={format}
|
|
102
121
|
bindings={props.bindings}
|
|
103
122
|
assetPaths={props.assetPaths}
|
|
104
123
|
public={props.public}
|
|
105
124
|
port={port}
|
|
125
|
+
rules={props.rules}
|
|
126
|
+
inspectorPort={props.inspectorPort}
|
|
106
127
|
enableLocalPersistence={props.enableLocalPersistence}
|
|
107
128
|
/>
|
|
108
129
|
) : (
|
|
109
130
|
<Remote
|
|
110
131
|
name={props.name}
|
|
111
132
|
bundle={bundle}
|
|
112
|
-
format={
|
|
133
|
+
format={format}
|
|
113
134
|
accountId={props.accountId}
|
|
114
135
|
apiToken={apiToken}
|
|
115
136
|
bindings={props.bindings}
|
|
116
137
|
assetPaths={props.assetPaths}
|
|
117
138
|
public={props.public}
|
|
118
139
|
port={port}
|
|
140
|
+
inspectorPort={props.inspectorPort}
|
|
119
141
|
compatibilityDate={props.compatibilityDate}
|
|
120
142
|
compatibilityFlags={props.compatibilityFlags}
|
|
121
143
|
usageModel={props.usageModel}
|
|
122
144
|
env={props.env}
|
|
145
|
+
legacyEnv={props.legacyEnv}
|
|
146
|
+
zone={props.zone}
|
|
123
147
|
/>
|
|
124
148
|
)}
|
|
125
149
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
@@ -135,13 +159,33 @@ function Dev(props: DevProps): JSX.Element {
|
|
|
135
159
|
);
|
|
136
160
|
}
|
|
137
161
|
|
|
162
|
+
function useWorkerFormat(props: {
|
|
163
|
+
entry: Entry | undefined;
|
|
164
|
+
format: undefined | CfScriptFormat;
|
|
165
|
+
}): CfScriptFormat | undefined {
|
|
166
|
+
const [format, setFormat] = useState<CfScriptFormat | undefined>();
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
async function validateFormat() {
|
|
169
|
+
if (!props.entry || format) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
setFormat(await guessWorkerFormat(props.entry, props.format));
|
|
173
|
+
}
|
|
174
|
+
validateFormat().catch((err) => {
|
|
175
|
+
console.error("Failed to validate worker format:", err);
|
|
176
|
+
});
|
|
177
|
+
}, [props.entry, props.format, format]);
|
|
178
|
+
return format;
|
|
179
|
+
}
|
|
180
|
+
|
|
138
181
|
function Remote(props: {
|
|
139
182
|
name: undefined | string;
|
|
140
183
|
bundle: EsbuildBundle | undefined;
|
|
141
|
-
format: CfScriptFormat;
|
|
184
|
+
format: CfScriptFormat | undefined;
|
|
142
185
|
public: undefined | string;
|
|
143
186
|
assetPaths: undefined | AssetPaths;
|
|
144
187
|
port: number;
|
|
188
|
+
inspectorPort: number;
|
|
145
189
|
accountId: undefined | string;
|
|
146
190
|
apiToken: undefined | string;
|
|
147
191
|
bindings: CfWorkerInit["bindings"];
|
|
@@ -149,6 +193,8 @@ function Remote(props: {
|
|
|
149
193
|
compatibilityFlags: undefined | string[];
|
|
150
194
|
usageModel: undefined | "bundled" | "unbound";
|
|
151
195
|
env: string | undefined;
|
|
196
|
+
legacyEnv: boolean | undefined;
|
|
197
|
+
zone: string | undefined;
|
|
152
198
|
}) {
|
|
153
199
|
assert(props.accountId, "accountId is required");
|
|
154
200
|
assert(props.apiToken, "apiToken is required");
|
|
@@ -166,6 +212,8 @@ function Remote(props: {
|
|
|
166
212
|
compatibilityFlags: props.compatibilityFlags,
|
|
167
213
|
usageModel: props.usageModel,
|
|
168
214
|
env: props.env,
|
|
215
|
+
legacyEnv: props.legacyEnv,
|
|
216
|
+
zone: props.zone,
|
|
169
217
|
});
|
|
170
218
|
|
|
171
219
|
usePreviewServer({
|
|
@@ -176,7 +224,7 @@ function Remote(props: {
|
|
|
176
224
|
|
|
177
225
|
useInspector({
|
|
178
226
|
inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
|
|
179
|
-
port:
|
|
227
|
+
port: props.inspectorPort,
|
|
180
228
|
logToTerminal: true,
|
|
181
229
|
});
|
|
182
230
|
return null;
|
|
@@ -184,11 +232,13 @@ function Remote(props: {
|
|
|
184
232
|
function Local(props: {
|
|
185
233
|
name: undefined | string;
|
|
186
234
|
bundle: EsbuildBundle | undefined;
|
|
187
|
-
format: CfScriptFormat;
|
|
235
|
+
format: CfScriptFormat | undefined;
|
|
188
236
|
bindings: CfWorkerInit["bindings"];
|
|
189
237
|
assetPaths: undefined | AssetPaths;
|
|
190
238
|
public: undefined | string;
|
|
191
239
|
port: number;
|
|
240
|
+
rules: Config["rules"];
|
|
241
|
+
inspectorPort: number;
|
|
192
242
|
enableLocalPersistence: boolean;
|
|
193
243
|
}) {
|
|
194
244
|
const { inspectorUrl } = useLocalWorker({
|
|
@@ -199,98 +249,151 @@ function Local(props: {
|
|
|
199
249
|
assetPaths: props.assetPaths,
|
|
200
250
|
public: props.public,
|
|
201
251
|
port: props.port,
|
|
252
|
+
rules: props.rules,
|
|
253
|
+
inspectorPort: props.port,
|
|
202
254
|
enableLocalPersistence: props.enableLocalPersistence,
|
|
203
255
|
});
|
|
204
|
-
useInspector({
|
|
256
|
+
useInspector({
|
|
257
|
+
inspectorUrl,
|
|
258
|
+
port: props.inspectorPort,
|
|
259
|
+
logToTerminal: false,
|
|
260
|
+
});
|
|
205
261
|
return null;
|
|
206
262
|
}
|
|
207
263
|
|
|
208
264
|
function useLocalWorker(props: {
|
|
209
265
|
name: undefined | string;
|
|
210
266
|
bundle: EsbuildBundle | undefined;
|
|
211
|
-
|
|
267
|
+
rules: Config["rules"];
|
|
268
|
+
format: CfScriptFormat | undefined;
|
|
212
269
|
bindings: CfWorkerInit["bindings"];
|
|
213
270
|
assetPaths: undefined | AssetPaths;
|
|
214
271
|
public: undefined | string;
|
|
215
272
|
port: number;
|
|
273
|
+
inspectorPort: number;
|
|
216
274
|
enableLocalPersistence: boolean;
|
|
217
275
|
}) {
|
|
218
276
|
// TODO: pass vars via command line
|
|
219
|
-
const { bundle, format, bindings, port, assetPaths } = props;
|
|
277
|
+
const { bundle, format, bindings, port, assetPaths, inspectorPort } = props;
|
|
220
278
|
const local = useRef<ReturnType<typeof spawn>>();
|
|
221
279
|
const removeSignalExitListener = useRef<() => void>();
|
|
222
280
|
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
223
281
|
useEffect(() => {
|
|
224
282
|
async function startLocalWorker() {
|
|
225
|
-
if (!bundle) return;
|
|
226
|
-
if (format === "modules" && bundle.type === "commonjs") {
|
|
227
|
-
console.error("⎔ Cannot use modules with a commonjs bundle.");
|
|
228
|
-
// TODO: a much better error message here, with what to do next
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (format === "service-worker" && bundle.type !== "esm") {
|
|
232
|
-
console.error("⎔ Cannot use service-worker with a esm bundle.");
|
|
233
|
-
// TODO: a much better error message here, with what to do next
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
283
|
+
if (!bundle || !format) return;
|
|
236
284
|
|
|
285
|
+
// port for the worker
|
|
237
286
|
await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
|
|
287
|
+
// port for inspector
|
|
288
|
+
await waitForPortToBeAvailable(inspectorPort, {
|
|
289
|
+
retryPeriod: 200,
|
|
290
|
+
timeout: 2000,
|
|
291
|
+
});
|
|
292
|
+
|
|
238
293
|
if (props.public) {
|
|
239
294
|
throw new Error(
|
|
240
295
|
'⎔ A "public" folder is not yet supported in local mode.'
|
|
241
296
|
);
|
|
242
297
|
}
|
|
243
298
|
|
|
299
|
+
// In local mode, we want to copy all referenced modules into
|
|
300
|
+
// the output bundle directory before starting up
|
|
301
|
+
for (const module of bundle.modules) {
|
|
302
|
+
await writeFile(
|
|
303
|
+
path.join(path.dirname(bundle.path), module.name),
|
|
304
|
+
module.content
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
244
308
|
console.log("⎔ Starting a local server...");
|
|
245
309
|
// TODO: just use execa for this
|
|
246
|
-
local.current = spawn(
|
|
247
|
-
"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
pattern
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
pattern
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
310
|
+
local.current = spawn(
|
|
311
|
+
"node",
|
|
312
|
+
[
|
|
313
|
+
"--experimental-vm-modules",
|
|
314
|
+
"--inspect",
|
|
315
|
+
require.resolve("miniflare/cli"),
|
|
316
|
+
bundle.path,
|
|
317
|
+
"--watch",
|
|
318
|
+
"--wrangler-config",
|
|
319
|
+
path.join(__dirname, "../miniflare-config-stubs/wrangler.empty.toml"),
|
|
320
|
+
"--env",
|
|
321
|
+
path.join(__dirname, "../miniflare-config-stubs/.env.empty"),
|
|
322
|
+
"--package",
|
|
323
|
+
path.join(__dirname, "../miniflare-config-stubs/package.empty.json"),
|
|
324
|
+
"--port",
|
|
325
|
+
port.toString(),
|
|
326
|
+
...(assetPaths
|
|
327
|
+
? [
|
|
328
|
+
"--site",
|
|
329
|
+
path.join(process.cwd(), assetPaths.baseDirectory),
|
|
330
|
+
...assetPaths.includePatterns.map((pattern) => [
|
|
331
|
+
"--site-include",
|
|
332
|
+
pattern,
|
|
333
|
+
]),
|
|
334
|
+
...assetPaths.excludePatterns.map((pattern) => [
|
|
335
|
+
"--site-exclude",
|
|
336
|
+
pattern,
|
|
337
|
+
]),
|
|
338
|
+
].flatMap((x) => x)
|
|
339
|
+
: []),
|
|
340
|
+
...(props.enableLocalPersistence
|
|
341
|
+
? ["--kv-persist", "--cache-persist", "--do-persist"]
|
|
342
|
+
: []),
|
|
343
|
+
...Object.entries(bindings.vars || {}).flatMap(([key, value]) => {
|
|
344
|
+
return ["--binding", `${key}=${value}`];
|
|
345
|
+
}),
|
|
346
|
+
...(bindings.kv_namespaces || []).flatMap(({ binding }) => {
|
|
347
|
+
return ["--kv", binding];
|
|
348
|
+
}),
|
|
349
|
+
...(bindings.durable_objects?.bindings || []).flatMap(
|
|
350
|
+
({ name, class_name }) => {
|
|
351
|
+
return ["--do", `${name}=${class_name}`];
|
|
352
|
+
}
|
|
353
|
+
),
|
|
354
|
+
...Object.entries(bindings.wasm_modules || {}).flatMap(
|
|
355
|
+
([name, filePath]) => {
|
|
356
|
+
return [
|
|
357
|
+
"--wasm",
|
|
358
|
+
`${name}=${path.join(process.cwd(), filePath)}`,
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
),
|
|
362
|
+
...bundle.modules.reduce<string[]>((cmd, { name, type }) => {
|
|
363
|
+
if (format === "service-worker") {
|
|
364
|
+
if (type === "compiled-wasm") {
|
|
365
|
+
// In service-worker format, .wasm modules are referenced
|
|
366
|
+
// by global identifiers, so we convert it here.
|
|
367
|
+
// This identifier has to be a valid JS identifier,
|
|
368
|
+
// so we replace all non alphanumeric characters
|
|
369
|
+
// with an underscore.
|
|
370
|
+
const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
371
|
+
return cmd.concat([`--wasm`, `${identifier}=${name}`]);
|
|
372
|
+
} else {
|
|
373
|
+
// TODO: we should actually support this
|
|
374
|
+
throw new Error(
|
|
375
|
+
`⎔ Unsupported module type ${type} for file ${name} in service-worker format`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return cmd;
|
|
380
|
+
}, []),
|
|
381
|
+
"--modules",
|
|
382
|
+
String(format === "modules"),
|
|
383
|
+
...(props.rules || [])
|
|
384
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
385
|
+
.concat(DEFAULT_MODULE_RULES!)
|
|
386
|
+
.flatMap((rule) =>
|
|
387
|
+
rule.globs.flatMap((glob) => [
|
|
388
|
+
"--modules-rule",
|
|
389
|
+
`${rule.type}=${glob}`,
|
|
390
|
+
])
|
|
391
|
+
),
|
|
392
|
+
],
|
|
393
|
+
{
|
|
394
|
+
cwd: path.dirname(bundle.path),
|
|
395
|
+
}
|
|
396
|
+
);
|
|
294
397
|
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
295
398
|
|
|
296
399
|
local.current.on("close", (code) => {
|
|
@@ -306,7 +409,7 @@ function useLocalWorker(props: {
|
|
|
306
409
|
local.current.stderr?.on("data", (data: Buffer) => {
|
|
307
410
|
console.error(`${data.toString()}`);
|
|
308
411
|
const matches =
|
|
309
|
-
/Debugger listening on (ws:\/\/127\.0\.0\.1
|
|
412
|
+
/Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9-]+)/.exec(
|
|
310
413
|
data.toString()
|
|
311
414
|
);
|
|
312
415
|
if (matches) {
|
|
@@ -349,12 +452,15 @@ function useLocalWorker(props: {
|
|
|
349
452
|
bundle,
|
|
350
453
|
format,
|
|
351
454
|
port,
|
|
455
|
+
inspectorPort,
|
|
352
456
|
bindings.durable_objects?.bindings,
|
|
353
457
|
bindings.kv_namespaces,
|
|
354
458
|
bindings.vars,
|
|
355
459
|
props.enableLocalPersistence,
|
|
356
460
|
assetPaths,
|
|
357
461
|
props.public,
|
|
462
|
+
props.rules,
|
|
463
|
+
bindings.wasm_modules,
|
|
358
464
|
]);
|
|
359
465
|
return { inspectorUrl };
|
|
360
466
|
}
|
|
@@ -391,21 +497,22 @@ function useTmpDir(): string | undefined {
|
|
|
391
497
|
}
|
|
392
498
|
|
|
393
499
|
function useCustomBuild(
|
|
394
|
-
expectedEntry:
|
|
500
|
+
expectedEntry: Entry,
|
|
395
501
|
props: {
|
|
396
502
|
command?: undefined | string;
|
|
397
503
|
cwd?: undefined | string;
|
|
398
504
|
watch_dir?: undefined | string;
|
|
399
505
|
}
|
|
400
|
-
): undefined |
|
|
401
|
-
const [entry, setEntry] = useState<
|
|
506
|
+
): undefined | Entry {
|
|
507
|
+
const [entry, setEntry] = useState<Entry | undefined>(
|
|
402
508
|
// if there's no build command, just return the expected entry
|
|
403
|
-
props.command
|
|
509
|
+
!props.command ? expectedEntry : undefined
|
|
404
510
|
);
|
|
405
511
|
const { command, cwd, watch_dir } = props;
|
|
406
512
|
useEffect(() => {
|
|
407
513
|
if (!command) return;
|
|
408
|
-
let cmd,
|
|
514
|
+
let cmd: ExecaChildProcess<string> | undefined,
|
|
515
|
+
interval: NodeJS.Timeout | undefined;
|
|
409
516
|
console.log("running:", command);
|
|
410
517
|
cmd = execaCommand(command, {
|
|
411
518
|
...(cwd && { cwd }),
|
|
@@ -418,7 +525,7 @@ function useCustomBuild(
|
|
|
418
525
|
"all",
|
|
419
526
|
(_event, filePath) => {
|
|
420
527
|
console.log(`The file ${filePath} changed, restarting build...`);
|
|
421
|
-
cmd
|
|
528
|
+
cmd?.kill();
|
|
422
529
|
cmd = execaCommand(command, {
|
|
423
530
|
...(cwd && { cwd }),
|
|
424
531
|
shell: true,
|
|
@@ -433,27 +540,39 @@ function useCustomBuild(
|
|
|
433
540
|
// if it does, we're done
|
|
434
541
|
const startedAt = Date.now();
|
|
435
542
|
interval = setInterval(() => {
|
|
436
|
-
|
|
437
|
-
|
|
543
|
+
let fileExists = false;
|
|
544
|
+
try {
|
|
545
|
+
// Use require.resolve to use node's resolution algorithm,
|
|
546
|
+
// this lets us use paths without explicit .js extension
|
|
547
|
+
// TODO: we should probably remove this, because it doesn't
|
|
548
|
+
// take into consideration other extensions like .tsx, .ts, .jsx, etc
|
|
549
|
+
fileExists = existsSync(require.resolve(expectedEntry.file));
|
|
550
|
+
} catch (e) {
|
|
551
|
+
// fail silently, usually means require.resolve threw MODULE_NOT_FOUND
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (fileExists === true) {
|
|
555
|
+
interval && clearInterval(interval);
|
|
438
556
|
setEntry(expectedEntry);
|
|
439
557
|
} else {
|
|
440
558
|
const elapsed = Date.now() - startedAt;
|
|
441
559
|
// timeout after 30 seconds of waiting
|
|
442
|
-
if (elapsed > 1000 *
|
|
443
|
-
console.error(
|
|
444
|
-
|
|
445
|
-
|
|
560
|
+
if (elapsed > 1000 * 30) {
|
|
561
|
+
console.error(
|
|
562
|
+
`⎔ Build timed out, Could not resolve ${expectedEntry.file}`
|
|
563
|
+
);
|
|
564
|
+
interval && clearInterval(interval);
|
|
565
|
+
cmd?.kill();
|
|
446
566
|
}
|
|
447
567
|
}
|
|
448
568
|
}, 200);
|
|
449
|
-
// TODO: we could probably timeout here after a while
|
|
450
569
|
|
|
451
570
|
return () => {
|
|
452
571
|
if (cmd) {
|
|
453
572
|
cmd.kill();
|
|
454
573
|
cmd = undefined;
|
|
455
574
|
}
|
|
456
|
-
clearInterval(interval);
|
|
575
|
+
interval && clearInterval(interval);
|
|
457
576
|
interval = undefined;
|
|
458
577
|
};
|
|
459
578
|
}, [command, cwd, expectedEntry, watch_dir]);
|
|
@@ -463,102 +582,102 @@ function useCustomBuild(
|
|
|
463
582
|
type EsbuildBundle = {
|
|
464
583
|
id: number;
|
|
465
584
|
path: string;
|
|
466
|
-
entry:
|
|
585
|
+
entry: Entry;
|
|
467
586
|
type: "esm" | "commonjs";
|
|
468
|
-
exports: string[];
|
|
469
587
|
modules: CfModule[];
|
|
588
|
+
serveAssetsFromWorker: boolean;
|
|
470
589
|
};
|
|
471
590
|
|
|
472
|
-
function useEsbuild(
|
|
473
|
-
entry
|
|
591
|
+
function useEsbuild({
|
|
592
|
+
entry,
|
|
593
|
+
destination,
|
|
594
|
+
staticRoot,
|
|
595
|
+
jsxFactory,
|
|
596
|
+
jsxFragment,
|
|
597
|
+
format,
|
|
598
|
+
rules,
|
|
599
|
+
serveAssetsFromWorker,
|
|
600
|
+
}: {
|
|
601
|
+
entry: undefined | Entry;
|
|
474
602
|
destination: string | undefined;
|
|
603
|
+
format: CfScriptFormat | undefined;
|
|
475
604
|
staticRoot: undefined | string;
|
|
476
605
|
jsxFactory: string | undefined;
|
|
477
606
|
jsxFragment: string | undefined;
|
|
607
|
+
rules: Config["rules"];
|
|
608
|
+
serveAssetsFromWorker: boolean;
|
|
478
609
|
}): EsbuildBundle | undefined {
|
|
479
|
-
const { entry, destination, staticRoot, jsxFactory, jsxFragment } = props;
|
|
480
610
|
const [bundle, setBundle] = useState<EsbuildBundle>();
|
|
481
611
|
useEffect(() => {
|
|
482
|
-
let
|
|
612
|
+
let stopWatching: (() => void) | undefined = undefined;
|
|
613
|
+
|
|
614
|
+
const watchMode: WatchMode = {
|
|
615
|
+
async onRebuild(error) {
|
|
616
|
+
if (error) console.error("watch build failed:", error);
|
|
617
|
+
else {
|
|
618
|
+
// nothing really changes here, so let's increment the id
|
|
619
|
+
// to change the return object's identity
|
|
620
|
+
setBundle((previousBundle) => {
|
|
621
|
+
assert(
|
|
622
|
+
previousBundle,
|
|
623
|
+
"Rebuild triggered with no previous build available"
|
|
624
|
+
);
|
|
625
|
+
return { ...previousBundle, id: previousBundle.id + 1 };
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
|
|
483
631
|
async function build() {
|
|
484
|
-
if (!destination || !entry) return;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
".pem": "text",
|
|
497
|
-
".txt": "text",
|
|
498
|
-
},
|
|
499
|
-
...(jsxFactory && { jsxFactory }),
|
|
500
|
-
...(jsxFragment && { jsxFragment }),
|
|
501
|
-
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
502
|
-
conditions: ["worker", "browser"],
|
|
503
|
-
plugins: [moduleCollector.plugin],
|
|
504
|
-
// TODO: import.meta.url
|
|
505
|
-
watch: {
|
|
506
|
-
async onRebuild(error) {
|
|
507
|
-
if (error) console.error("watch build failed:", error);
|
|
508
|
-
else {
|
|
509
|
-
// nothing really changes here, so let's increment the id
|
|
510
|
-
// to change the return object's identity
|
|
511
|
-
setBundle((previousBundle) => {
|
|
512
|
-
if (previousBundle === undefined) {
|
|
513
|
-
assert.fail(
|
|
514
|
-
"Rebuild triggered with no previous build available"
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
return { ...previousBundle, id: previousBundle.id + 1 };
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
});
|
|
632
|
+
if (!destination || !entry || !format) return;
|
|
633
|
+
|
|
634
|
+
const { resolvedEntryPointPath, bundleType, modules, stop } =
|
|
635
|
+
await bundleWorker(entry, destination, {
|
|
636
|
+
// In dev, we serve assets from the local proxy before we send the request to the worker.
|
|
637
|
+
serveAssetsFromWorker: false,
|
|
638
|
+
jsxFactory,
|
|
639
|
+
jsxFragment,
|
|
640
|
+
format,
|
|
641
|
+
rules,
|
|
642
|
+
watch: watchMode,
|
|
643
|
+
});
|
|
523
644
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
const metafile = result.metafile!;
|
|
527
|
-
const outputEntry = Object.entries(metafile.outputs).find(
|
|
528
|
-
([_path, { entryPoint }]) => entryPoint === entry
|
|
529
|
-
); // assumedly only one entry point
|
|
645
|
+
// Capture the `stop()` method to use as the `useEffect()` destructor.
|
|
646
|
+
stopWatching = stop;
|
|
530
647
|
|
|
531
|
-
if (outputEntry === undefined) {
|
|
532
|
-
throw new Error(
|
|
533
|
-
`Cannot find entry-point "${entry}" in generated bundle.`
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
648
|
setBundle({
|
|
537
649
|
id: 0,
|
|
538
650
|
entry,
|
|
539
|
-
path:
|
|
540
|
-
type:
|
|
541
|
-
|
|
542
|
-
|
|
651
|
+
path: resolvedEntryPointPath,
|
|
652
|
+
type: bundleType,
|
|
653
|
+
modules,
|
|
654
|
+
serveAssetsFromWorker,
|
|
543
655
|
});
|
|
544
656
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// and we don't want to end the process
|
|
548
|
-
// on build errors anyway
|
|
549
|
-
// so this is a no-op error handler
|
|
657
|
+
|
|
658
|
+
build().catch(() => {
|
|
659
|
+
// esbuild already logs errors to stderr and we don't want to end the process
|
|
660
|
+
// on build errors anyway so this is a no-op error handler
|
|
550
661
|
});
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
662
|
+
|
|
663
|
+
return stopWatching;
|
|
664
|
+
}, [
|
|
665
|
+
entry,
|
|
666
|
+
destination,
|
|
667
|
+
staticRoot,
|
|
668
|
+
jsxFactory,
|
|
669
|
+
jsxFragment,
|
|
670
|
+
format,
|
|
671
|
+
serveAssetsFromWorker,
|
|
672
|
+
rules,
|
|
673
|
+
]);
|
|
555
674
|
return bundle;
|
|
556
675
|
}
|
|
557
676
|
|
|
558
677
|
function useWorker(props: {
|
|
559
678
|
name: undefined | string;
|
|
560
679
|
bundle: EsbuildBundle | undefined;
|
|
561
|
-
format: CfScriptFormat;
|
|
680
|
+
format: CfScriptFormat | undefined;
|
|
562
681
|
modules: CfModule[];
|
|
563
682
|
accountId: string;
|
|
564
683
|
apiToken: string;
|
|
@@ -569,6 +688,8 @@ function useWorker(props: {
|
|
|
569
688
|
compatibilityFlags: string[] | undefined;
|
|
570
689
|
usageModel: undefined | "bundled" | "unbound";
|
|
571
690
|
env: string | undefined;
|
|
691
|
+
legacyEnv: boolean | undefined;
|
|
692
|
+
zone: string | undefined;
|
|
572
693
|
}): CfPreviewToken | undefined {
|
|
573
694
|
const {
|
|
574
695
|
name,
|
|
@@ -595,17 +716,7 @@ function useWorker(props: {
|
|
|
595
716
|
async function start() {
|
|
596
717
|
setToken(undefined); // reset token in case we're re-running
|
|
597
718
|
|
|
598
|
-
if (!bundle) return;
|
|
599
|
-
if (format === "modules" && bundle.type === "commonjs") {
|
|
600
|
-
console.error("⎔ Cannot use modules with a commonjs bundle.");
|
|
601
|
-
// TODO: a much better error message here, with what to do next
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (format === "service-worker" && bundle.type !== "esm") {
|
|
605
|
-
console.error("⎔ Cannot use service-worker with a esm bundle.");
|
|
606
|
-
// TODO: a much better error message here, with what to do next
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
719
|
+
if (!bundle || !format) return;
|
|
609
720
|
|
|
610
721
|
if (!startedRef.current) {
|
|
611
722
|
startedRef.current = true;
|
|
@@ -615,29 +726,26 @@ function useWorker(props: {
|
|
|
615
726
|
|
|
616
727
|
const assets = await syncAssets(
|
|
617
728
|
accountId,
|
|
618
|
-
|
|
729
|
+
// When we're using the newer service environments, we wouldn't
|
|
730
|
+
// have added the env name on to the script name. However, we must
|
|
731
|
+
// include it in the kv namespace name regardless (since there's no
|
|
732
|
+
// concept of service environments for kv namespaces yet).
|
|
733
|
+
name + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
|
|
619
734
|
assetPaths,
|
|
620
|
-
true
|
|
621
|
-
props.env
|
|
735
|
+
true
|
|
622
736
|
); // TODO: cancellable?
|
|
623
737
|
|
|
624
|
-
const
|
|
625
|
-
let content = await readFile(bundle.path, "utf-8");
|
|
626
|
-
if (workerType !== "esm" && assets.manifest) {
|
|
627
|
-
content = `const __STATIC_CONTENT_MANIFEST = ${JSON.stringify(
|
|
628
|
-
assets.manifest
|
|
629
|
-
)};\n${content}`;
|
|
630
|
-
}
|
|
738
|
+
const content = await readFile(bundle.path, "utf-8");
|
|
631
739
|
|
|
632
740
|
const init: CfWorkerInit = {
|
|
633
741
|
name,
|
|
634
742
|
main: {
|
|
635
743
|
name: path.basename(bundle.path),
|
|
636
|
-
type:
|
|
744
|
+
type: format === "modules" ? "esm" : "commonjs",
|
|
637
745
|
content,
|
|
638
746
|
},
|
|
639
747
|
modules: modules.concat(
|
|
640
|
-
assets.manifest
|
|
748
|
+
assets.manifest
|
|
641
749
|
? {
|
|
642
750
|
name: "__STATIC_CONTENT_MANIFEST",
|
|
643
751
|
content: JSON.stringify(assets.manifest),
|
|
@@ -652,6 +760,13 @@ function useWorker(props: {
|
|
|
652
760
|
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
653
761
|
: []
|
|
654
762
|
),
|
|
763
|
+
text_blobs: {
|
|
764
|
+
...bindings.text_blobs,
|
|
765
|
+
...(assets.manifest &&
|
|
766
|
+
format === "service-worker" && {
|
|
767
|
+
__STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
|
|
768
|
+
}),
|
|
769
|
+
},
|
|
655
770
|
},
|
|
656
771
|
migrations: undefined, // no migrations in dev
|
|
657
772
|
compatibility_date: compatibilityDate,
|
|
@@ -659,10 +774,14 @@ function useWorker(props: {
|
|
|
659
774
|
usage_model: usageModel,
|
|
660
775
|
};
|
|
661
776
|
setToken(
|
|
662
|
-
await createWorker(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
777
|
+
await createWorker(
|
|
778
|
+
init,
|
|
779
|
+
{
|
|
780
|
+
accountId,
|
|
781
|
+
apiToken,
|
|
782
|
+
},
|
|
783
|
+
{ env: props.env, legacyEnv: props.legacyEnv, zone: props.zone }
|
|
784
|
+
)
|
|
666
785
|
);
|
|
667
786
|
}
|
|
668
787
|
start().catch((err) => {
|
|
@@ -684,6 +803,8 @@ function useWorker(props: {
|
|
|
684
803
|
bindings,
|
|
685
804
|
modules,
|
|
686
805
|
props.env,
|
|
806
|
+
props.legacyEnv,
|
|
807
|
+
props.zone,
|
|
687
808
|
]);
|
|
688
809
|
return token;
|
|
689
810
|
}
|
|
@@ -721,7 +842,7 @@ function useTunnel(toggle: boolean) {
|
|
|
721
842
|
await commandExists("cloudflared");
|
|
722
843
|
} catch (e) {
|
|
723
844
|
console.error(
|
|
724
|
-
"To share your worker on the
|
|
845
|
+
"To share your worker on the Internet, please install `cloudflared` from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation"
|
|
725
846
|
);
|
|
726
847
|
return;
|
|
727
848
|
}
|
|
@@ -772,26 +893,32 @@ type useHotkeysInitialState = {
|
|
|
772
893
|
local: boolean;
|
|
773
894
|
tunnel: boolean;
|
|
774
895
|
};
|
|
775
|
-
function useHotkeys(
|
|
896
|
+
function useHotkeys(
|
|
897
|
+
initial: useHotkeysInitialState,
|
|
898
|
+
port: number,
|
|
899
|
+
inspectorPort: number
|
|
900
|
+
) {
|
|
776
901
|
// UGH, we should put port in context instead
|
|
777
902
|
const [toggles, setToggles] = useState(initial);
|
|
778
903
|
useInput(
|
|
779
904
|
async (
|
|
780
905
|
input,
|
|
781
|
-
// eslint-disable-next-line
|
|
906
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
782
907
|
key
|
|
783
908
|
) => {
|
|
784
909
|
switch (input.toLowerCase()) {
|
|
785
910
|
// open browser
|
|
786
|
-
case "b":
|
|
787
|
-
await
|
|
911
|
+
case "b": {
|
|
912
|
+
await openInBrowser(`http://localhost:${port}`);
|
|
788
913
|
break;
|
|
914
|
+
}
|
|
789
915
|
// toggle inspector
|
|
790
|
-
case "d":
|
|
791
|
-
await
|
|
792
|
-
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost
|
|
916
|
+
case "d": {
|
|
917
|
+
await openInBrowser(
|
|
918
|
+
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`
|
|
793
919
|
);
|
|
794
920
|
break;
|
|
921
|
+
}
|
|
795
922
|
// toggle tunnel
|
|
796
923
|
case "s":
|
|
797
924
|
setToggles((previousToggles) => ({
|