wrangler 0.0.0-e3cab74 → 0.0.0-e3e3243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/wrangler.js +10 -1
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +10 -7
- package/pages/functions/filepath-routing.test.ts +125 -25
- package/pages/functions/filepath-routing.ts +6 -3
- package/src/__tests__/configuration.test.ts +959 -324
- package/src/__tests__/dev.test.tsx +512 -84
- package/src/__tests__/guess-worker-format.test.ts +19 -17
- package/src/__tests__/helpers/mock-account-id.ts +22 -4
- package/src/__tests__/helpers/mock-cfetch.ts +3 -3
- package/src/__tests__/helpers/mock-console.ts +17 -9
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/run-in-tmp.ts +3 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +128 -11
- package/src/__tests__/jest.setup.ts +16 -4
- package/src/__tests__/kv.test.ts +364 -125
- package/src/__tests__/logout.test.ts +15 -13
- package/src/__tests__/pages.test.ts +82 -0
- package/src/__tests__/publish.test.ts +1838 -735
- package/src/__tests__/r2.test.ts +4 -4
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +167 -68
- package/src/__tests__/tail.test.ts +2 -4
- package/src/__tests__/whoami.test.tsx +21 -7
- package/src/bundle.ts +27 -5
- package/src/cfetch/internal.ts +11 -5
- package/src/config/README.md +6 -6
- package/src/config/config.ts +54 -101
- package/src/config/environment.ts +67 -1
- package/src/config/index.ts +13 -3
- package/src/config/validation-helpers.ts +207 -23
- package/src/config/validation.ts +344 -129
- package/src/{api/preview.ts → create-worker-preview.ts} +39 -20
- package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
- package/src/dev/dev.tsx +392 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +104 -0
- package/src/entry.ts +192 -9
- package/src/https-options.ts +122 -0
- package/src/index.tsx +577 -515
- package/src/inspect.ts +9 -1
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +97 -53
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +90 -2
- package/src/open-in-browser.ts +14 -2
- package/src/pages.tsx +26 -10
- package/src/proxy.ts +104 -40
- package/src/publish.ts +53 -71
- package/src/reporting.ts +25 -9
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +68 -29
- package/src/user.tsx +163 -85
- package/src/{api/worker.ts → worker.ts} +1 -24
- package/wrangler-dist/cli.js +5933 -3842
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/dev.tsx +0 -961
- package/src/guess-worker-format.ts +0 -68
package/src/pages.tsx
CHANGED
|
@@ -22,11 +22,15 @@ import type { BuilderCallback } from "yargs";
|
|
|
22
22
|
// and also modifies some `stream/web` and `undici` prototypes, so we
|
|
23
23
|
// don't want to do this if pages commands aren't being called.
|
|
24
24
|
|
|
25
|
+
export const pagesBetaWarning =
|
|
26
|
+
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
|
|
27
|
+
|
|
25
28
|
const EXIT_CALLBACKS: (() => void)[] = [];
|
|
26
29
|
const EXIT = (message?: string, code?: number) => {
|
|
27
30
|
if (message) console.log(message);
|
|
28
31
|
if (code) process.exitCode = code;
|
|
29
32
|
EXIT_CALLBACKS.forEach((callback) => callback());
|
|
33
|
+
RUNNING_BUILDERS.forEach((builder) => builder.stop?.());
|
|
30
34
|
process.exit(code);
|
|
31
35
|
};
|
|
32
36
|
|
|
@@ -759,7 +763,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
759
763
|
description: "Auto reload HTML pages when change is detected",
|
|
760
764
|
},
|
|
761
765
|
// TODO: Miniflare user options
|
|
762
|
-
})
|
|
766
|
+
})
|
|
767
|
+
.epilogue(pagesBetaWarning);
|
|
763
768
|
},
|
|
764
769
|
async ({
|
|
765
770
|
local,
|
|
@@ -773,6 +778,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
773
778
|
"live-reload": liveReload,
|
|
774
779
|
_: [_pages, _dev, ...remaining],
|
|
775
780
|
}) => {
|
|
781
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
782
|
+
console.log(pagesBetaWarning);
|
|
783
|
+
|
|
776
784
|
if (!local) {
|
|
777
785
|
console.error("Only local mode is supported at the moment.");
|
|
778
786
|
return;
|
|
@@ -805,13 +813,15 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
805
813
|
|
|
806
814
|
console.log(`Compiling worker to "${scriptPath}"...`);
|
|
807
815
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
816
|
+
try {
|
|
817
|
+
await buildFunctions({
|
|
818
|
+
scriptPath,
|
|
819
|
+
functionsDirectory,
|
|
820
|
+
sourcemap: true,
|
|
821
|
+
watch: true,
|
|
822
|
+
onEnd: () => scriptReadyResolve(),
|
|
823
|
+
});
|
|
824
|
+
} catch {}
|
|
815
825
|
|
|
816
826
|
watch([functionsDirectory], {
|
|
817
827
|
persistent: true,
|
|
@@ -895,7 +905,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
895
905
|
// User bindings
|
|
896
906
|
bindings: {
|
|
897
907
|
...Object.fromEntries(
|
|
898
|
-
bindings
|
|
908
|
+
bindings
|
|
909
|
+
.map((binding) => binding.toString().split("="))
|
|
910
|
+
.map(([key, ...values]) => [key, values.join("=")])
|
|
899
911
|
),
|
|
900
912
|
},
|
|
901
913
|
|
|
@@ -1014,7 +1026,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1014
1026
|
description:
|
|
1015
1027
|
"Watch for changes to the functions and automatically rebuild the Worker script",
|
|
1016
1028
|
},
|
|
1017
|
-
})
|
|
1029
|
+
})
|
|
1030
|
+
.epilogue(pagesBetaWarning),
|
|
1018
1031
|
async ({
|
|
1019
1032
|
directory,
|
|
1020
1033
|
"script-path": scriptPath,
|
|
@@ -1024,6 +1037,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1024
1037
|
fallbackService,
|
|
1025
1038
|
watch,
|
|
1026
1039
|
}) => {
|
|
1040
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
1041
|
+
console.log(pagesBetaWarning);
|
|
1042
|
+
|
|
1027
1043
|
await buildFunctions({
|
|
1028
1044
|
scriptPath,
|
|
1029
1045
|
outputConfigPath,
|
package/src/proxy.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
1
|
+
import { createServer as createHttpServer } from "node:http";
|
|
2
2
|
import { connect } from "node:http2";
|
|
3
|
+
import { createServer as createHttpsServer } from "node:https";
|
|
3
4
|
import WebSocket from "faye-websocket";
|
|
4
5
|
import { useEffect, useRef, useState } from "react";
|
|
5
6
|
import serveStatic from "serve-static";
|
|
6
|
-
import
|
|
7
|
+
import { getHttpsOptions } from "./https-options";
|
|
8
|
+
import { reportError } from "./reporting";
|
|
9
|
+
import type { CfPreviewToken } from "./create-worker-preview";
|
|
7
10
|
import type {
|
|
8
11
|
IncomingHttpHeaders,
|
|
9
12
|
RequestListener,
|
|
10
13
|
IncomingMessage,
|
|
11
14
|
ServerResponse,
|
|
12
|
-
Server,
|
|
15
|
+
Server as HttpServer,
|
|
13
16
|
} from "node:http";
|
|
14
17
|
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
18
|
+
import type { Server as HttpsServer } from "node:https";
|
|
15
19
|
import type ws from "ws";
|
|
16
20
|
|
|
17
21
|
interface IWebsocket extends ws {
|
|
@@ -47,13 +51,17 @@ function addCfPreviewTokenHeader(
|
|
|
47
51
|
function rewriteRemoteHostToLocalHostInHeaders(
|
|
48
52
|
headers: IncomingHttpHeaders,
|
|
49
53
|
remoteHost: string,
|
|
50
|
-
localPort: number
|
|
54
|
+
localPort: number,
|
|
55
|
+
localProtocol: "https" | "http"
|
|
51
56
|
) {
|
|
52
57
|
for (const [name, value] of Object.entries(headers)) {
|
|
53
58
|
// Rewrite the remote host to the local host.
|
|
54
59
|
if (typeof value === "string" && value.includes(remoteHost)) {
|
|
55
60
|
headers[name] = value
|
|
56
|
-
.replaceAll(
|
|
61
|
+
.replaceAll(
|
|
62
|
+
`https://${remoteHost}`,
|
|
63
|
+
`${localProtocol}://localhost:${localPort}`
|
|
64
|
+
)
|
|
57
65
|
.replaceAll(remoteHost, `localhost:${localPort}`);
|
|
58
66
|
}
|
|
59
67
|
}
|
|
@@ -62,15 +70,33 @@ function rewriteRemoteHostToLocalHostInHeaders(
|
|
|
62
70
|
export function usePreviewServer({
|
|
63
71
|
previewToken,
|
|
64
72
|
publicRoot,
|
|
65
|
-
|
|
73
|
+
localProtocol,
|
|
74
|
+
localPort: port,
|
|
75
|
+
ip,
|
|
66
76
|
}: {
|
|
67
77
|
previewToken: CfPreviewToken | undefined;
|
|
68
78
|
publicRoot: undefined | string;
|
|
69
|
-
|
|
79
|
+
localProtocol: "https" | "http";
|
|
80
|
+
localPort: number;
|
|
81
|
+
ip: string;
|
|
70
82
|
}) {
|
|
71
83
|
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
72
|
-
const proxyServer =
|
|
73
|
-
|
|
84
|
+
const [proxyServer, setProxyServer] = useState<HttpServer | HttpsServer>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create the instance of the local proxy server that will pass on
|
|
88
|
+
* requests to the preview worker.
|
|
89
|
+
*/
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (proxyServer === undefined) {
|
|
92
|
+
createProxyServer(localProtocol)
|
|
93
|
+
.then((proxy) => setProxyServer(proxy))
|
|
94
|
+
.catch(async (err) => {
|
|
95
|
+
console.error("Failed to create proxy server.", err);
|
|
96
|
+
await reportError(err);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}, [proxyServer, localProtocol]);
|
|
74
100
|
|
|
75
101
|
/**
|
|
76
102
|
* When we're not connected / getting a fresh token on changes,
|
|
@@ -98,6 +124,10 @@ export function usePreviewServer({
|
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
useEffect(() => {
|
|
127
|
+
if (proxyServer === undefined) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
// If we don't have a token, that means either we're just starting up,
|
|
102
132
|
// or we're refreshing the token.
|
|
103
133
|
if (!previewToken) {
|
|
@@ -109,8 +139,8 @@ export function usePreviewServer({
|
|
|
109
139
|
// store the stream in a buffer so we can replay it later
|
|
110
140
|
streamBufferRef.current.push({ stream, headers });
|
|
111
141
|
};
|
|
112
|
-
|
|
113
|
-
cleanupListeners.push(() =>
|
|
142
|
+
proxyServer.on("stream", bufferStream);
|
|
143
|
+
cleanupListeners.push(() => proxyServer.off("stream", bufferStream));
|
|
114
144
|
|
|
115
145
|
const bufferRequestResponse = (
|
|
116
146
|
request: IncomingMessage,
|
|
@@ -120,8 +150,10 @@ export function usePreviewServer({
|
|
|
120
150
|
requestResponseBufferRef.current.push({ request, response });
|
|
121
151
|
};
|
|
122
152
|
|
|
123
|
-
|
|
124
|
-
cleanupListeners.push(() =>
|
|
153
|
+
proxyServer.on("request", bufferRequestResponse);
|
|
154
|
+
cleanupListeners.push(() =>
|
|
155
|
+
proxyServer.off("request", bufferRequestResponse)
|
|
156
|
+
);
|
|
125
157
|
return () => {
|
|
126
158
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
127
159
|
};
|
|
@@ -142,9 +174,14 @@ export function usePreviewServer({
|
|
|
142
174
|
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
143
175
|
|
|
144
176
|
/** HTTP/2 -> HTTP/2 */
|
|
145
|
-
const handleStream = createStreamHandler(
|
|
146
|
-
|
|
147
|
-
|
|
177
|
+
const handleStream = createStreamHandler(
|
|
178
|
+
previewToken,
|
|
179
|
+
remote,
|
|
180
|
+
port,
|
|
181
|
+
localProtocol
|
|
182
|
+
);
|
|
183
|
+
proxyServer.on("stream", handleStream);
|
|
184
|
+
cleanupListeners.push(() => proxyServer.off("stream", handleStream));
|
|
148
185
|
|
|
149
186
|
// flush and replay buffered streams
|
|
150
187
|
streamBufferRef.current.forEach((buffer) =>
|
|
@@ -177,7 +214,8 @@ export function usePreviewServer({
|
|
|
177
214
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
178
215
|
responseHeaders,
|
|
179
216
|
previewToken.host,
|
|
180
|
-
port
|
|
217
|
+
port,
|
|
218
|
+
localProtocol
|
|
181
219
|
);
|
|
182
220
|
for (const name of Object.keys(responseHeaders)) {
|
|
183
221
|
if (name.startsWith(":")) {
|
|
@@ -195,8 +233,10 @@ export function usePreviewServer({
|
|
|
195
233
|
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
196
234
|
: handleRequest;
|
|
197
235
|
|
|
198
|
-
|
|
199
|
-
cleanupListeners.push(() =>
|
|
236
|
+
proxyServer.on("request", actualHandleRequest);
|
|
237
|
+
cleanupListeners.push(() =>
|
|
238
|
+
proxyServer.off("request", actualHandleRequest)
|
|
239
|
+
);
|
|
200
240
|
|
|
201
241
|
// flush and replay buffered requests
|
|
202
242
|
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
@@ -227,8 +267,8 @@ export function usePreviewServer({
|
|
|
227
267
|
remoteWebsocketClient.close();
|
|
228
268
|
});
|
|
229
269
|
};
|
|
230
|
-
|
|
231
|
-
cleanupListeners.push(() =>
|
|
270
|
+
proxyServer.on("upgrade", handleUpgrade);
|
|
271
|
+
cleanupListeners.push(() => proxyServer.off("upgrade", handleUpgrade));
|
|
232
272
|
|
|
233
273
|
return () => {
|
|
234
274
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
@@ -237,7 +277,8 @@ export function usePreviewServer({
|
|
|
237
277
|
previewToken,
|
|
238
278
|
publicRoot,
|
|
239
279
|
port,
|
|
240
|
-
|
|
280
|
+
localProtocol,
|
|
281
|
+
proxyServer,
|
|
241
282
|
// We use a state value as a sigil to trigger reconnecting the server.
|
|
242
283
|
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
243
284
|
// doesn't complain if it's not included in the dependency array.
|
|
@@ -248,19 +289,23 @@ export function usePreviewServer({
|
|
|
248
289
|
// Start/stop the server whenever the
|
|
249
290
|
// containing component is mounted/unmounted.
|
|
250
291
|
useEffect(() => {
|
|
292
|
+
if (proxyServer === undefined) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
251
296
|
waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
|
|
252
297
|
.then(() => {
|
|
253
|
-
|
|
254
|
-
console.log(`⬣ Listening at
|
|
298
|
+
proxyServer.listen(port, ip);
|
|
299
|
+
console.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
|
|
255
300
|
})
|
|
256
301
|
.catch((err) => {
|
|
257
302
|
console.error(`⬣ Failed to start server: ${err}`);
|
|
258
303
|
});
|
|
259
304
|
|
|
260
305
|
return () => {
|
|
261
|
-
|
|
306
|
+
proxyServer.close();
|
|
262
307
|
};
|
|
263
|
-
}, [port,
|
|
308
|
+
}, [port, ip, proxyServer, localProtocol]);
|
|
264
309
|
}
|
|
265
310
|
|
|
266
311
|
function createHandleAssetsRequest(
|
|
@@ -288,8 +333,15 @@ const HTTP1_HEADERS = new Set([
|
|
|
288
333
|
"http2-settings",
|
|
289
334
|
]);
|
|
290
335
|
|
|
291
|
-
function createProxyServer(
|
|
292
|
-
|
|
336
|
+
async function createProxyServer(
|
|
337
|
+
localProtocol: "https" | "http"
|
|
338
|
+
): Promise<HttpServer | HttpsServer> {
|
|
339
|
+
const server: HttpServer | HttpsServer =
|
|
340
|
+
localProtocol === "https"
|
|
341
|
+
? createHttpsServer(await getHttpsOptions())
|
|
342
|
+
: createHttpServer();
|
|
343
|
+
|
|
344
|
+
return server
|
|
293
345
|
.on("request", function (req, res) {
|
|
294
346
|
// log all requests
|
|
295
347
|
console.log(
|
|
@@ -318,7 +370,8 @@ function createProxyServer() {
|
|
|
318
370
|
function createStreamHandler(
|
|
319
371
|
previewToken: CfPreviewToken,
|
|
320
372
|
remote: ClientHttp2Session,
|
|
321
|
-
|
|
373
|
+
localPort: number,
|
|
374
|
+
localProtocol: "https" | "http"
|
|
322
375
|
) {
|
|
323
376
|
return function handleStream(
|
|
324
377
|
stream: ServerHttp2Stream,
|
|
@@ -331,7 +384,8 @@ function createStreamHandler(
|
|
|
331
384
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
332
385
|
responseHeaders,
|
|
333
386
|
previewToken.host,
|
|
334
|
-
|
|
387
|
+
localPort,
|
|
388
|
+
localProtocol
|
|
335
389
|
);
|
|
336
390
|
stream.respond(responseHeaders);
|
|
337
391
|
request.pipe(stream, { end: true });
|
|
@@ -348,29 +402,39 @@ export async function waitForPortToBeAvailable(
|
|
|
348
402
|
): Promise<void> {
|
|
349
403
|
return new Promise((resolve, reject) => {
|
|
350
404
|
const timeout = setTimeout(() => {
|
|
351
|
-
|
|
405
|
+
doReject(new Error(`Timed out waiting for port ${port}`));
|
|
352
406
|
}, options.timeout);
|
|
353
407
|
|
|
408
|
+
const interval = setInterval(checkPort, options.retryPeriod);
|
|
409
|
+
checkPort();
|
|
410
|
+
|
|
411
|
+
function doResolve() {
|
|
412
|
+
clearTimeout(timeout);
|
|
413
|
+
clearInterval(interval);
|
|
414
|
+
resolve();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function doReject(err: unknown) {
|
|
418
|
+
clearInterval(interval);
|
|
419
|
+
clearTimeout(timeout);
|
|
420
|
+
reject(err);
|
|
421
|
+
}
|
|
422
|
+
|
|
354
423
|
function checkPort() {
|
|
355
424
|
// Testing whether a port is 'available' involves simply
|
|
356
425
|
// trying to make a server listen on that port, and retrying
|
|
357
426
|
// until it succeeds.
|
|
358
|
-
const server =
|
|
427
|
+
const server = createHttpServer();
|
|
359
428
|
server.on("error", (err) => {
|
|
360
429
|
// @ts-expect-error non standard property on Error
|
|
361
|
-
if (err.code
|
|
362
|
-
|
|
363
|
-
} else {
|
|
364
|
-
reject(err);
|
|
430
|
+
if (err.code !== "EADDRINUSE") {
|
|
431
|
+
doReject(err);
|
|
365
432
|
}
|
|
366
433
|
});
|
|
367
434
|
server.listen(port, () => {
|
|
368
435
|
server.close();
|
|
369
|
-
|
|
370
|
-
resolve();
|
|
436
|
+
doResolve();
|
|
371
437
|
});
|
|
372
438
|
}
|
|
373
|
-
|
|
374
|
-
checkPort();
|
|
375
439
|
});
|
|
376
440
|
}
|
package/src/publish.ts
CHANGED
|
@@ -2,22 +2,20 @@ import assert from "node:assert";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { URLSearchParams } from "node:url";
|
|
5
|
-
import { execaCommand } from "execa";
|
|
6
5
|
import tmp from "tmp-promise";
|
|
7
|
-
import { toFormData } from "./api/form_data";
|
|
8
6
|
import { bundleWorker } from "./bundle";
|
|
9
7
|
import { fetchResult } from "./cfetch";
|
|
10
|
-
import {
|
|
11
|
-
import guessWorkerFormat from "./guess-worker-format";
|
|
8
|
+
import { createWorkerUploadForm } from "./create-worker-upload-form";
|
|
12
9
|
import { syncAssets } from "./sites";
|
|
13
|
-
import type { CfScriptFormat, CfWorkerInit } from "./api/worker";
|
|
14
10
|
import type { Config } from "./config";
|
|
11
|
+
import type { Entry } from "./entry";
|
|
15
12
|
import type { AssetPaths } from "./sites";
|
|
13
|
+
import type { CfWorkerInit } from "./worker";
|
|
16
14
|
|
|
17
15
|
type Props = {
|
|
18
16
|
config: Config;
|
|
19
|
-
|
|
20
|
-
entry:
|
|
17
|
+
accountId: string;
|
|
18
|
+
entry: Entry;
|
|
21
19
|
rules: Config["rules"];
|
|
22
20
|
name: string | undefined;
|
|
23
21
|
env: string | undefined;
|
|
@@ -29,6 +27,7 @@ type Props = {
|
|
|
29
27
|
legacyEnv: boolean | undefined;
|
|
30
28
|
jsxFactory: undefined | string;
|
|
31
29
|
jsxFragment: undefined | string;
|
|
30
|
+
tsconfig: undefined | string;
|
|
32
31
|
experimentalPublic: boolean;
|
|
33
32
|
};
|
|
34
33
|
|
|
@@ -38,33 +37,22 @@ function sleep(ms: number) {
|
|
|
38
37
|
|
|
39
38
|
export default async function publish(props: Props): Promise<void> {
|
|
40
39
|
// TODO: warn if git/hg has uncommitted changes
|
|
41
|
-
const { config } = props;
|
|
42
|
-
|
|
43
|
-
// TODO: should we automatically fallback to top level config if there is no matching environment??
|
|
44
|
-
const envRootObj = (props.env && config.env[props.env]) || config;
|
|
40
|
+
const { config, accountId } = props;
|
|
45
41
|
|
|
46
42
|
assert(
|
|
47
|
-
|
|
43
|
+
props.compatibilityDate || config.compatibility_date,
|
|
48
44
|
"A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
|
|
49
45
|
);
|
|
50
46
|
|
|
51
|
-
const triggers = props.triggers ||
|
|
47
|
+
const triggers = props.triggers || config.triggers?.crons;
|
|
52
48
|
const routes =
|
|
53
|
-
props.routes ??
|
|
54
|
-
envRootObj.routes ??
|
|
55
|
-
(envRootObj.route ? [envRootObj.route] : []) ??
|
|
56
|
-
[];
|
|
57
|
-
|
|
58
|
-
const { account_id: accountId, workers_dev: deployToWorkersDev } = config;
|
|
59
|
-
|
|
60
|
-
if (accountId === undefined) {
|
|
61
|
-
throw new Error("No account_id provided.");
|
|
62
|
-
}
|
|
49
|
+
props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
const
|
|
51
|
+
// deployToWorkersDev defaults to true only if there aren't any routes defined
|
|
52
|
+
const deployToWorkersDev = config.workers_dev ?? routes.length === 0;
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
const jsxFactory = props.jsxFactory || config.jsx_factory;
|
|
55
|
+
const jsxFragment = props.jsxFragment || config.jsx_fragment;
|
|
68
56
|
|
|
69
57
|
const scriptName = props.name;
|
|
70
58
|
assert(
|
|
@@ -72,14 +60,6 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
72
60
|
'You need to provide a name when publishing a worker. Either pass it as a cli arg with `--name <name>` or in your config file as `name = "<name>"`'
|
|
73
61
|
);
|
|
74
62
|
|
|
75
|
-
if (config.site?.["entry-point"]) {
|
|
76
|
-
console.warn(
|
|
77
|
-
"Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
|
|
78
|
-
"The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `main` config field.\n" +
|
|
79
|
-
"Please remove the `site.entry-point` field from the `wrangler.toml` file."
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
63
|
assert(
|
|
84
64
|
!config.site || config.site.bucket,
|
|
85
65
|
"A [site] definition requires a `bucket` field with a path to the site's public directory."
|
|
@@ -89,28 +69,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
89
69
|
try {
|
|
90
70
|
const envName = props.env ?? "production";
|
|
91
71
|
|
|
92
|
-
|
|
93
|
-
// TODO: add a deprecation message here?
|
|
94
|
-
console.log("running:", config.build.command);
|
|
95
|
-
await execaCommand(config.build.command, {
|
|
96
|
-
shell: true,
|
|
97
|
-
stdout: "inherit",
|
|
98
|
-
stderr: "inherit",
|
|
99
|
-
timeout: 1000 * 30,
|
|
100
|
-
...(config.build.cwd && { cwd: config.build.cwd }),
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (fileExists(props.entry.file) === false) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Could not resolve "${path.relative(
|
|
106
|
-
process.cwd(),
|
|
107
|
-
props.entry.file
|
|
108
|
-
)}".`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const format = await guessWorkerFormat(props.entry, props.format);
|
|
72
|
+
const { format } = props.entry;
|
|
114
73
|
|
|
115
74
|
if (props.experimentalPublic && format === "service-worker") {
|
|
116
75
|
// TODO: check config too
|
|
@@ -138,8 +97,8 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
138
97
|
serveAssetsFromWorker: props.experimentalPublic,
|
|
139
98
|
jsxFactory,
|
|
140
99
|
jsxFragment,
|
|
141
|
-
format,
|
|
142
100
|
rules: props.rules,
|
|
101
|
+
tsconfig: props.tsconfig ?? config.tsconfig,
|
|
143
102
|
}
|
|
144
103
|
);
|
|
145
104
|
|
|
@@ -198,12 +157,12 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
198
157
|
);
|
|
199
158
|
|
|
200
159
|
const bindings: CfWorkerInit["bindings"] = {
|
|
201
|
-
kv_namespaces: (
|
|
160
|
+
kv_namespaces: (config.kv_namespaces || []).concat(
|
|
202
161
|
assets.namespace
|
|
203
162
|
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
204
163
|
: []
|
|
205
164
|
),
|
|
206
|
-
vars:
|
|
165
|
+
vars: config.vars,
|
|
207
166
|
wasm_modules: config.wasm_modules,
|
|
208
167
|
text_blobs: {
|
|
209
168
|
...config.text_blobs,
|
|
@@ -212,9 +171,9 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
212
171
|
__STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
|
|
213
172
|
}),
|
|
214
173
|
},
|
|
215
|
-
durable_objects:
|
|
216
|
-
r2_buckets:
|
|
217
|
-
unsafe:
|
|
174
|
+
durable_objects: config.durable_objects,
|
|
175
|
+
r2_buckets: config.r2_buckets,
|
|
176
|
+
unsafe: config.unsafe?.bindings,
|
|
218
177
|
};
|
|
219
178
|
|
|
220
179
|
if (assets.manifest) {
|
|
@@ -235,8 +194,9 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
235
194
|
bindings,
|
|
236
195
|
migrations,
|
|
237
196
|
modules,
|
|
238
|
-
compatibility_date: config.compatibility_date,
|
|
239
|
-
compatibility_flags:
|
|
197
|
+
compatibility_date: props.compatibilityDate ?? config.compatibility_date,
|
|
198
|
+
compatibility_flags:
|
|
199
|
+
props.compatibilityFlags ?? config.compatibility_flags,
|
|
240
200
|
usage_model: config.usage_model,
|
|
241
201
|
};
|
|
242
202
|
|
|
@@ -252,22 +212,17 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
252
212
|
workerUrl,
|
|
253
213
|
{
|
|
254
214
|
method: "PUT",
|
|
255
|
-
body:
|
|
215
|
+
body: createWorkerUploadForm(worker),
|
|
256
216
|
},
|
|
257
217
|
new URLSearchParams({ available_on_subdomain: "true" })
|
|
258
218
|
);
|
|
259
219
|
|
|
260
220
|
const uploadMs = Date.now() - start;
|
|
261
|
-
console.log("Uploaded", workerName, formatTime(uploadMs));
|
|
262
221
|
const deployments: Promise<string[]>[] = [];
|
|
263
222
|
|
|
264
223
|
if (deployToWorkersDev) {
|
|
265
224
|
// Deploy to a subdomain of `workers.dev`
|
|
266
|
-
const userSubdomain = (
|
|
267
|
-
await fetchResult<{ subdomain: string }>(
|
|
268
|
-
`/accounts/${accountId}/workers/subdomain`
|
|
269
|
-
)
|
|
270
|
-
).subdomain;
|
|
225
|
+
const userSubdomain = await getSubdomain(accountId);
|
|
271
226
|
const scriptURL =
|
|
272
227
|
props.legacyEnv || !props.env
|
|
273
228
|
? `${scriptName}.${userSubdomain}.workers.dev`
|
|
@@ -306,6 +261,8 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
306
261
|
});
|
|
307
262
|
}
|
|
308
263
|
|
|
264
|
+
console.log("Uploaded", workerName, formatTime(uploadMs));
|
|
265
|
+
|
|
309
266
|
// Update routing table for the script.
|
|
310
267
|
if (routes.length > 0) {
|
|
311
268
|
deployments.push(
|
|
@@ -366,3 +323,28 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
366
323
|
function formatTime(duration: number) {
|
|
367
324
|
return `(${(duration / 1000).toFixed(2)} sec)`;
|
|
368
325
|
}
|
|
326
|
+
|
|
327
|
+
async function getSubdomain(accountId: string): Promise<string> {
|
|
328
|
+
try {
|
|
329
|
+
const { subdomain } = await fetchResult(
|
|
330
|
+
`/accounts/${accountId}/workers/subdomain`
|
|
331
|
+
);
|
|
332
|
+
return subdomain;
|
|
333
|
+
} catch (e) {
|
|
334
|
+
const error = e as { code?: number };
|
|
335
|
+
if (typeof error === "object" && !!error && error.code === 10007) {
|
|
336
|
+
// 10007 error code: not found
|
|
337
|
+
// https://api.cloudflare.com/#worker-subdomain-get-subdomain
|
|
338
|
+
|
|
339
|
+
const errorMessage =
|
|
340
|
+
"Error: You need to register a workers.dev subdomain before publishing to workers.dev";
|
|
341
|
+
const solutionMessage =
|
|
342
|
+
"You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
|
|
343
|
+
const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
|
|
344
|
+
|
|
345
|
+
throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
|
|
346
|
+
} else {
|
|
347
|
+
throw e;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
package/src/reporting.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { appendFile, readFile } from "fs/promises";
|
|
2
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
|
-
import path from "path
|
|
4
|
+
import path from "node:path";
|
|
5
5
|
import TOML from "@iarna/toml";
|
|
6
6
|
import { RewriteFrames } from "@sentry/integrations";
|
|
7
7
|
import {
|
|
@@ -22,6 +22,14 @@ export function initReporting() {
|
|
|
22
22
|
initialScope: {
|
|
23
23
|
tags: { [pkj.name]: pkj.version },
|
|
24
24
|
},
|
|
25
|
+
beforeBreadcrumb: (_breadcrumb, _hint) => {
|
|
26
|
+
// eventually, we might want to actually start dropping our own breadcrumbs.
|
|
27
|
+
// until then, we want to just ignore all of them, since sentry will
|
|
28
|
+
// automatically include breadcrumbs for things like console output,
|
|
29
|
+
// which can include unsanitized data that the user logs
|
|
30
|
+
// from their worker during development
|
|
31
|
+
return null;
|
|
32
|
+
},
|
|
25
33
|
dsn: "https://5089b76bf8a64a9c949bf5c2b5e8003c@o51786.ingest.sentry.io/6190959",
|
|
26
34
|
tracesSampleRate: 1.0,
|
|
27
35
|
integrations: [
|
|
@@ -43,12 +51,20 @@ export function initReporting() {
|
|
|
43
51
|
|
|
44
52
|
async function appendReportingDecision(userInput: "true" | "false") {
|
|
45
53
|
const homePath = path.join(os.homedir(), ".wrangler/config/");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
const reportingConfigLocation = path.join(homePath, "reporting.toml");
|
|
55
|
+
try {
|
|
56
|
+
fs.mkdirSync(homePath, { recursive: true });
|
|
57
|
+
await writeFile(
|
|
58
|
+
reportingConfigLocation,
|
|
59
|
+
`error_tracking_opt = ${userInput} # Sentry \nerror_tracking_opt_date = ${new Date().toISOString()} # Sentry Date Decision \n`,
|
|
60
|
+
"utf-8"
|
|
61
|
+
);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.warn(
|
|
64
|
+
`Unable to create wrangler's error reporting configuration file at ${reportingConfigLocation}: `,
|
|
65
|
+
err
|
|
66
|
+
);
|
|
67
|
+
}
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
function exceptionTransaction(error: Error, origin = "") {
|
|
@@ -72,7 +88,7 @@ export async function reportError(err: Error, origin = "") {
|
|
|
72
88
|
message: "Would you like to submit a report when an error occurs?",
|
|
73
89
|
choices: [
|
|
74
90
|
{ title: "Always", value: true },
|
|
75
|
-
{ title: "
|
|
91
|
+
{ title: "Only this time", value: "once" },
|
|
76
92
|
{ title: "No", value: false },
|
|
77
93
|
],
|
|
78
94
|
initial: 2,
|