wrangler 0.0.0-ece06ea → 0.0.0-edc4b53
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 +24 -15
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +221 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +144 -0
- package/src/__tests__/dev.test.tsx +66 -0
- package/src/__tests__/index.test.ts +254 -120
- package/src/__tests__/jest.setup.ts +22 -0
- package/src/__tests__/kv.test.ts +1106 -0
- package/src/__tests__/mock-cfetch.ts +171 -0
- package/src/__tests__/mock-dialogs.ts +65 -0
- package/src/__tests__/run-in-tmp.ts +19 -0
- package/src/__tests__/run-wrangler.ts +32 -0
- package/src/api/form_data.ts +71 -98
- package/src/api/preview.ts +29 -24
- package/src/api/worker.ts +38 -44
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +65 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +476 -113
- package/src/dev.tsx +291 -256
- package/src/dialogs.tsx +4 -12
- package/src/index.tsx +381 -252
- package/src/inspect.ts +524 -0
- package/src/kv.tsx +110 -45
- package/src/module-collection.ts +64 -0
- package/src/pages.tsx +900 -213
- package/src/proxy.ts +294 -0
- package/src/publish.ts +67 -63
- package/src/sites.tsx +10 -7
- package/src/tail.tsx +5 -3
- package/src/user.tsx +31 -33
- package/wrangler-dist/cli.js +43444 -56347
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/fixtures/init/.gitkeep +0 -0
- package/src/__tests__/mock-cfetch.js +0 -46
- package/src/api/inspect.ts +0 -441
- package/src/cfetch.ts +0 -72
- package/src/util/fetch.ts +0 -74
package/src/proxy.ts
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { connect } from "node:http2";
|
|
2
|
+
import type { 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
|
+
if (!proxyServer.current) {
|
|
68
|
+
proxyServer.current = createServer()
|
|
69
|
+
.on("request", function (req, res) {
|
|
70
|
+
// log all requests
|
|
71
|
+
console.log(
|
|
72
|
+
new Date().toLocaleTimeString(),
|
|
73
|
+
req.method,
|
|
74
|
+
req.url,
|
|
75
|
+
res.statusCode
|
|
76
|
+
);
|
|
77
|
+
})
|
|
78
|
+
.on("upgrade", (req) => {
|
|
79
|
+
// log all websocket connections
|
|
80
|
+
console.log(
|
|
81
|
+
new Date().toLocaleTimeString(),
|
|
82
|
+
req.method,
|
|
83
|
+
req.url,
|
|
84
|
+
101,
|
|
85
|
+
"(WebSocket)"
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
.on("error", (err) => {
|
|
89
|
+
// log all connection errors
|
|
90
|
+
console.error(new Date().toLocaleTimeString(), err);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* When we're not connected / getting a fresh token on changes,
|
|
96
|
+
* we'd like to buffer streams/requests until we're connected.
|
|
97
|
+
* Once connected, we can flush the buffered streams/requests.
|
|
98
|
+
* streamBufferRef is used to buffer http/2 streams, while
|
|
99
|
+
* requestResponseBufferRef is used to buffer http/1 requests.
|
|
100
|
+
*/
|
|
101
|
+
const streamBufferRef = useRef<
|
|
102
|
+
{ stream: ServerHttp2Stream; headers: IncomingHttpHeaders }[]
|
|
103
|
+
>([]);
|
|
104
|
+
const requestResponseBufferRef = useRef<
|
|
105
|
+
{ request: IncomingMessage; response: ServerResponse }[]
|
|
106
|
+
>([]);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const proxy = proxyServer.current;
|
|
110
|
+
|
|
111
|
+
// If we don't have a token, that means either we're just starting up,
|
|
112
|
+
// or we're refreshing the token.
|
|
113
|
+
if (!previewToken) {
|
|
114
|
+
const cleanupListeners: (() => void)[] = [];
|
|
115
|
+
const bufferStream = (
|
|
116
|
+
stream: ServerHttp2Stream,
|
|
117
|
+
headers: IncomingHttpHeaders
|
|
118
|
+
) => {
|
|
119
|
+
// store the stream in a buffer so we can replay it later
|
|
120
|
+
streamBufferRef.current.push({ stream, headers });
|
|
121
|
+
};
|
|
122
|
+
proxy.on("stream", bufferStream);
|
|
123
|
+
cleanupListeners.push(() => proxy.off("stream", bufferStream));
|
|
124
|
+
|
|
125
|
+
const bufferRequestResponse = (
|
|
126
|
+
request: IncomingMessage,
|
|
127
|
+
response: ServerResponse
|
|
128
|
+
) => {
|
|
129
|
+
// store the request and response in a buffer so we can replay it later
|
|
130
|
+
requestResponseBufferRef.current.push({ request, response });
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
proxy.on("request", bufferRequestResponse);
|
|
134
|
+
cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
|
|
135
|
+
return () => {
|
|
136
|
+
cleanupListeners.forEach((cleanup) => cleanup());
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// We have a token. Let's proxy requests to the preview end point.
|
|
141
|
+
const cleanupListeners: (() => void)[] = [];
|
|
142
|
+
|
|
143
|
+
const assetPath = typeof publicRoot === "string" ? publicRoot : null;
|
|
144
|
+
|
|
145
|
+
// create a ClientHttp2Session
|
|
146
|
+
const remote = connect(`https://${previewToken.host}`);
|
|
147
|
+
cleanupListeners.push(() => remote.destroy());
|
|
148
|
+
|
|
149
|
+
/** HTTP/2 -> HTTP/2 */
|
|
150
|
+
function handleStream(
|
|
151
|
+
stream: ServerHttp2Stream,
|
|
152
|
+
headers: IncomingHttpHeaders
|
|
153
|
+
) {
|
|
154
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
155
|
+
headers[":authority"] = previewToken.host;
|
|
156
|
+
const request = stream.pipe(remote.request(headers));
|
|
157
|
+
request.on("response", (responseHeaders: IncomingHttpHeaders) => {
|
|
158
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
159
|
+
responseHeaders,
|
|
160
|
+
previewToken.host,
|
|
161
|
+
port
|
|
162
|
+
);
|
|
163
|
+
stream.respond(responseHeaders);
|
|
164
|
+
request.pipe(stream, { end: true });
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
proxy.on("stream", handleStream);
|
|
168
|
+
cleanupListeners.push(() => proxy.off("stream", handleStream));
|
|
169
|
+
|
|
170
|
+
// flush and replay buffered streams
|
|
171
|
+
streamBufferRef.current.forEach((buffer) =>
|
|
172
|
+
handleStream(buffer.stream, buffer.headers)
|
|
173
|
+
);
|
|
174
|
+
streamBufferRef.current = [];
|
|
175
|
+
|
|
176
|
+
/** HTTP/1 -> HTTP/2 */
|
|
177
|
+
const handleRequest: RequestListener = (
|
|
178
|
+
message: IncomingMessage,
|
|
179
|
+
response: ServerResponse
|
|
180
|
+
) => {
|
|
181
|
+
const { httpVersionMajor, headers, method, url } = message;
|
|
182
|
+
if (httpVersionMajor >= 2) {
|
|
183
|
+
return; // Already handled by the "stream" event.
|
|
184
|
+
}
|
|
185
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
186
|
+
headers[":method"] = method;
|
|
187
|
+
headers[":path"] = url;
|
|
188
|
+
headers[":authority"] = previewToken.host;
|
|
189
|
+
headers[":scheme"] = "https";
|
|
190
|
+
for (const name of Object.keys(headers)) {
|
|
191
|
+
if (HTTP1_HEADERS.has(name.toLowerCase())) {
|
|
192
|
+
delete headers[name];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const request = message.pipe(remote.request(headers));
|
|
196
|
+
request.on("response", (responseHeaders) => {
|
|
197
|
+
const status = responseHeaders[":status"];
|
|
198
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
199
|
+
responseHeaders,
|
|
200
|
+
previewToken.host,
|
|
201
|
+
port
|
|
202
|
+
);
|
|
203
|
+
for (const name of Object.keys(responseHeaders)) {
|
|
204
|
+
if (name.startsWith(":")) {
|
|
205
|
+
delete responseHeaders[name];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
response.writeHead(status, responseHeaders);
|
|
209
|
+
request.pipe(response, { end: true });
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// If an asset path is defined, check the file system
|
|
214
|
+
// for a file first and serve if it exists.
|
|
215
|
+
const actualHandleRequest = assetPath
|
|
216
|
+
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
217
|
+
: handleRequest;
|
|
218
|
+
|
|
219
|
+
proxy.on("request", actualHandleRequest);
|
|
220
|
+
cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
|
|
221
|
+
|
|
222
|
+
// flush and replay buffered requests
|
|
223
|
+
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
224
|
+
actualHandleRequest(request, response)
|
|
225
|
+
);
|
|
226
|
+
requestResponseBufferRef.current = [];
|
|
227
|
+
|
|
228
|
+
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
229
|
+
const handleUpgrade = (
|
|
230
|
+
message: IncomingMessage,
|
|
231
|
+
socket: WebSocket,
|
|
232
|
+
body: Buffer
|
|
233
|
+
) => {
|
|
234
|
+
const { headers, url } = message;
|
|
235
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
236
|
+
headers["host"] = previewToken.host;
|
|
237
|
+
const localWebsocket = new WebSocket(message, socket, body);
|
|
238
|
+
// TODO(soon): Custom WebSocket protocol is not working?
|
|
239
|
+
const remoteWebsocketClient = new WebSocket.Client(
|
|
240
|
+
`wss://${previewToken.host}${url}`,
|
|
241
|
+
[],
|
|
242
|
+
{ headers }
|
|
243
|
+
);
|
|
244
|
+
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
245
|
+
// We close down websockets whenever we refresh the token.
|
|
246
|
+
cleanupListeners.push(() => {
|
|
247
|
+
localWebsocket.destroy();
|
|
248
|
+
remoteWebsocketClient.destroy();
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
proxy.on("upgrade", handleUpgrade);
|
|
252
|
+
cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
|
|
253
|
+
|
|
254
|
+
return () => {
|
|
255
|
+
cleanupListeners.forEach((d) => d());
|
|
256
|
+
};
|
|
257
|
+
}, [previewToken, publicRoot, port]);
|
|
258
|
+
|
|
259
|
+
// Start/stop the server whenever the
|
|
260
|
+
// containing component is mounted/unmounted.
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
proxyServer.current.listen(port);
|
|
263
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
proxyServer.current.close();
|
|
267
|
+
};
|
|
268
|
+
}, [port]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createHandleAssetsRequest(
|
|
272
|
+
assetPath: string,
|
|
273
|
+
handleRequest: RequestListener
|
|
274
|
+
) {
|
|
275
|
+
const handleAsset = serveStatic(assetPath, {
|
|
276
|
+
cacheControl: false,
|
|
277
|
+
});
|
|
278
|
+
return (request: IncomingMessage, response: ServerResponse) => {
|
|
279
|
+
handleAsset(request, response, () => {
|
|
280
|
+
handleRequest(request, response);
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** A Set of headers we want to remove from HTTP/1 requests. */
|
|
286
|
+
const HTTP1_HEADERS = new Set([
|
|
287
|
+
"host",
|
|
288
|
+
"connection",
|
|
289
|
+
"upgrade",
|
|
290
|
+
"keep-alive",
|
|
291
|
+
"proxy-connection",
|
|
292
|
+
"transfer-encoding",
|
|
293
|
+
"http2-settings",
|
|
294
|
+
]);
|
package/src/publish.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
3
4
|
import esbuild from "esbuild";
|
|
5
|
+
import { execa } from "execa";
|
|
4
6
|
import tmp from "tmp-promise";
|
|
7
|
+
import type { CfWorkerInit } from "./api/worker";
|
|
8
|
+
import { toFormData } from "./api/form_data";
|
|
9
|
+
import { fetchResult } from "./cfetch";
|
|
5
10
|
import type { Config } from "./config";
|
|
6
|
-
import
|
|
7
|
-
import { readFile } from "fs/promises";
|
|
8
|
-
import cfetch from "./cfetch";
|
|
9
|
-
import assert from "node:assert";
|
|
11
|
+
import makeModuleCollector from "./module-collection";
|
|
10
12
|
import { syncAssets } from "./sites";
|
|
11
|
-
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
|
|
12
|
-
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
|
|
13
13
|
|
|
14
|
-
type CfScriptFormat =
|
|
14
|
+
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
15
15
|
|
|
16
16
|
type Props = {
|
|
17
17
|
config: Config;
|
|
@@ -19,14 +19,15 @@ type Props = {
|
|
|
19
19
|
script?: string;
|
|
20
20
|
name?: string;
|
|
21
21
|
env?: string;
|
|
22
|
+
compatibilityDate?: string;
|
|
23
|
+
compatibilityFlags?: string[];
|
|
22
24
|
public?: string;
|
|
23
25
|
site?: string;
|
|
24
26
|
triggers?: (string | number)[];
|
|
25
27
|
routes?: (string | number)[];
|
|
26
28
|
legacyEnv?: boolean;
|
|
27
|
-
jsxFactory:
|
|
28
|
-
jsxFragment:
|
|
29
|
-
polyfillNode: void | boolean;
|
|
29
|
+
jsxFactory: undefined | string;
|
|
30
|
+
jsxFragment: undefined | string;
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
function sleep(ms: number) {
|
|
@@ -49,6 +50,13 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
49
50
|
__path__,
|
|
50
51
|
} = config;
|
|
51
52
|
|
|
53
|
+
const envRootObj = props.env ? config.env[props.env] || {} : config;
|
|
54
|
+
|
|
55
|
+
assert(
|
|
56
|
+
envRootObj.compatibility_date || props["compatibility-date"],
|
|
57
|
+
"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."
|
|
58
|
+
);
|
|
59
|
+
|
|
52
60
|
const triggers = props.triggers || config.triggers?.crons;
|
|
53
61
|
const routes = props.routes || config.routes;
|
|
54
62
|
|
|
@@ -78,6 +86,18 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
78
86
|
|
|
79
87
|
const destination = await tmp.dir({ unsafeCleanup: true });
|
|
80
88
|
|
|
89
|
+
if (props.config.build?.command) {
|
|
90
|
+
// TODO: add a deprecation message here?
|
|
91
|
+
console.log("running:", props.config.build.command);
|
|
92
|
+
const buildCommandPieces = props.config.build.command.split(" ");
|
|
93
|
+
await execa(buildCommandPieces[0], buildCommandPieces.slice(1), {
|
|
94
|
+
stdout: "inherit",
|
|
95
|
+
stderr: "inherit",
|
|
96
|
+
...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const moduleCollector = makeModuleCollector();
|
|
81
101
|
const result = await esbuild.build({
|
|
82
102
|
...(props.public
|
|
83
103
|
? {
|
|
@@ -94,24 +114,17 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
94
114
|
}
|
|
95
115
|
: { entryPoints: [file] }),
|
|
96
116
|
bundle: true,
|
|
97
|
-
define: {
|
|
98
|
-
...((props.polyfillNode ?? config.polyfill_node) && {
|
|
99
|
-
global: "globalThis",
|
|
100
|
-
}),
|
|
101
|
-
},
|
|
102
117
|
nodePaths: props.public ? [path.join(__dirname, "../vendor")] : undefined,
|
|
103
118
|
outdir: destination.path,
|
|
104
119
|
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
105
120
|
format: "esm",
|
|
106
|
-
plugins:
|
|
107
|
-
props.polyfillNode ?? config.polyfill_node
|
|
108
|
-
? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
|
|
109
|
-
: undefined,
|
|
110
121
|
sourcemap: true,
|
|
111
122
|
metafile: true,
|
|
123
|
+
conditions: ["worker", "browser"],
|
|
112
124
|
loader: {
|
|
113
125
|
".js": "jsx",
|
|
114
126
|
},
|
|
127
|
+
plugins: [moduleCollector.plugin],
|
|
115
128
|
...(jsxFactory && { jsxFactory }),
|
|
116
129
|
...(jsxFragment && { jsxFragment }),
|
|
117
130
|
});
|
|
@@ -121,7 +134,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
121
134
|
entryPoint ===
|
|
122
135
|
(props.public
|
|
123
136
|
? path.join(path.dirname(file), "static-asset-facade.js")
|
|
124
|
-
:
|
|
137
|
+
: Object.keys(result.metafile.inputs)[0])
|
|
125
138
|
);
|
|
126
139
|
|
|
127
140
|
const { format } = props;
|
|
@@ -145,16 +158,16 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
const content = await readFile(chunks[0], { encoding: "utf-8" });
|
|
148
|
-
destination.cleanup();
|
|
161
|
+
await destination.cleanup();
|
|
149
162
|
|
|
150
163
|
// if config.migrations
|
|
151
164
|
// get current migration tag
|
|
152
165
|
let migrations;
|
|
153
166
|
if ("migrations" in config) {
|
|
154
|
-
const scripts = await
|
|
167
|
+
const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
|
|
155
168
|
`/accounts/${accountId}/workers/scripts`
|
|
156
169
|
);
|
|
157
|
-
const script = scripts.find((
|
|
170
|
+
const script = scripts.find(({ id }) => id === scriptName);
|
|
158
171
|
if (script?.migration_tag) {
|
|
159
172
|
// was already published once
|
|
160
173
|
const foundIndex = config.migrations.findIndex(
|
|
@@ -162,7 +175,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
162
175
|
);
|
|
163
176
|
if (foundIndex === -1) {
|
|
164
177
|
console.warn(
|
|
165
|
-
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already
|
|
178
|
+
`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...`
|
|
166
179
|
);
|
|
167
180
|
migrations = {
|
|
168
181
|
old_tag: script.migration_tag,
|
|
@@ -196,7 +209,16 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
196
209
|
)
|
|
197
210
|
: { manifest: undefined, namespace: undefined };
|
|
198
211
|
|
|
199
|
-
const
|
|
212
|
+
const bindings: CfWorkerInit["bindings"] = {
|
|
213
|
+
kv_namespaces: envRootObj.kv_namespaces?.concat(
|
|
214
|
+
assets.namespace
|
|
215
|
+
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
216
|
+
: []
|
|
217
|
+
),
|
|
218
|
+
vars: envRootObj.vars,
|
|
219
|
+
durable_objects: envRootObj.durable_objects,
|
|
220
|
+
services: envRootObj.experimental_services,
|
|
221
|
+
};
|
|
200
222
|
|
|
201
223
|
const worker: CfWorkerInit = {
|
|
202
224
|
name: scriptName,
|
|
@@ -205,35 +227,17 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
205
227
|
content: content,
|
|
206
228
|
type: bundle.type === "esm" ? "esm" : "commonjs",
|
|
207
229
|
},
|
|
208
|
-
|
|
209
|
-
...(envRootObj?.vars || {}),
|
|
210
|
-
...(envRootObj?.kv_namespaces || []).reduce(
|
|
211
|
-
(obj, { binding, preview_id: _preview_id, id }) => {
|
|
212
|
-
return { ...obj, [binding]: { namespaceId: id } };
|
|
213
|
-
},
|
|
214
|
-
{}
|
|
215
|
-
),
|
|
216
|
-
...(envRootObj?.durable_objects?.bindings || []).reduce(
|
|
217
|
-
(obj, { name, class_name, script_name }) => {
|
|
218
|
-
return {
|
|
219
|
-
...obj,
|
|
220
|
-
[name]: { class_name, ...(script_name && { script_name }) },
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
{}
|
|
224
|
-
),
|
|
225
|
-
...(assets.namespace
|
|
226
|
-
? { __STATIC_CONTENT: { namespaceId: assets.namespace } }
|
|
227
|
-
: {}),
|
|
228
|
-
},
|
|
230
|
+
bindings,
|
|
229
231
|
...(migrations && { migrations }),
|
|
230
|
-
modules:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
232
|
+
modules: moduleCollector.modules.concat(
|
|
233
|
+
assets.manifest
|
|
234
|
+
? {
|
|
235
|
+
name: "__STATIC_CONTENT_MANIFEST",
|
|
236
|
+
content: JSON.stringify(assets.manifest),
|
|
237
|
+
type: "text",
|
|
238
|
+
}
|
|
239
|
+
: []
|
|
240
|
+
),
|
|
237
241
|
compatibility_date: config.compatibility_date,
|
|
238
242
|
compatibility_flags: config.compatibility_flags,
|
|
239
243
|
usage_model: config.usage_model,
|
|
@@ -250,8 +254,8 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
250
254
|
? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
|
|
251
255
|
: `/accounts/${accountId}/workers/scripts/${scriptName}`;
|
|
252
256
|
|
|
253
|
-
// Upload the script so it has time to
|
|
254
|
-
const { available_on_subdomain } = await
|
|
257
|
+
// Upload the script so it has time to propagate.
|
|
258
|
+
const { available_on_subdomain } = await fetchResult(
|
|
255
259
|
`${workerUrl}?available_on_subdomain=true`,
|
|
256
260
|
{
|
|
257
261
|
method: "PUT",
|
|
@@ -265,7 +269,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
265
269
|
const deployments: Promise<string[]>[] = [];
|
|
266
270
|
|
|
267
271
|
const userSubdomain = (
|
|
268
|
-
await
|
|
272
|
+
await fetchResult<{ subdomain: string }>(
|
|
269
273
|
`/accounts/${accountId}/workers/subdomain`
|
|
270
274
|
)
|
|
271
275
|
).subdomain;
|
|
@@ -279,7 +283,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
279
283
|
// TODO: Make this configurable.
|
|
280
284
|
if (!available_on_subdomain) {
|
|
281
285
|
deployments.push(
|
|
282
|
-
|
|
286
|
+
fetchResult(`${workerUrl}/subdomain`, {
|
|
283
287
|
method: "POST",
|
|
284
288
|
body: JSON.stringify({ enabled: true }),
|
|
285
289
|
headers: {
|
|
@@ -291,8 +295,8 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
291
295
|
// This is to prevent an issue where a negative cache-hit
|
|
292
296
|
// causes the subdomain to be unavailable for 30 seconds.
|
|
293
297
|
// This is a temporary measure until we fix this on the edge.
|
|
294
|
-
.then((url) => {
|
|
295
|
-
sleep(3000);
|
|
298
|
+
.then(async (url) => {
|
|
299
|
+
await sleep(3000);
|
|
296
300
|
return url;
|
|
297
301
|
})
|
|
298
302
|
);
|
|
@@ -303,7 +307,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
303
307
|
// Update routing table for the script.
|
|
304
308
|
if (routes && routes.length) {
|
|
305
309
|
deployments.push(
|
|
306
|
-
|
|
310
|
+
fetchResult(`${workerUrl}/routes`, {
|
|
307
311
|
// TODO: PATCH will not delete previous routes on this script,
|
|
308
312
|
// whereas PUT will. We need to decide on the default behaviour
|
|
309
313
|
// and how to configure it.
|
|
@@ -328,7 +332,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
328
332
|
// TODO: rename this to `schedules`?
|
|
329
333
|
if (triggers && triggers.length) {
|
|
330
334
|
deployments.push(
|
|
331
|
-
|
|
335
|
+
fetchResult(`${workerUrl}/schedules`, {
|
|
332
336
|
// TODO: Unlike routes, this endpoint does not support PATCH.
|
|
333
337
|
// So technically, this will override any previous schedules.
|
|
334
338
|
// We should change the endpoint to support PATCH.
|
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
|
package/src/tail.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
-
import cfetch from "./cfetch";
|
|
3
2
|
import { version as packageVersion } from "../package.json";
|
|
3
|
+
import { fetchResult } from "./cfetch";
|
|
4
4
|
|
|
5
5
|
export type TailApiResponse = {
|
|
6
6
|
id: string;
|
|
@@ -27,7 +27,9 @@ async function createTailButDontConnect(
|
|
|
27
27
|
): Promise<TailApiResponse> {
|
|
28
28
|
const createTailUrl = makeCreateTailUrl(accountId, workerName);
|
|
29
29
|
/// https://api.cloudflare.com/#worker-tail-logs-start-tail
|
|
30
|
-
return await
|
|
30
|
+
return await fetchResult<TailApiResponse>(createTailUrl, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
export async function createTail(
|
|
@@ -48,7 +50,7 @@ export async function createTail(
|
|
|
48
50
|
|
|
49
51
|
// deletes the tail
|
|
50
52
|
async function deleteTail() {
|
|
51
|
-
await
|
|
53
|
+
await fetchResult(deleteUrl, { method: "DELETE" });
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const tail = new WebSocket(websocketUrl, "trace-v1", {
|