wrangler 0.0.0-e043479 → 0.0.0-e0b9366
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 +10 -4
- package/pages/functions/filepath-routing.test.ts +125 -25
- package/pages/functions/filepath-routing.ts +6 -3
- package/src/__tests__/configuration.test.ts +479 -114
- package/src/__tests__/dev.test.tsx +162 -8
- package/src/__tests__/dev2.test.tsx +152 -0
- package/src/__tests__/guess-worker-format.test.ts +9 -17
- package/src/__tests__/helpers/mock-account-id.ts +4 -0
- package/src/__tests__/helpers/mock-cfetch.ts +3 -3
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- 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 +125 -0
- package/src/__tests__/jest.setup.ts +1 -1
- package/src/__tests__/kv.test.ts +27 -53
- package/src/__tests__/logout.test.ts +15 -13
- package/src/__tests__/pages.test.ts +44 -0
- package/src/__tests__/publish.test.ts +504 -32
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/whoami.test.tsx +21 -7
- package/src/bundle.ts +20 -11
- package/src/cfetch/internal.ts +11 -5
- package/src/config/config.ts +53 -44
- package/src/config/validation-helpers.ts +110 -12
- package/src/config/validation.ts +59 -41
- package/src/{api/preview.ts → create-worker-preview.ts} +23 -4
- package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
- package/src/dev/dev.tsx +357 -0
- package/src/dev/local.tsx +241 -0
- package/src/dev/remote.tsx +201 -0
- package/src/dev/use-esbuild.ts +100 -0
- package/src/entry.ts +111 -9
- package/src/https-options.ts +122 -0
- package/src/index.tsx +171 -109
- package/src/inspect.ts +9 -1
- package/src/{kv.tsx → kv.ts} +0 -0
- package/src/module-collection.ts +85 -2
- package/src/open-in-browser.ts +14 -2
- package/src/pages.tsx +1 -1
- package/src/proxy.ts +102 -40
- package/src/publish.ts +41 -50
- package/src/reporting.ts +25 -9
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +51 -16
- package/src/user.tsx +132 -77
- package/src/{api/worker.ts → worker.ts} +0 -23
- package/wrangler-dist/cli.js +5012 -3107
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/dev.tsx +0 -960
- package/src/guess-worker-format.ts +0 -68
package/src/module-collection.ts
CHANGED
|
@@ -2,8 +2,8 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import globToRegExp from "glob-to-regexp";
|
|
5
|
-
import type { CfModule, CfModuleType, CfScriptFormat } from "./api/worker";
|
|
6
5
|
import type { Config, ConfigModuleRuleType } from "./config";
|
|
6
|
+
import type { CfModule, CfModuleType, CfScriptFormat } from "./worker";
|
|
7
7
|
import type esbuild from "esbuild";
|
|
8
8
|
|
|
9
9
|
const RuleTypeToModuleType: Record<ConfigModuleRuleType, CfModuleType> = {
|
|
@@ -28,9 +28,15 @@ export const DEFAULT_MODULE_RULES: Config["rules"] = [
|
|
|
28
28
|
{ type: "CompiledWasm", globs: ["**/*.wasm"] },
|
|
29
29
|
];
|
|
30
30
|
|
|
31
|
-
export default function
|
|
31
|
+
export default function createModuleCollector(props: {
|
|
32
32
|
format: CfScriptFormat;
|
|
33
33
|
rules?: Config["rules"];
|
|
34
|
+
// a collection of "legacy" style module references, which are just file names
|
|
35
|
+
// we will eventually deprecate this functionality, hence the verbose greppable name
|
|
36
|
+
wrangler1xlegacyModuleReferences: {
|
|
37
|
+
rootDirectory: string;
|
|
38
|
+
fileNames: Set<string>;
|
|
39
|
+
};
|
|
34
40
|
}): {
|
|
35
41
|
modules: CfModule[];
|
|
36
42
|
plugin: esbuild.Plugin;
|
|
@@ -41,6 +47,10 @@ export default function makeModuleCollector(props: {
|
|
|
41
47
|
...DEFAULT_MODULE_RULES!,
|
|
42
48
|
];
|
|
43
49
|
|
|
50
|
+
// First, we want to add some validations to the module rules
|
|
51
|
+
// We want to warn if rules are accidentally configured in such a way that
|
|
52
|
+
// subsequent rules will never match because `fallthrough` hasn't been set
|
|
53
|
+
|
|
44
54
|
const completedRuleLocations: Record<string, number> = {};
|
|
45
55
|
let index = 0;
|
|
46
56
|
const rulesToRemove: Config["rules"] = [];
|
|
@@ -92,6 +102,79 @@ export default function makeModuleCollector(props: {
|
|
|
92
102
|
modules.splice(0);
|
|
93
103
|
});
|
|
94
104
|
|
|
105
|
+
// ~ start legacy module specifier support ~
|
|
106
|
+
|
|
107
|
+
// This section detects usage of "legacy" 1.x style module specifiers
|
|
108
|
+
// and modifies them so they "work" in wrangler v2, but with a warning
|
|
109
|
+
|
|
110
|
+
const rulesMatchers = rules.flatMap((rule) => {
|
|
111
|
+
return rule.globs.map((glob) => {
|
|
112
|
+
const regex = globToRegExp(glob);
|
|
113
|
+
return {
|
|
114
|
+
regex,
|
|
115
|
+
rule,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
build.onResolve(
|
|
121
|
+
{
|
|
122
|
+
filter: new RegExp(
|
|
123
|
+
[...props.wrangler1xlegacyModuleReferences.fileNames]
|
|
124
|
+
.map((fileName) => `^${fileName}$`)
|
|
125
|
+
.join("|")
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
async (args: esbuild.OnResolveArgs) => {
|
|
129
|
+
if (
|
|
130
|
+
args.kind !== "import-statement" &&
|
|
131
|
+
args.kind !== "require-call"
|
|
132
|
+
) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// In the future, this will simply throw an error
|
|
136
|
+
console.warn(
|
|
137
|
+
`Deprecation warning: detected a legacy module import in "./${path.relative(
|
|
138
|
+
process.cwd(),
|
|
139
|
+
args.importer
|
|
140
|
+
)}". This will stop working in the future. Replace references to "${
|
|
141
|
+
args.path
|
|
142
|
+
}" with "./${args.path}";`
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// take the file and massage it to a
|
|
146
|
+
// transportable/manageable format
|
|
147
|
+
const filePath = path.join(
|
|
148
|
+
props.wrangler1xlegacyModuleReferences.rootDirectory,
|
|
149
|
+
args.path
|
|
150
|
+
);
|
|
151
|
+
const fileContent = await readFile(filePath);
|
|
152
|
+
const fileHash = crypto
|
|
153
|
+
.createHash("sha1")
|
|
154
|
+
.update(fileContent)
|
|
155
|
+
.digest("hex");
|
|
156
|
+
const fileName = `./${fileHash}-${path.basename(args.path)}`;
|
|
157
|
+
|
|
158
|
+
const { rule } =
|
|
159
|
+
rulesMatchers.find(({ regex }) => regex.test(fileName)) || {};
|
|
160
|
+
if (rule) {
|
|
161
|
+
// add the module to the array
|
|
162
|
+
modules.push({
|
|
163
|
+
name: fileName,
|
|
164
|
+
content: fileContent,
|
|
165
|
+
type: RuleTypeToModuleType[rule.type],
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
path: fileName, // change the reference to the changed module
|
|
169
|
+
external: props.format === "modules", // mark it as external in the bundle
|
|
170
|
+
namespace: `wrangler-module-${rule.type}`, // just a tag, this isn't strictly necessary
|
|
171
|
+
watchFiles: [filePath], // we also add the file to esbuild's watch list
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
// ~ end legacy module specifier support ~
|
|
177
|
+
|
|
95
178
|
rules?.forEach((rule) => {
|
|
96
179
|
if (rule.type === "ESModule" || rule.type === "CommonJS") return; // TODO: we should treat these as js files, and use the jsx loader
|
|
97
180
|
|
package/src/open-in-browser.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import open from "open";
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* An extremely simple wrapper around the open command.
|
|
4
5
|
* Specifically, it adds an 'error' event handler so that when this function
|
|
5
6
|
* is called in environments where we can't open the browser (e.g. github codespaces,
|
|
6
7
|
* stackblitz, remote servers), it doesn't just crash the process.
|
|
8
|
+
*
|
|
9
|
+
* @param url the URL to point the browser at
|
|
10
|
+
* @param options open a chromium-based browser instead of the default
|
|
7
11
|
*/
|
|
8
|
-
export default async function openInBrowser(
|
|
9
|
-
|
|
12
|
+
export default async function openInBrowser(
|
|
13
|
+
url: string,
|
|
14
|
+
{ forceChromium }: { forceChromium: boolean } = { forceChromium: false }
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const options: open.Options | undefined = forceChromium
|
|
17
|
+
? {
|
|
18
|
+
app: [{ name: open.apps.chrome }, { name: open.apps.edge }],
|
|
19
|
+
}
|
|
20
|
+
: undefined;
|
|
21
|
+
const childProcess = await open(url, options);
|
|
10
22
|
childProcess.on("error", () => {
|
|
11
23
|
console.warn(`Failed to open ${url} in a browser`);
|
|
12
24
|
});
|
package/src/pages.tsx
CHANGED
|
@@ -895,7 +895,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
895
895
|
// User bindings
|
|
896
896
|
bindings: {
|
|
897
897
|
...Object.fromEntries(
|
|
898
|
-
bindings.map((binding) => binding.toString().split("="))
|
|
898
|
+
bindings.map((binding) => binding.toString().split("=", 1))
|
|
899
899
|
),
|
|
900
900
|
},
|
|
901
901
|
|
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,31 @@ function rewriteRemoteHostToLocalHostInHeaders(
|
|
|
62
70
|
export function usePreviewServer({
|
|
63
71
|
previewToken,
|
|
64
72
|
publicRoot,
|
|
65
|
-
|
|
73
|
+
localProtocol,
|
|
74
|
+
localPort: port,
|
|
66
75
|
}: {
|
|
67
76
|
previewToken: CfPreviewToken | undefined;
|
|
68
77
|
publicRoot: undefined | string;
|
|
69
|
-
|
|
78
|
+
localProtocol: "https" | "http";
|
|
79
|
+
localPort: number;
|
|
70
80
|
}) {
|
|
71
81
|
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
72
|
-
const proxyServer =
|
|
73
|
-
|
|
82
|
+
const [proxyServer, setProxyServer] = useState<HttpServer | HttpsServer>();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create the instance of the local proxy server that will pass on
|
|
86
|
+
* requests to the preview worker.
|
|
87
|
+
*/
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (proxyServer === undefined) {
|
|
90
|
+
createProxyServer(localProtocol)
|
|
91
|
+
.then((proxy) => setProxyServer(proxy))
|
|
92
|
+
.catch(async (err) => {
|
|
93
|
+
console.error("Failed to create proxy server.", err);
|
|
94
|
+
await reportError(err);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}, [proxyServer, localProtocol]);
|
|
74
98
|
|
|
75
99
|
/**
|
|
76
100
|
* When we're not connected / getting a fresh token on changes,
|
|
@@ -98,6 +122,10 @@ export function usePreviewServer({
|
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
useEffect(() => {
|
|
125
|
+
if (proxyServer === undefined) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
101
129
|
// If we don't have a token, that means either we're just starting up,
|
|
102
130
|
// or we're refreshing the token.
|
|
103
131
|
if (!previewToken) {
|
|
@@ -109,8 +137,8 @@ export function usePreviewServer({
|
|
|
109
137
|
// store the stream in a buffer so we can replay it later
|
|
110
138
|
streamBufferRef.current.push({ stream, headers });
|
|
111
139
|
};
|
|
112
|
-
|
|
113
|
-
cleanupListeners.push(() =>
|
|
140
|
+
proxyServer.on("stream", bufferStream);
|
|
141
|
+
cleanupListeners.push(() => proxyServer.off("stream", bufferStream));
|
|
114
142
|
|
|
115
143
|
const bufferRequestResponse = (
|
|
116
144
|
request: IncomingMessage,
|
|
@@ -120,8 +148,10 @@ export function usePreviewServer({
|
|
|
120
148
|
requestResponseBufferRef.current.push({ request, response });
|
|
121
149
|
};
|
|
122
150
|
|
|
123
|
-
|
|
124
|
-
cleanupListeners.push(() =>
|
|
151
|
+
proxyServer.on("request", bufferRequestResponse);
|
|
152
|
+
cleanupListeners.push(() =>
|
|
153
|
+
proxyServer.off("request", bufferRequestResponse)
|
|
154
|
+
);
|
|
125
155
|
return () => {
|
|
126
156
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
127
157
|
};
|
|
@@ -142,9 +172,14 @@ export function usePreviewServer({
|
|
|
142
172
|
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
143
173
|
|
|
144
174
|
/** HTTP/2 -> HTTP/2 */
|
|
145
|
-
const handleStream = createStreamHandler(
|
|
146
|
-
|
|
147
|
-
|
|
175
|
+
const handleStream = createStreamHandler(
|
|
176
|
+
previewToken,
|
|
177
|
+
remote,
|
|
178
|
+
port,
|
|
179
|
+
localProtocol
|
|
180
|
+
);
|
|
181
|
+
proxyServer.on("stream", handleStream);
|
|
182
|
+
cleanupListeners.push(() => proxyServer.off("stream", handleStream));
|
|
148
183
|
|
|
149
184
|
// flush and replay buffered streams
|
|
150
185
|
streamBufferRef.current.forEach((buffer) =>
|
|
@@ -177,7 +212,8 @@ export function usePreviewServer({
|
|
|
177
212
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
178
213
|
responseHeaders,
|
|
179
214
|
previewToken.host,
|
|
180
|
-
port
|
|
215
|
+
port,
|
|
216
|
+
localProtocol
|
|
181
217
|
);
|
|
182
218
|
for (const name of Object.keys(responseHeaders)) {
|
|
183
219
|
if (name.startsWith(":")) {
|
|
@@ -195,8 +231,10 @@ export function usePreviewServer({
|
|
|
195
231
|
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
196
232
|
: handleRequest;
|
|
197
233
|
|
|
198
|
-
|
|
199
|
-
cleanupListeners.push(() =>
|
|
234
|
+
proxyServer.on("request", actualHandleRequest);
|
|
235
|
+
cleanupListeners.push(() =>
|
|
236
|
+
proxyServer.off("request", actualHandleRequest)
|
|
237
|
+
);
|
|
200
238
|
|
|
201
239
|
// flush and replay buffered requests
|
|
202
240
|
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
@@ -227,8 +265,8 @@ export function usePreviewServer({
|
|
|
227
265
|
remoteWebsocketClient.close();
|
|
228
266
|
});
|
|
229
267
|
};
|
|
230
|
-
|
|
231
|
-
cleanupListeners.push(() =>
|
|
268
|
+
proxyServer.on("upgrade", handleUpgrade);
|
|
269
|
+
cleanupListeners.push(() => proxyServer.off("upgrade", handleUpgrade));
|
|
232
270
|
|
|
233
271
|
return () => {
|
|
234
272
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
@@ -237,7 +275,8 @@ export function usePreviewServer({
|
|
|
237
275
|
previewToken,
|
|
238
276
|
publicRoot,
|
|
239
277
|
port,
|
|
240
|
-
|
|
278
|
+
localProtocol,
|
|
279
|
+
proxyServer,
|
|
241
280
|
// We use a state value as a sigil to trigger reconnecting the server.
|
|
242
281
|
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
243
282
|
// doesn't complain if it's not included in the dependency array.
|
|
@@ -248,19 +287,23 @@ export function usePreviewServer({
|
|
|
248
287
|
// Start/stop the server whenever the
|
|
249
288
|
// containing component is mounted/unmounted.
|
|
250
289
|
useEffect(() => {
|
|
290
|
+
if (proxyServer === undefined) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
251
294
|
waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
|
|
252
295
|
.then(() => {
|
|
253
|
-
|
|
254
|
-
console.log(`⬣ Listening at
|
|
296
|
+
proxyServer.listen(port);
|
|
297
|
+
console.log(`⬣ Listening at ${localProtocol}://localhost:${port}`);
|
|
255
298
|
})
|
|
256
299
|
.catch((err) => {
|
|
257
300
|
console.error(`⬣ Failed to start server: ${err}`);
|
|
258
301
|
});
|
|
259
302
|
|
|
260
303
|
return () => {
|
|
261
|
-
|
|
304
|
+
proxyServer.close();
|
|
262
305
|
};
|
|
263
|
-
}, [port,
|
|
306
|
+
}, [port, proxyServer, localProtocol]);
|
|
264
307
|
}
|
|
265
308
|
|
|
266
309
|
function createHandleAssetsRequest(
|
|
@@ -288,8 +331,15 @@ const HTTP1_HEADERS = new Set([
|
|
|
288
331
|
"http2-settings",
|
|
289
332
|
]);
|
|
290
333
|
|
|
291
|
-
function createProxyServer(
|
|
292
|
-
|
|
334
|
+
async function createProxyServer(
|
|
335
|
+
localProtocol: "https" | "http"
|
|
336
|
+
): Promise<HttpServer | HttpsServer> {
|
|
337
|
+
const server: HttpServer | HttpsServer =
|
|
338
|
+
localProtocol === "https"
|
|
339
|
+
? createHttpsServer(await getHttpsOptions())
|
|
340
|
+
: createHttpServer();
|
|
341
|
+
|
|
342
|
+
return server
|
|
293
343
|
.on("request", function (req, res) {
|
|
294
344
|
// log all requests
|
|
295
345
|
console.log(
|
|
@@ -318,7 +368,8 @@ function createProxyServer() {
|
|
|
318
368
|
function createStreamHandler(
|
|
319
369
|
previewToken: CfPreviewToken,
|
|
320
370
|
remote: ClientHttp2Session,
|
|
321
|
-
|
|
371
|
+
localPort: number,
|
|
372
|
+
localProtocol: "https" | "http"
|
|
322
373
|
) {
|
|
323
374
|
return function handleStream(
|
|
324
375
|
stream: ServerHttp2Stream,
|
|
@@ -331,7 +382,8 @@ function createStreamHandler(
|
|
|
331
382
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
332
383
|
responseHeaders,
|
|
333
384
|
previewToken.host,
|
|
334
|
-
|
|
385
|
+
localPort,
|
|
386
|
+
localProtocol
|
|
335
387
|
);
|
|
336
388
|
stream.respond(responseHeaders);
|
|
337
389
|
request.pipe(stream, { end: true });
|
|
@@ -348,29 +400,39 @@ export async function waitForPortToBeAvailable(
|
|
|
348
400
|
): Promise<void> {
|
|
349
401
|
return new Promise((resolve, reject) => {
|
|
350
402
|
const timeout = setTimeout(() => {
|
|
351
|
-
|
|
403
|
+
doReject(new Error(`Timed out waiting for port ${port}`));
|
|
352
404
|
}, options.timeout);
|
|
353
405
|
|
|
406
|
+
const interval = setInterval(checkPort, options.retryPeriod);
|
|
407
|
+
checkPort();
|
|
408
|
+
|
|
409
|
+
function doResolve() {
|
|
410
|
+
clearTimeout(timeout);
|
|
411
|
+
clearInterval(interval);
|
|
412
|
+
resolve();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function doReject(err: unknown) {
|
|
416
|
+
clearInterval(interval);
|
|
417
|
+
clearTimeout(timeout);
|
|
418
|
+
reject(err);
|
|
419
|
+
}
|
|
420
|
+
|
|
354
421
|
function checkPort() {
|
|
355
422
|
// Testing whether a port is 'available' involves simply
|
|
356
423
|
// trying to make a server listen on that port, and retrying
|
|
357
424
|
// until it succeeds.
|
|
358
|
-
const server =
|
|
425
|
+
const server = createHttpServer();
|
|
359
426
|
server.on("error", (err) => {
|
|
360
427
|
// @ts-expect-error non standard property on Error
|
|
361
|
-
if (err.code
|
|
362
|
-
|
|
363
|
-
} else {
|
|
364
|
-
reject(err);
|
|
428
|
+
if (err.code !== "EADDRINUSE") {
|
|
429
|
+
doReject(err);
|
|
365
430
|
}
|
|
366
431
|
});
|
|
367
432
|
server.listen(port, () => {
|
|
368
433
|
server.close();
|
|
369
|
-
|
|
370
|
-
resolve();
|
|
434
|
+
doResolve();
|
|
371
435
|
});
|
|
372
436
|
}
|
|
373
|
-
|
|
374
|
-
checkPort();
|
|
375
437
|
});
|
|
376
438
|
}
|
package/src/publish.ts
CHANGED
|
@@ -2,23 +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
17
|
accountId: string;
|
|
20
|
-
|
|
21
|
-
entry: { file: string; directory: string };
|
|
18
|
+
entry: Entry;
|
|
22
19
|
rules: Config["rules"];
|
|
23
20
|
name: string | undefined;
|
|
24
21
|
env: string | undefined;
|
|
@@ -45,7 +42,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
45
42
|
const envRootObj = (props.env && config.env[props.env]) || config;
|
|
46
43
|
|
|
47
44
|
assert(
|
|
48
|
-
|
|
45
|
+
props.compatibilityDate || envRootObj.compatibility_date,
|
|
49
46
|
"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."
|
|
50
47
|
);
|
|
51
48
|
|
|
@@ -56,7 +53,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
56
53
|
(envRootObj.route ? [envRootObj.route] : []) ??
|
|
57
54
|
[];
|
|
58
55
|
|
|
59
|
-
const
|
|
56
|
+
const deployToWorkersDev = envRootObj.workers_dev;
|
|
60
57
|
|
|
61
58
|
const jsxFactory = props.jsxFactory || envRootObj.jsx_factory;
|
|
62
59
|
const jsxFragment = props.jsxFragment || envRootObj.jsx_fragment;
|
|
@@ -67,14 +64,6 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
67
64
|
'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>"`'
|
|
68
65
|
);
|
|
69
66
|
|
|
70
|
-
if (config.site?.["entry-point"]) {
|
|
71
|
-
console.warn(
|
|
72
|
-
"Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
|
|
73
|
-
"The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `main` config field.\n" +
|
|
74
|
-
"Please remove the `site.entry-point` field from the `wrangler.toml` file."
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
67
|
assert(
|
|
79
68
|
!config.site || config.site.bucket,
|
|
80
69
|
"A [site] definition requires a `bucket` field with a path to the site's public directory."
|
|
@@ -84,28 +73,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
84
73
|
try {
|
|
85
74
|
const envName = props.env ?? "production";
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
// TODO: add a deprecation message here?
|
|
89
|
-
console.log("running:", config.build.command);
|
|
90
|
-
await execaCommand(config.build.command, {
|
|
91
|
-
shell: true,
|
|
92
|
-
stdout: "inherit",
|
|
93
|
-
stderr: "inherit",
|
|
94
|
-
timeout: 1000 * 30,
|
|
95
|
-
...(config.build.cwd && { cwd: config.build.cwd }),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (fileExists(props.entry.file) === false) {
|
|
99
|
-
throw new Error(
|
|
100
|
-
`Could not resolve "${path.relative(
|
|
101
|
-
process.cwd(),
|
|
102
|
-
props.entry.file
|
|
103
|
-
)}".`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const format = await guessWorkerFormat(props.entry, props.format);
|
|
76
|
+
const { format } = props.entry;
|
|
109
77
|
|
|
110
78
|
if (props.experimentalPublic && format === "service-worker") {
|
|
111
79
|
// TODO: check config too
|
|
@@ -133,7 +101,6 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
133
101
|
serveAssetsFromWorker: props.experimentalPublic,
|
|
134
102
|
jsxFactory,
|
|
135
103
|
jsxFragment,
|
|
136
|
-
format,
|
|
137
104
|
rules: props.rules,
|
|
138
105
|
}
|
|
139
106
|
);
|
|
@@ -230,9 +197,11 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
230
197
|
bindings,
|
|
231
198
|
migrations,
|
|
232
199
|
modules,
|
|
233
|
-
compatibility_date:
|
|
234
|
-
|
|
235
|
-
|
|
200
|
+
compatibility_date:
|
|
201
|
+
props.compatibilityDate ?? envRootObj.compatibility_date,
|
|
202
|
+
compatibility_flags:
|
|
203
|
+
props.compatibilityFlags ?? envRootObj.compatibility_flags,
|
|
204
|
+
usage_model: envRootObj.usage_model,
|
|
236
205
|
};
|
|
237
206
|
|
|
238
207
|
const start = Date.now();
|
|
@@ -247,22 +216,17 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
247
216
|
workerUrl,
|
|
248
217
|
{
|
|
249
218
|
method: "PUT",
|
|
250
|
-
body:
|
|
219
|
+
body: createWorkerUploadForm(worker),
|
|
251
220
|
},
|
|
252
221
|
new URLSearchParams({ available_on_subdomain: "true" })
|
|
253
222
|
);
|
|
254
223
|
|
|
255
224
|
const uploadMs = Date.now() - start;
|
|
256
|
-
console.log("Uploaded", workerName, formatTime(uploadMs));
|
|
257
225
|
const deployments: Promise<string[]>[] = [];
|
|
258
226
|
|
|
259
227
|
if (deployToWorkersDev) {
|
|
260
228
|
// Deploy to a subdomain of `workers.dev`
|
|
261
|
-
const userSubdomain = (
|
|
262
|
-
await fetchResult<{ subdomain: string }>(
|
|
263
|
-
`/accounts/${accountId}/workers/subdomain`
|
|
264
|
-
)
|
|
265
|
-
).subdomain;
|
|
229
|
+
const userSubdomain = await getSubdomain(accountId);
|
|
266
230
|
const scriptURL =
|
|
267
231
|
props.legacyEnv || !props.env
|
|
268
232
|
? `${scriptName}.${userSubdomain}.workers.dev`
|
|
@@ -301,6 +265,8 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
301
265
|
});
|
|
302
266
|
}
|
|
303
267
|
|
|
268
|
+
console.log("Uploaded", workerName, formatTime(uploadMs));
|
|
269
|
+
|
|
304
270
|
// Update routing table for the script.
|
|
305
271
|
if (routes.length > 0) {
|
|
306
272
|
deployments.push(
|
|
@@ -361,3 +327,28 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
361
327
|
function formatTime(duration: number) {
|
|
362
328
|
return `(${(duration / 1000).toFixed(2)} sec)`;
|
|
363
329
|
}
|
|
330
|
+
|
|
331
|
+
async function getSubdomain(accountId: string): Promise<string> {
|
|
332
|
+
try {
|
|
333
|
+
const { subdomain } = await fetchResult(
|
|
334
|
+
`/accounts/${accountId}/workers/subdomain`
|
|
335
|
+
);
|
|
336
|
+
return subdomain;
|
|
337
|
+
} catch (e) {
|
|
338
|
+
const error = e as { code?: number };
|
|
339
|
+
if (typeof error === "object" && !!error && error.code === 10007) {
|
|
340
|
+
// 10007 error code: not found
|
|
341
|
+
// https://api.cloudflare.com/#worker-subdomain-get-subdomain
|
|
342
|
+
|
|
343
|
+
const errorMessage =
|
|
344
|
+
"Error: You need to register a workers.dev subdomain before publishing to workers.dev";
|
|
345
|
+
const solutionMessage =
|
|
346
|
+
"You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
|
|
347
|
+
const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
|
|
348
|
+
|
|
349
|
+
throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
|
|
350
|
+
} else {
|
|
351
|
+
throw e;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
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,
|