wrangler 0.0.0-fb43821 → 0.0.0-fe5dcfd
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 +25 -10
- 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__/index.test.ts +42 -175
- package/src/__tests__/jest.setup.ts +22 -0
- package/src/__tests__/kv.test.ts +1199 -0
- package/src/__tests__/mock-cfetch.ts +173 -0
- package/src/__tests__/mock-dialogs.ts +65 -0
- package/src/__tests__/publish.test.ts +737 -0
- package/src/__tests__/run-in-tmp.ts +19 -0
- package/src/__tests__/run-wrangler.ts +32 -0
- package/src/__tests__/whoami.test.tsx +130 -0
- package/src/api/form_data.ts +66 -76
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +38 -44
- package/src/cfetch/index.ts +110 -0
- package/src/cfetch/internal.ts +71 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +634 -110
- package/src/dev.tsx +275 -272
- package/src/dialogs.tsx +2 -2
- package/src/index.tsx +504 -403
- package/src/inspect.ts +543 -0
- package/src/kv.tsx +109 -44
- package/src/pages.tsx +444 -222
- package/src/proxy.ts +302 -0
- package/src/publish.ts +123 -87
- package/src/sites.tsx +169 -41
- package/src/tail.tsx +5 -3
- package/src/user.tsx +64 -74
- package/src/whoami.tsx +69 -0
- package/wrangler-dist/cli.js +42792 -42619
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-cfetch.js +0 -42
- package/src/api/inspect.ts +0 -430
- package/src/cfetch.ts +0 -72
package/src/proxy.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { connect } from "node:http2";
|
|
2
|
+
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import type {
|
|
5
|
+
IncomingHttpHeaders,
|
|
6
|
+
RequestListener,
|
|
7
|
+
IncomingMessage,
|
|
8
|
+
ServerResponse,
|
|
9
|
+
Server,
|
|
10
|
+
} from "node:http";
|
|
11
|
+
import WebSocket from "faye-websocket";
|
|
12
|
+
import serveStatic from "serve-static";
|
|
13
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
14
|
+
import { useEffect, useRef } from "react";
|
|
15
|
+
|
|
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;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Rewrite references in request headers
|
|
39
|
+
* from the preview host to the local host.
|
|
40
|
+
*/
|
|
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}`);
|
|
52
|
+
}
|
|
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
|
+
};
|
|
111
|
+
}
|
|
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];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
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 });
|
|
167
|
+
});
|
|
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)
|
|
182
|
+
);
|
|
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,
|
|
234
|
+
});
|
|
235
|
+
return (request: IncomingMessage, response: ServerResponse) => {
|
|
236
|
+
handleAsset(request, response, () => {
|
|
237
|
+
handleRequest(request, response);
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** A Set of headers we want to remove from HTTP/1 requests. */
|
|
243
|
+
const HTTP1_HEADERS = new Set([
|
|
244
|
+
"host",
|
|
245
|
+
"connection",
|
|
246
|
+
"upgrade",
|
|
247
|
+
"keep-alive",
|
|
248
|
+
"proxy-connection",
|
|
249
|
+
"transfer-encoding",
|
|
250
|
+
"http2-settings",
|
|
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,30 +1,35 @@
|
|
|
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";
|
|
13
|
+
import type { AssetPaths } from "./sites";
|
|
14
|
+
import { syncAssets } from "./sites";
|
|
12
15
|
|
|
13
|
-
type CfScriptFormat =
|
|
16
|
+
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
14
17
|
|
|
15
18
|
type Props = {
|
|
16
19
|
config: Config;
|
|
17
|
-
format
|
|
18
|
-
script
|
|
19
|
-
name
|
|
20
|
-
env
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
format: CfScriptFormat | undefined;
|
|
21
|
+
script: string | undefined;
|
|
22
|
+
name: string | undefined;
|
|
23
|
+
env: string | undefined;
|
|
24
|
+
compatibilityDate: string | undefined;
|
|
25
|
+
compatibilityFlags: string[] | undefined;
|
|
26
|
+
assetPaths: AssetPaths | undefined;
|
|
27
|
+
triggers: (string | number)[] | undefined;
|
|
28
|
+
routes: (string | number)[] | undefined;
|
|
29
|
+
legacyEnv: boolean | undefined;
|
|
30
|
+
jsxFactory: undefined | string;
|
|
31
|
+
jsxFragment: undefined | string;
|
|
32
|
+
experimentalPublic: boolean;
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
function sleep(ms: number) {
|
|
@@ -32,10 +37,10 @@ function sleep(ms: number) {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export default async function publish(props: Props): Promise<void> {
|
|
35
|
-
if (props.
|
|
40
|
+
if (props.experimentalPublic && props.format === "service-worker") {
|
|
36
41
|
// TODO: check config too
|
|
37
42
|
throw new Error(
|
|
38
|
-
"You cannot
|
|
43
|
+
"You cannot publish in the service worker format with a public directory."
|
|
39
44
|
);
|
|
40
45
|
}
|
|
41
46
|
// TODO: warn if git/hg has uncommitted changes
|
|
@@ -47,6 +52,18 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
47
52
|
__path__,
|
|
48
53
|
} = config;
|
|
49
54
|
|
|
55
|
+
const envRootObj =
|
|
56
|
+
props.env && config.env ? config.env[props.env] || {} : config;
|
|
57
|
+
|
|
58
|
+
assert(
|
|
59
|
+
envRootObj.compatibility_date || props.compatibilityDate,
|
|
60
|
+
"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."
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (accountId === undefined) {
|
|
64
|
+
throw new Error("No account_id provided.");
|
|
65
|
+
}
|
|
66
|
+
|
|
50
67
|
const triggers = props.triggers || config.triggers?.crons;
|
|
51
68
|
const routes = props.routes || config.routes;
|
|
52
69
|
|
|
@@ -63,10 +80,12 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
63
80
|
|
|
64
81
|
let file: string;
|
|
65
82
|
if (props.script) {
|
|
66
|
-
|
|
83
|
+
// If the script name comes from the command line it is relative to the current working directory.
|
|
84
|
+
file = path.resolve(props.script);
|
|
67
85
|
} else {
|
|
86
|
+
// If the script name comes from the config, then it is relative to the wrangler.toml file.
|
|
68
87
|
assert(build?.upload?.main, "missing main file");
|
|
69
|
-
file = path.
|
|
88
|
+
file = path.resolve(path.dirname(__path__), build.upload.main);
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
if (props.legacyEnv) {
|
|
@@ -75,10 +94,20 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
75
94
|
const envName = props.env ?? "production";
|
|
76
95
|
|
|
77
96
|
const destination = await tmp.dir({ unsafeCleanup: true });
|
|
97
|
+
if (props.config.build?.command) {
|
|
98
|
+
// TODO: add a deprecation message here?
|
|
99
|
+
console.log("running:", props.config.build.command);
|
|
100
|
+
const buildCommandPieces = props.config.build.command.split(" ");
|
|
101
|
+
await execa(buildCommandPieces[0], buildCommandPieces.slice(1), {
|
|
102
|
+
stdout: "inherit",
|
|
103
|
+
stderr: "inherit",
|
|
104
|
+
...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
78
107
|
|
|
79
108
|
const moduleCollector = makeModuleCollector();
|
|
80
109
|
const result = await esbuild.build({
|
|
81
|
-
...(props.
|
|
110
|
+
...(props.experimentalPublic
|
|
82
111
|
? {
|
|
83
112
|
stdin: {
|
|
84
113
|
contents: (
|
|
@@ -86,14 +115,14 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
86
115
|
path.join(__dirname, "../static-asset-facade.js"),
|
|
87
116
|
"utf8"
|
|
88
117
|
)
|
|
89
|
-
).replace("__ENTRY_POINT__",
|
|
118
|
+
).replace("__ENTRY_POINT__", file),
|
|
90
119
|
sourcefile: "static-asset-facade.js",
|
|
91
120
|
resolveDir: path.dirname(file),
|
|
92
121
|
},
|
|
122
|
+
nodePaths: [path.join(__dirname, "../vendor")],
|
|
93
123
|
}
|
|
94
124
|
: { entryPoints: [file] }),
|
|
95
125
|
bundle: true,
|
|
96
|
-
nodePaths: props.public ? [path.join(__dirname, "../vendor")] : undefined,
|
|
97
126
|
outdir: destination.path,
|
|
98
127
|
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
99
128
|
format: "esm",
|
|
@@ -108,18 +137,28 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
108
137
|
...(jsxFragment && { jsxFragment }),
|
|
109
138
|
});
|
|
110
139
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
: file)
|
|
140
|
+
// result.metafile is defined because of the `metafile: true` option above.
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
142
|
+
const metafile = result.metafile!;
|
|
143
|
+
const entryPoints = Object.entries(metafile.outputs).filter(
|
|
144
|
+
([_path, output]) => output.entryPoint !== undefined
|
|
117
145
|
);
|
|
118
|
-
|
|
146
|
+
assert(
|
|
147
|
+
entryPoints.length > 0,
|
|
148
|
+
`Cannot find entry-point "${file}" in generated bundle.` +
|
|
149
|
+
listEntryPoints(entryPoints)
|
|
150
|
+
);
|
|
151
|
+
assert(
|
|
152
|
+
entryPoints.length < 2,
|
|
153
|
+
"More than one entry-point found for generated bundle." +
|
|
154
|
+
listEntryPoints(entryPoints)
|
|
155
|
+
);
|
|
156
|
+
const entryPointExports = entryPoints[0][1].exports;
|
|
157
|
+
const resolvedEntryPointPath = entryPoints[0][0];
|
|
119
158
|
const { format } = props;
|
|
120
159
|
const bundle = {
|
|
121
|
-
type:
|
|
122
|
-
exports:
|
|
160
|
+
type: entryPointExports.length > 0 ? "esm" : "commonjs",
|
|
161
|
+
exports: entryPointExports,
|
|
123
162
|
};
|
|
124
163
|
|
|
125
164
|
// TODO: instead of bundling the facade with the worker, we should just bundle the worker and expose it as a module.
|
|
@@ -136,17 +175,17 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
136
175
|
return;
|
|
137
176
|
}
|
|
138
177
|
|
|
139
|
-
const content = await readFile(
|
|
178
|
+
const content = await readFile(resolvedEntryPointPath, { encoding: "utf-8" });
|
|
140
179
|
await destination.cleanup();
|
|
141
180
|
|
|
142
181
|
// if config.migrations
|
|
143
182
|
// get current migration tag
|
|
144
183
|
let migrations;
|
|
145
|
-
if (
|
|
146
|
-
const scripts = await
|
|
184
|
+
if (config.migrations !== undefined) {
|
|
185
|
+
const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
|
|
147
186
|
`/accounts/${accountId}/workers/scripts`
|
|
148
187
|
);
|
|
149
|
-
const script = scripts.find((
|
|
188
|
+
const script = scripts.find(({ id }) => id === scriptName);
|
|
150
189
|
if (script?.migration_tag) {
|
|
151
190
|
// was already published once
|
|
152
191
|
const foundIndex = config.migrations.findIndex(
|
|
@@ -154,7 +193,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
154
193
|
);
|
|
155
194
|
if (foundIndex === -1) {
|
|
156
195
|
console.warn(
|
|
157
|
-
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already
|
|
196
|
+
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
|
|
158
197
|
);
|
|
159
198
|
migrations = {
|
|
160
199
|
old_tag: script.migration_tag,
|
|
@@ -178,54 +217,42 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
178
217
|
}
|
|
179
218
|
}
|
|
180
219
|
|
|
181
|
-
const assets =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
false
|
|
188
|
-
)
|
|
189
|
-
: { manifest: undefined, namespace: undefined };
|
|
220
|
+
const assets = await syncAssets(
|
|
221
|
+
accountId,
|
|
222
|
+
scriptName,
|
|
223
|
+
props.assetPaths,
|
|
224
|
+
false
|
|
225
|
+
);
|
|
190
226
|
|
|
191
|
-
const
|
|
227
|
+
const bindings: CfWorkerInit["bindings"] = {
|
|
228
|
+
kv_namespaces: envRootObj.kv_namespaces?.concat(
|
|
229
|
+
assets.namespace
|
|
230
|
+
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
231
|
+
: []
|
|
232
|
+
),
|
|
233
|
+
vars: envRootObj.vars,
|
|
234
|
+
durable_objects: envRootObj.durable_objects,
|
|
235
|
+
services: envRootObj.experimental_services,
|
|
236
|
+
};
|
|
192
237
|
|
|
193
238
|
const worker: CfWorkerInit = {
|
|
194
239
|
name: scriptName,
|
|
195
240
|
main: {
|
|
196
|
-
name: path.basename(
|
|
241
|
+
name: path.basename(resolvedEntryPointPath),
|
|
197
242
|
content: content,
|
|
198
243
|
type: bundle.type === "esm" ? "esm" : "commonjs",
|
|
199
244
|
},
|
|
200
|
-
|
|
201
|
-
...(envRootObj?.vars || {}),
|
|
202
|
-
...(envRootObj?.kv_namespaces || []).reduce(
|
|
203
|
-
(obj, { binding, preview_id: _preview_id, id }) => {
|
|
204
|
-
return { ...obj, [binding]: { namespaceId: id } };
|
|
205
|
-
},
|
|
206
|
-
{}
|
|
207
|
-
),
|
|
208
|
-
...(envRootObj?.durable_objects?.bindings || []).reduce(
|
|
209
|
-
(obj, { name, class_name, script_name }) => {
|
|
210
|
-
return {
|
|
211
|
-
...obj,
|
|
212
|
-
[name]: { class_name, ...(script_name && { script_name }) },
|
|
213
|
-
};
|
|
214
|
-
},
|
|
215
|
-
{}
|
|
216
|
-
),
|
|
217
|
-
...(assets.namespace
|
|
218
|
-
? { __STATIC_CONTENT: { namespaceId: assets.namespace } }
|
|
219
|
-
: {}),
|
|
220
|
-
},
|
|
245
|
+
bindings,
|
|
221
246
|
...(migrations && { migrations }),
|
|
222
|
-
modules:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
247
|
+
modules: moduleCollector.modules.concat(
|
|
248
|
+
assets.manifest
|
|
249
|
+
? {
|
|
250
|
+
name: "__STATIC_CONTENT_MANIFEST",
|
|
251
|
+
content: JSON.stringify(assets.manifest),
|
|
252
|
+
type: "text",
|
|
253
|
+
}
|
|
254
|
+
: []
|
|
255
|
+
),
|
|
229
256
|
compatibility_date: config.compatibility_date,
|
|
230
257
|
compatibility_flags: config.compatibility_flags,
|
|
231
258
|
usage_model: config.usage_model,
|
|
@@ -242,14 +269,15 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
242
269
|
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
|
|
243
270
|
: `/accounts/${accountId}/workers/scripts/${scriptName}`;
|
|
244
271
|
|
|
245
|
-
// Upload the script so it has time to
|
|
246
|
-
const { available_on_subdomain } = await
|
|
247
|
-
|
|
272
|
+
// Upload the script so it has time to propagate.
|
|
273
|
+
const { available_on_subdomain } = await fetchResult(
|
|
274
|
+
workerUrl,
|
|
248
275
|
{
|
|
249
276
|
method: "PUT",
|
|
250
277
|
// @ts-expect-error: TODO: fix this type error!
|
|
251
278
|
body: toFormData(worker),
|
|
252
|
-
}
|
|
279
|
+
},
|
|
280
|
+
new URLSearchParams({ available_on_subdomains: "true" })
|
|
253
281
|
);
|
|
254
282
|
|
|
255
283
|
const uploadMs = Date.now() - start;
|
|
@@ -257,7 +285,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
257
285
|
const deployments: Promise<string[]>[] = [];
|
|
258
286
|
|
|
259
287
|
const userSubdomain = (
|
|
260
|
-
await
|
|
288
|
+
await fetchResult<{ subdomain: string }>(
|
|
261
289
|
`/accounts/${accountId}/workers/subdomain`
|
|
262
290
|
)
|
|
263
291
|
).subdomain;
|
|
@@ -271,7 +299,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
271
299
|
// TODO: Make this configurable.
|
|
272
300
|
if (!available_on_subdomain) {
|
|
273
301
|
deployments.push(
|
|
274
|
-
|
|
302
|
+
fetchResult(`${workerUrl}/subdomain`, {
|
|
275
303
|
method: "POST",
|
|
276
304
|
body: JSON.stringify({ enabled: true }),
|
|
277
305
|
headers: {
|
|
@@ -295,7 +323,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
295
323
|
// Update routing table for the script.
|
|
296
324
|
if (routes && routes.length) {
|
|
297
325
|
deployments.push(
|
|
298
|
-
|
|
326
|
+
fetchResult(`${workerUrl}/routes`, {
|
|
299
327
|
// TODO: PATCH will not delete previous routes on this script,
|
|
300
328
|
// whereas PUT will. We need to decide on the default behaviour
|
|
301
329
|
// and how to configure it.
|
|
@@ -320,7 +348,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
320
348
|
// TODO: rename this to `schedules`?
|
|
321
349
|
if (triggers && triggers.length) {
|
|
322
350
|
deployments.push(
|
|
323
|
-
|
|
351
|
+
fetchResult(`${workerUrl}/schedules`, {
|
|
324
352
|
// TODO: Unlike routes, this endpoint does not support PATCH.
|
|
325
353
|
// So technically, this will override any previous schedules.
|
|
326
354
|
// We should change the endpoint to support PATCH.
|
|
@@ -344,3 +372,11 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
344
372
|
console.log(" ", target);
|
|
345
373
|
}
|
|
346
374
|
}
|
|
375
|
+
|
|
376
|
+
function listEntryPoints(
|
|
377
|
+
outputs: [string, ValueOf<Metafile["outputs"]>][]
|
|
378
|
+
): string {
|
|
379
|
+
return outputs.map(([_input, output]) => output.entryPoint).join("\n");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type ValueOf<T> = T[keyof T];
|