wrangler 0.0.0-e9a1820 → 0.0.0-e9ab55a
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 +12 -5
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +228 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +136 -0
- package/src/__tests__/dev.test.tsx +67 -0
- package/src/__tests__/jest.setup.ts +5 -1
- package/src/__tests__/kv.test.ts +849 -45
- package/src/__tests__/mock-cfetch.ts +137 -29
- package/src/__tests__/publish.test.ts +102 -0
- package/src/api/form_data.ts +1 -1
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +6 -6
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +69 -0
- package/src/config.ts +633 -118
- package/src/dev.tsx +114 -209
- package/src/index.tsx +265 -239
- package/src/inspect.ts +543 -0
- package/src/kv.tsx +49 -30
- package/src/pages.tsx +431 -242
- package/src/proxy.ts +275 -76
- package/src/publish.ts +79 -47
- package/src/sites.tsx +10 -7
- package/src/tail.tsx +5 -3
- package/src/user.tsx +22 -17
- package/wrangler-dist/cli.js +42319 -36190
- package/wrangler-dist/cli.js.map +3 -3
- package/src/api/inspect.ts +0 -430
- package/src/cfetch.ts +0 -72
package/src/proxy.ts
CHANGED
|
@@ -1,97 +1,245 @@
|
|
|
1
1
|
import { connect } from "node:http2";
|
|
2
|
+
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IncomingHttpHeaders,
|
|
6
|
+
RequestListener,
|
|
7
|
+
IncomingMessage,
|
|
8
|
+
ServerResponse,
|
|
9
|
+
Server,
|
|
10
|
+
} from "node:http";
|
|
4
11
|
import WebSocket from "faye-websocket";
|
|
5
12
|
import serveStatic from "serve-static";
|
|
13
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
14
|
+
import { useEffect, useRef } from "react";
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `usePreviewServer` is a React hook that creates a local development
|
|
18
|
+
* server that can be used to develop a Worker.
|
|
19
|
+
*
|
|
20
|
+
* When we run `wrangler dev`, we start by uploading the compiled worker
|
|
21
|
+
* to the preview service, which responds with a preview token.
|
|
22
|
+
* (see `useWorker`/`createWorker` for details.)
|
|
23
|
+
* We can then use that token to connect to the preview server for a
|
|
24
|
+
* great local development experience. Further, as we change the worker,
|
|
25
|
+
* we can update the preview token transparently without having to restart
|
|
26
|
+
* the development server.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Rewrite request headers to add the preview token. */
|
|
30
|
+
function addCfPreviewTokenHeader(
|
|
31
|
+
headers: IncomingHttpHeaders,
|
|
32
|
+
previewTokenValue: string
|
|
33
|
+
) {
|
|
34
|
+
headers["cf-workers-preview-token"] = previewTokenValue;
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
/**
|
|
15
|
-
*
|
|
38
|
+
* Rewrite references in request headers
|
|
39
|
+
* from the preview host to the local host.
|
|
16
40
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stream.respond(responseHeaders);
|
|
29
|
-
request.pipe(stream, { end: true });
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
// HTTP/1 -> HTTP/2
|
|
33
|
-
const handleRequest: RequestListener = (message, response) => {
|
|
34
|
-
const { httpVersionMajor, headers, method, url } = message;
|
|
35
|
-
if (httpVersionMajor >= 2) {
|
|
36
|
-
return; // Already handled by the "stream" event.
|
|
41
|
+
function rewriteRemoteHostToLocalHostInHeaders(
|
|
42
|
+
headers: IncomingHttpHeaders,
|
|
43
|
+
remoteHost: string,
|
|
44
|
+
localPort: number
|
|
45
|
+
) {
|
|
46
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
47
|
+
// Rewrite the remote host to the local host.
|
|
48
|
+
if (typeof value === "string" && value.includes(remoteHost)) {
|
|
49
|
+
headers[name] = value
|
|
50
|
+
.replaceAll(`https://${remoteHost}`, `http://localhost:${localPort}`)
|
|
51
|
+
.replaceAll(remoteHost, `localhost:${localPort}`);
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function usePreviewServer({
|
|
57
|
+
previewToken,
|
|
58
|
+
publicRoot,
|
|
59
|
+
port,
|
|
60
|
+
}: {
|
|
61
|
+
previewToken: CfPreviewToken | undefined;
|
|
62
|
+
publicRoot: undefined | string;
|
|
63
|
+
port: number;
|
|
64
|
+
}) {
|
|
65
|
+
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
66
|
+
const proxyServer = useRef<Server>();
|
|
67
|
+
const proxy = (proxyServer.current ??= createProxyServer());
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* When we're not connected / getting a fresh token on changes,
|
|
71
|
+
* we'd like to buffer streams/requests until we're connected.
|
|
72
|
+
* Once connected, we can flush the buffered streams/requests.
|
|
73
|
+
* streamBufferRef is used to buffer http/2 streams, while
|
|
74
|
+
* requestResponseBufferRef is used to buffer http/1 requests.
|
|
75
|
+
*/
|
|
76
|
+
const streamBufferRef = useRef<
|
|
77
|
+
{ stream: ServerHttp2Stream; headers: IncomingHttpHeaders }[]
|
|
78
|
+
>([]);
|
|
79
|
+
const requestResponseBufferRef = useRef<
|
|
80
|
+
{ request: IncomingMessage; response: ServerResponse }[]
|
|
81
|
+
>([]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
// If we don't have a token, that means either we're just starting up,
|
|
85
|
+
// or we're refreshing the token.
|
|
86
|
+
if (!previewToken) {
|
|
87
|
+
const cleanupListeners: (() => void)[] = [];
|
|
88
|
+
const bufferStream = (
|
|
89
|
+
stream: ServerHttp2Stream,
|
|
90
|
+
headers: IncomingHttpHeaders
|
|
91
|
+
) => {
|
|
92
|
+
// store the stream in a buffer so we can replay it later
|
|
93
|
+
streamBufferRef.current.push({ stream, headers });
|
|
94
|
+
};
|
|
95
|
+
proxy.on("stream", bufferStream);
|
|
96
|
+
cleanupListeners.push(() => proxy.off("stream", bufferStream));
|
|
97
|
+
|
|
98
|
+
const bufferRequestResponse = (
|
|
99
|
+
request: IncomingMessage,
|
|
100
|
+
response: ServerResponse
|
|
101
|
+
) => {
|
|
102
|
+
// store the request and response in a buffer so we can replay it later
|
|
103
|
+
requestResponseBufferRef.current.push({ request, response });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
proxy.on("request", bufferRequestResponse);
|
|
107
|
+
cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
|
|
108
|
+
return () => {
|
|
109
|
+
cleanupListeners.forEach((cleanup) => cleanup());
|
|
110
|
+
};
|
|
47
111
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
112
|
+
|
|
113
|
+
// We have a token. Let's proxy requests to the preview end point.
|
|
114
|
+
const cleanupListeners: (() => void)[] = [];
|
|
115
|
+
|
|
116
|
+
const assetPath = typeof publicRoot === "string" ? publicRoot : null;
|
|
117
|
+
|
|
118
|
+
// create a ClientHttp2Session
|
|
119
|
+
const remote = connect(`https://${previewToken.host}`);
|
|
120
|
+
cleanupListeners.push(() => remote.destroy());
|
|
121
|
+
|
|
122
|
+
/** HTTP/2 -> HTTP/2 */
|
|
123
|
+
const handleStream = createStreamHandler(previewToken, remote, port);
|
|
124
|
+
proxy.on("stream", handleStream);
|
|
125
|
+
cleanupListeners.push(() => proxy.off("stream", handleStream));
|
|
126
|
+
|
|
127
|
+
// flush and replay buffered streams
|
|
128
|
+
streamBufferRef.current.forEach((buffer) =>
|
|
129
|
+
handleStream(buffer.stream, buffer.headers)
|
|
130
|
+
);
|
|
131
|
+
streamBufferRef.current = [];
|
|
132
|
+
|
|
133
|
+
/** HTTP/1 -> HTTP/2 */
|
|
134
|
+
const handleRequest: RequestListener = (
|
|
135
|
+
message: IncomingMessage,
|
|
136
|
+
response: ServerResponse
|
|
137
|
+
) => {
|
|
138
|
+
const { httpVersionMajor, headers, method, url } = message;
|
|
139
|
+
if (httpVersionMajor >= 2) {
|
|
140
|
+
return; // Already handled by the "stream" event.
|
|
141
|
+
}
|
|
142
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
143
|
+
headers[":method"] = method;
|
|
144
|
+
headers[":path"] = url;
|
|
145
|
+
headers[":authority"] = previewToken.host;
|
|
146
|
+
headers[":scheme"] = "https";
|
|
147
|
+
for (const name of Object.keys(headers)) {
|
|
148
|
+
if (HTTP1_HEADERS.has(name.toLowerCase())) {
|
|
149
|
+
delete headers[name];
|
|
55
150
|
}
|
|
56
151
|
}
|
|
57
|
-
|
|
58
|
-
request.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
152
|
+
const request = message.pipe(remote.request(headers));
|
|
153
|
+
request.on("response", (responseHeaders) => {
|
|
154
|
+
const status = responseHeaders[":status"] ?? 500;
|
|
155
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
156
|
+
responseHeaders,
|
|
157
|
+
previewToken.host,
|
|
158
|
+
port
|
|
159
|
+
);
|
|
160
|
+
for (const name of Object.keys(responseHeaders)) {
|
|
161
|
+
if (name.startsWith(":")) {
|
|
162
|
+
delete responseHeaders[name];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
response.writeHead(status, responseHeaders);
|
|
166
|
+
request.pipe(response, { end: true });
|
|
70
167
|
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{ headers }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// If an asset path is defined, check the file system
|
|
171
|
+
// for a file first and serve if it exists.
|
|
172
|
+
const actualHandleRequest = assetPath
|
|
173
|
+
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
174
|
+
: handleRequest;
|
|
175
|
+
|
|
176
|
+
proxy.on("request", actualHandleRequest);
|
|
177
|
+
cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
|
|
178
|
+
|
|
179
|
+
// flush and replay buffered requests
|
|
180
|
+
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
181
|
+
actualHandleRequest(request, response)
|
|
86
182
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
183
|
+
requestResponseBufferRef.current = [];
|
|
184
|
+
|
|
185
|
+
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
186
|
+
const handleUpgrade = (
|
|
187
|
+
message: IncomingMessage,
|
|
188
|
+
socket: WebSocket,
|
|
189
|
+
body: Buffer
|
|
190
|
+
) => {
|
|
191
|
+
const { headers, url } = message;
|
|
192
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
193
|
+
headers["host"] = previewToken.host;
|
|
194
|
+
const localWebsocket = new WebSocket(message, socket, body);
|
|
195
|
+
// TODO(soon): Custom WebSocket protocol is not working?
|
|
196
|
+
const remoteWebsocketClient = new WebSocket.Client(
|
|
197
|
+
`wss://${previewToken.host}${url}`,
|
|
198
|
+
[],
|
|
199
|
+
{ headers }
|
|
200
|
+
);
|
|
201
|
+
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
202
|
+
// We close down websockets whenever we refresh the token.
|
|
203
|
+
cleanupListeners.push(() => {
|
|
204
|
+
localWebsocket.destroy();
|
|
205
|
+
remoteWebsocketClient.destroy();
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
proxy.on("upgrade", handleUpgrade);
|
|
209
|
+
cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
|
|
210
|
+
|
|
211
|
+
return () => {
|
|
212
|
+
cleanupListeners.forEach((d) => d());
|
|
213
|
+
};
|
|
214
|
+
}, [previewToken, publicRoot, port, proxy]);
|
|
215
|
+
|
|
216
|
+
// Start/stop the server whenever the
|
|
217
|
+
// containing component is mounted/unmounted.
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
proxy.listen(port);
|
|
220
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
221
|
+
|
|
222
|
+
return () => {
|
|
223
|
+
proxy.close();
|
|
224
|
+
};
|
|
225
|
+
}, [port, proxy]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function createHandleAssetsRequest(
|
|
229
|
+
assetPath: string,
|
|
230
|
+
handleRequest: RequestListener
|
|
231
|
+
) {
|
|
232
|
+
const handleAsset = serveStatic(assetPath, {
|
|
233
|
+
cacheControl: false,
|
|
91
234
|
});
|
|
92
|
-
return
|
|
235
|
+
return (request: IncomingMessage, response: ServerResponse) => {
|
|
236
|
+
handleAsset(request, response, () => {
|
|
237
|
+
handleRequest(request, response);
|
|
238
|
+
});
|
|
239
|
+
};
|
|
93
240
|
}
|
|
94
241
|
|
|
242
|
+
/** A Set of headers we want to remove from HTTP/1 requests. */
|
|
95
243
|
const HTTP1_HEADERS = new Set([
|
|
96
244
|
"host",
|
|
97
245
|
"connection",
|
|
@@ -101,3 +249,54 @@ const HTTP1_HEADERS = new Set([
|
|
|
101
249
|
"transfer-encoding",
|
|
102
250
|
"http2-settings",
|
|
103
251
|
]);
|
|
252
|
+
|
|
253
|
+
function createProxyServer() {
|
|
254
|
+
return createServer()
|
|
255
|
+
.on("request", function (req, res) {
|
|
256
|
+
// log all requests
|
|
257
|
+
console.log(
|
|
258
|
+
new Date().toLocaleTimeString(),
|
|
259
|
+
req.method,
|
|
260
|
+
req.url,
|
|
261
|
+
res.statusCode
|
|
262
|
+
);
|
|
263
|
+
})
|
|
264
|
+
.on("upgrade", (req) => {
|
|
265
|
+
// log all websocket connections
|
|
266
|
+
console.log(
|
|
267
|
+
new Date().toLocaleTimeString(),
|
|
268
|
+
req.method,
|
|
269
|
+
req.url,
|
|
270
|
+
101,
|
|
271
|
+
"(WebSocket)"
|
|
272
|
+
);
|
|
273
|
+
})
|
|
274
|
+
.on("error", (err) => {
|
|
275
|
+
// log all connection errors
|
|
276
|
+
console.error(new Date().toLocaleTimeString(), err);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function createStreamHandler(
|
|
281
|
+
previewToken: CfPreviewToken,
|
|
282
|
+
remote: ClientHttp2Session,
|
|
283
|
+
port: number
|
|
284
|
+
) {
|
|
285
|
+
return function handleStream(
|
|
286
|
+
stream: ServerHttp2Stream,
|
|
287
|
+
headers: IncomingHttpHeaders
|
|
288
|
+
) {
|
|
289
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
290
|
+
headers[":authority"] = previewToken.host;
|
|
291
|
+
const request = stream.pipe(remote.request(headers));
|
|
292
|
+
request.on("response", (responseHeaders: IncomingHttpHeaders) => {
|
|
293
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
294
|
+
responseHeaders,
|
|
295
|
+
previewToken.host,
|
|
296
|
+
port
|
|
297
|
+
);
|
|
298
|
+
stream.respond(responseHeaders);
|
|
299
|
+
request.pipe(stream, { end: true });
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
}
|
package/src/publish.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import * as esbuild from "esbuild";
|
|
5
|
+
import type { Metafile } from "esbuild";
|
|
6
|
+
import { execa } from "execa";
|
|
7
|
+
import tmp from "tmp-promise";
|
|
1
8
|
import type { CfWorkerInit } from "./api/worker";
|
|
2
9
|
import { toFormData } from "./api/form_data";
|
|
3
|
-
import
|
|
4
|
-
import tmp from "tmp-promise";
|
|
10
|
+
import { fetchResult } from "./cfetch";
|
|
5
11
|
import type { Config } from "./config";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import { readFile } from "fs/promises";
|
|
8
|
-
import cfetch from "./cfetch";
|
|
9
|
-
import assert from "node:assert";
|
|
10
|
-
import { syncAssets } from "./sites";
|
|
11
12
|
import makeModuleCollector from "./module-collection";
|
|
12
|
-
import {
|
|
13
|
+
import { syncAssets } from "./sites";
|
|
13
14
|
|
|
14
|
-
type CfScriptFormat =
|
|
15
|
+
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
15
16
|
|
|
16
17
|
type Props = {
|
|
17
18
|
config: Config;
|
|
@@ -19,13 +20,15 @@ type Props = {
|
|
|
19
20
|
script?: string;
|
|
20
21
|
name?: string;
|
|
21
22
|
env?: string;
|
|
23
|
+
compatibilityDate?: string;
|
|
24
|
+
compatibilityFlags?: string[];
|
|
22
25
|
public?: string;
|
|
23
26
|
site?: string;
|
|
24
27
|
triggers?: (string | number)[];
|
|
25
28
|
routes?: (string | number)[];
|
|
26
29
|
legacyEnv?: boolean;
|
|
27
|
-
jsxFactory:
|
|
28
|
-
jsxFragment:
|
|
30
|
+
jsxFactory: undefined | string;
|
|
31
|
+
jsxFragment: undefined | string;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
function sleep(ms: number) {
|
|
@@ -48,6 +51,18 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
48
51
|
__path__,
|
|
49
52
|
} = config;
|
|
50
53
|
|
|
54
|
+
const envRootObj =
|
|
55
|
+
props.env && config.env ? config.env[props.env] || {} : config;
|
|
56
|
+
|
|
57
|
+
assert(
|
|
58
|
+
envRootObj.compatibility_date || props.compatibilityDate,
|
|
59
|
+
"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."
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (accountId === undefined) {
|
|
63
|
+
throw new Error("No account_id provided.");
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
const triggers = props.triggers || config.triggers?.crons;
|
|
52
67
|
const routes = props.routes || config.routes;
|
|
53
68
|
|
|
@@ -64,10 +79,12 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
64
79
|
|
|
65
80
|
let file: string;
|
|
66
81
|
if (props.script) {
|
|
67
|
-
|
|
82
|
+
// If the script name comes from the command line it is relative to the current working directory.
|
|
83
|
+
file = path.resolve(props.script);
|
|
68
84
|
} else {
|
|
85
|
+
// If the script name comes from the config, then it is relative to the wrangler.toml file.
|
|
69
86
|
assert(build?.upload?.main, "missing main file");
|
|
70
|
-
file = path.
|
|
87
|
+
file = path.resolve(path.dirname(__path__), build.upload.main);
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
if (props.legacyEnv) {
|
|
@@ -76,7 +93,6 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
76
93
|
const envName = props.env ?? "production";
|
|
77
94
|
|
|
78
95
|
const destination = await tmp.dir({ unsafeCleanup: true });
|
|
79
|
-
|
|
80
96
|
if (props.config.build?.command) {
|
|
81
97
|
// TODO: add a deprecation message here?
|
|
82
98
|
console.log("running:", props.config.build.command);
|
|
@@ -98,7 +114,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
98
114
|
path.join(__dirname, "../static-asset-facade.js"),
|
|
99
115
|
"utf8"
|
|
100
116
|
)
|
|
101
|
-
).replace("__ENTRY_POINT__",
|
|
117
|
+
).replace("__ENTRY_POINT__", file),
|
|
102
118
|
sourcefile: "static-asset-facade.js",
|
|
103
119
|
resolveDir: path.dirname(file),
|
|
104
120
|
},
|
|
@@ -120,18 +136,33 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
120
136
|
...(jsxFragment && { jsxFragment }),
|
|
121
137
|
});
|
|
122
138
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
// result.metafile is defined because of the `metafile: true` option above.
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
141
|
+
const metafile = result.metafile!;
|
|
142
|
+
const entryPoints = Object.values(metafile.outputs).filter(
|
|
143
|
+
(output) => output.entryPoint !== undefined
|
|
144
|
+
);
|
|
145
|
+
assert(
|
|
146
|
+
entryPoints.length > 0,
|
|
147
|
+
`Cannot find entry-point "${file}" in generated bundle.` +
|
|
148
|
+
listEntryPoints(entryPoints)
|
|
149
|
+
);
|
|
150
|
+
assert(
|
|
151
|
+
entryPoints.length < 2,
|
|
152
|
+
"More than one entry-point found for generated bundle." +
|
|
153
|
+
listEntryPoints(entryPoints)
|
|
154
|
+
);
|
|
155
|
+
const entryPointExports = entryPoints[0].exports;
|
|
156
|
+
const resolvedEntryPointPath = path.resolve(
|
|
157
|
+
destination.path,
|
|
158
|
+
// We know that entryPoint is not null because we filtered out those without above.
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
160
|
+
entryPoints[0].entryPoint!
|
|
129
161
|
);
|
|
130
|
-
|
|
131
162
|
const { format } = props;
|
|
132
163
|
const bundle = {
|
|
133
|
-
type:
|
|
134
|
-
exports:
|
|
164
|
+
type: entryPointExports.length > 0 ? "esm" : "commonjs",
|
|
165
|
+
exports: entryPointExports,
|
|
135
166
|
};
|
|
136
167
|
|
|
137
168
|
// TODO: instead of bundling the facade with the worker, we should just bundle the worker and expose it as a module.
|
|
@@ -148,14 +179,14 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
148
179
|
return;
|
|
149
180
|
}
|
|
150
181
|
|
|
151
|
-
const content = await readFile(
|
|
182
|
+
const content = await readFile(resolvedEntryPointPath, { encoding: "utf-8" });
|
|
152
183
|
await destination.cleanup();
|
|
153
184
|
|
|
154
185
|
// if config.migrations
|
|
155
186
|
// get current migration tag
|
|
156
187
|
let migrations;
|
|
157
|
-
if (
|
|
158
|
-
const scripts = await
|
|
188
|
+
if (config.migrations !== undefined) {
|
|
189
|
+
const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
|
|
159
190
|
`/accounts/${accountId}/workers/scripts`
|
|
160
191
|
);
|
|
161
192
|
const script = scripts.find(({ id }) => id === scriptName);
|
|
@@ -190,17 +221,11 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
190
221
|
}
|
|
191
222
|
}
|
|
192
223
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
props.public || props.site || props.config.site?.bucket,
|
|
199
|
-
false
|
|
200
|
-
)
|
|
201
|
-
: { manifest: undefined, namespace: undefined };
|
|
202
|
-
|
|
203
|
-
const envRootObj = props.env ? config.env[props.env] || {} : config;
|
|
224
|
+
const assetPath = props.public || props.site || props.config.site?.bucket; // TODO: allow both
|
|
225
|
+
const assets = assetPath
|
|
226
|
+
? await syncAssets(accountId, scriptName, assetPath, false)
|
|
227
|
+
: { manifest: undefined, namespace: undefined };
|
|
228
|
+
|
|
204
229
|
const bindings: CfWorkerInit["bindings"] = {
|
|
205
230
|
kv_namespaces: envRootObj.kv_namespaces?.concat(
|
|
206
231
|
assets.namespace
|
|
@@ -215,7 +240,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
215
240
|
const worker: CfWorkerInit = {
|
|
216
241
|
name: scriptName,
|
|
217
242
|
main: {
|
|
218
|
-
name: path.basename(
|
|
243
|
+
name: path.basename(resolvedEntryPointPath),
|
|
219
244
|
content: content,
|
|
220
245
|
type: bundle.type === "esm" ? "esm" : "commonjs",
|
|
221
246
|
},
|
|
@@ -246,14 +271,15 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
246
271
|
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
|
|
247
272
|
: `/accounts/${accountId}/workers/scripts/${scriptName}`;
|
|
248
273
|
|
|
249
|
-
// Upload the script so it has time to
|
|
250
|
-
const { available_on_subdomain } = await
|
|
251
|
-
|
|
274
|
+
// Upload the script so it has time to propagate.
|
|
275
|
+
const { available_on_subdomain } = await fetchResult(
|
|
276
|
+
workerUrl,
|
|
252
277
|
{
|
|
253
278
|
method: "PUT",
|
|
254
279
|
// @ts-expect-error: TODO: fix this type error!
|
|
255
280
|
body: toFormData(worker),
|
|
256
|
-
}
|
|
281
|
+
},
|
|
282
|
+
new URLSearchParams({ available_on_subdomains: "true" })
|
|
257
283
|
);
|
|
258
284
|
|
|
259
285
|
const uploadMs = Date.now() - start;
|
|
@@ -261,7 +287,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
261
287
|
const deployments: Promise<string[]>[] = [];
|
|
262
288
|
|
|
263
289
|
const userSubdomain = (
|
|
264
|
-
await
|
|
290
|
+
await fetchResult<{ subdomain: string }>(
|
|
265
291
|
`/accounts/${accountId}/workers/subdomain`
|
|
266
292
|
)
|
|
267
293
|
).subdomain;
|
|
@@ -275,7 +301,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
275
301
|
// TODO: Make this configurable.
|
|
276
302
|
if (!available_on_subdomain) {
|
|
277
303
|
deployments.push(
|
|
278
|
-
|
|
304
|
+
fetchResult(`${workerUrl}/subdomain`, {
|
|
279
305
|
method: "POST",
|
|
280
306
|
body: JSON.stringify({ enabled: true }),
|
|
281
307
|
headers: {
|
|
@@ -299,7 +325,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
299
325
|
// Update routing table for the script.
|
|
300
326
|
if (routes && routes.length) {
|
|
301
327
|
deployments.push(
|
|
302
|
-
|
|
328
|
+
fetchResult(`${workerUrl}/routes`, {
|
|
303
329
|
// TODO: PATCH will not delete previous routes on this script,
|
|
304
330
|
// whereas PUT will. We need to decide on the default behaviour
|
|
305
331
|
// and how to configure it.
|
|
@@ -324,7 +350,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
324
350
|
// TODO: rename this to `schedules`?
|
|
325
351
|
if (triggers && triggers.length) {
|
|
326
352
|
deployments.push(
|
|
327
|
-
|
|
353
|
+
fetchResult(`${workerUrl}/schedules`, {
|
|
328
354
|
// TODO: Unlike routes, this endpoint does not support PATCH.
|
|
329
355
|
// So technically, this will override any previous schedules.
|
|
330
356
|
// We should change the endpoint to support PATCH.
|
|
@@ -348,3 +374,9 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
348
374
|
console.log(" ", target);
|
|
349
375
|
}
|
|
350
376
|
}
|
|
377
|
+
|
|
378
|
+
function listEntryPoints(outputs: ValueOf<Metafile["outputs"]>[]): string {
|
|
379
|
+
return outputs.map((output) => output.entryPoint).join("\n");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type ValueOf<T> = T[keyof T];
|
package/src/sites.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { fetchResult } from "./cfetch";
|
|
4
6
|
import { listNamespaceKeys, listNamespaces, putBulkKeyValue } from "./kv";
|
|
5
7
|
|
|
6
|
-
import * as path from "path";
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
|
-
|
|
9
8
|
async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
|
|
10
9
|
const files = await readdir(dirPath, { withFileTypes: true });
|
|
11
10
|
for (const file of files) {
|
|
@@ -54,7 +53,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
|
|
|
54
53
|
|
|
55
54
|
// else we make the namespace
|
|
56
55
|
// TODO: use an export from ./kv
|
|
57
|
-
const json = await
|
|
56
|
+
const json = await fetchResult<{ id: string }>(
|
|
58
57
|
`/accounts/${accountId}/storage/kv/namespaces`,
|
|
59
58
|
{
|
|
60
59
|
method: "POST",
|
|
@@ -90,7 +89,11 @@ export async function syncAssets(
|
|
|
90
89
|
);
|
|
91
90
|
|
|
92
91
|
const manifest = {};
|
|
93
|
-
const upload
|
|
92
|
+
const upload: {
|
|
93
|
+
key: string;
|
|
94
|
+
value: string;
|
|
95
|
+
base64: boolean;
|
|
96
|
+
}[] = [];
|
|
94
97
|
// TODO: this can be more efficient by parallelising
|
|
95
98
|
for await (const file of getFilesInFolder(dirPath)) {
|
|
96
99
|
// TODO: "exclude:" config
|