wrangler 2.0.6 → 2.0.7
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 +5 -3
- package/pages/functions/buildPlugin.ts +13 -0
- package/pages/functions/buildWorker.ts +13 -0
- package/src/__tests__/configuration.test.ts +14 -3
- package/src/__tests__/dev.test.tsx +11 -2
- package/src/__tests__/index.test.ts +25 -10
- package/src/__tests__/init.test.ts +61 -20
- package/src/__tests__/kv.test.ts +8 -8
- package/src/__tests__/pages.test.ts +339 -32
- package/src/__tests__/parse.test.ts +5 -1
- package/src/__tests__/publish.test.ts +184 -69
- package/src/abort.d.ts +3 -0
- package/src/cfetch/index.ts +4 -2
- package/src/cfetch/internal.ts +8 -9
- package/src/config/index.ts +162 -0
- package/src/config/validation.ts +24 -2
- package/src/create-worker-preview.ts +17 -7
- package/src/dev/dev.tsx +5 -4
- package/src/dev/remote.tsx +15 -1
- package/src/durable.ts +102 -0
- package/src/index.tsx +111 -41
- package/src/inspect.ts +39 -0
- package/src/kv.ts +74 -25
- package/src/open-in-browser.ts +5 -12
- package/src/pages.tsx +203 -61
- package/src/parse.ts +21 -4
- package/src/proxy.ts +38 -22
- package/src/publish.ts +18 -93
- package/src/sites.tsx +2 -7
- package/templates/new-worker.ts +16 -1
- package/wrangler-dist/cli.js +32779 -20005
package/src/parse.ts
CHANGED
|
@@ -62,8 +62,6 @@ export class ParseError extends Error implements Message {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
66
|
-
|
|
67
65
|
const TOML_ERROR_NAME = "TomlError";
|
|
68
66
|
const TOML_ERROR_SUFFIX = " at row ";
|
|
69
67
|
|
|
@@ -78,7 +76,7 @@ type TomlError = Error & {
|
|
|
78
76
|
export function parseTOML(input: string, file?: string): TOML.JsonMap | never {
|
|
79
77
|
try {
|
|
80
78
|
// Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
|
|
81
|
-
const normalizedInput = input.replace(/\r\n
|
|
79
|
+
const normalizedInput = input.replace(/\r\n/g, "\n");
|
|
82
80
|
return TOML.parse(normalizedInput);
|
|
83
81
|
} catch (err) {
|
|
84
82
|
const { name, message, line, col } = err as TomlError;
|
|
@@ -100,10 +98,29 @@ export function parseTOML(input: string, file?: string): TOML.JsonMap | never {
|
|
|
100
98
|
|
|
101
99
|
const JSON_ERROR_SUFFIX = " in JSON at position ";
|
|
102
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A minimal type describing a package.json file.
|
|
103
|
+
*/
|
|
104
|
+
export type PackageJSON = {
|
|
105
|
+
devDependencies?: Record<string, unknown>;
|
|
106
|
+
dependencies?: Record<string, unknown>;
|
|
107
|
+
scripts?: Record<string, unknown>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A typed version of `parseJSON()`.
|
|
112
|
+
*/
|
|
113
|
+
export function parsePackageJSON<T extends PackageJSON = PackageJSON>(
|
|
114
|
+
input: string,
|
|
115
|
+
file?: string
|
|
116
|
+
): T {
|
|
117
|
+
return parseJSON<T>(input, file);
|
|
118
|
+
}
|
|
119
|
+
|
|
103
120
|
/**
|
|
104
121
|
* A wrapper around `JSON.parse` that throws a `ParseError`.
|
|
105
122
|
*/
|
|
106
|
-
export function parseJSON(input: string, file?: string):
|
|
123
|
+
export function parseJSON<T>(input: string, file?: string): T {
|
|
107
124
|
try {
|
|
108
125
|
return JSON.parse(input);
|
|
109
126
|
} catch (err) {
|
package/src/proxy.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { createServer as createHttpServer } from "node:http";
|
|
|
2
2
|
import { connect } from "node:http2";
|
|
3
3
|
import { createServer as createHttpsServer } from "node:https";
|
|
4
4
|
import WebSocket from "faye-websocket";
|
|
5
|
+
import { createHttpTerminator } from "http-terminator";
|
|
5
6
|
import { useEffect, useRef, useState } from "react";
|
|
6
7
|
import serveStatic from "serve-static";
|
|
7
8
|
import { getHttpsOptions } from "./https-options";
|
|
8
9
|
import { logger } from "./logger";
|
|
9
10
|
import type { CfPreviewToken } from "./create-worker-preview";
|
|
11
|
+
import type { HttpTerminator } from "http-terminator";
|
|
10
12
|
import type {
|
|
11
13
|
IncomingHttpHeaders,
|
|
12
14
|
RequestListener,
|
|
@@ -67,6 +69,11 @@ function rewriteRemoteHostToLocalHostInHeaders(
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
type PreviewProxy = {
|
|
73
|
+
server: HttpServer | HttpsServer;
|
|
74
|
+
terminator: HttpTerminator;
|
|
75
|
+
};
|
|
76
|
+
|
|
70
77
|
export function usePreviewServer({
|
|
71
78
|
previewToken,
|
|
72
79
|
publicRoot,
|
|
@@ -81,21 +88,26 @@ export function usePreviewServer({
|
|
|
81
88
|
ip: string;
|
|
82
89
|
}) {
|
|
83
90
|
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
84
|
-
const [
|
|
91
|
+
const [proxy, setProxy] = useState<PreviewProxy>();
|
|
85
92
|
|
|
86
93
|
/**
|
|
87
94
|
* Create the instance of the local proxy server that will pass on
|
|
88
95
|
* requests to the preview worker.
|
|
89
96
|
*/
|
|
90
97
|
useEffect(() => {
|
|
91
|
-
if (
|
|
98
|
+
if (proxy === undefined) {
|
|
92
99
|
createProxyServer(localProtocol)
|
|
93
|
-
.then((
|
|
100
|
+
.then((server) => {
|
|
101
|
+
setProxy({
|
|
102
|
+
server,
|
|
103
|
+
terminator: createHttpTerminator({ server }),
|
|
104
|
+
});
|
|
105
|
+
})
|
|
94
106
|
.catch(async (err) => {
|
|
95
107
|
logger.error("Failed to create proxy server:", err);
|
|
96
108
|
});
|
|
97
109
|
}
|
|
98
|
-
}, [
|
|
110
|
+
}, [proxy, localProtocol]);
|
|
99
111
|
|
|
100
112
|
/**
|
|
101
113
|
* When we're not connected / getting a fresh token on changes,
|
|
@@ -123,7 +135,7 @@ export function usePreviewServer({
|
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
useEffect(() => {
|
|
126
|
-
if (
|
|
138
|
+
if (proxy === undefined) {
|
|
127
139
|
return;
|
|
128
140
|
}
|
|
129
141
|
|
|
@@ -138,8 +150,8 @@ export function usePreviewServer({
|
|
|
138
150
|
// store the stream in a buffer so we can replay it later
|
|
139
151
|
streamBufferRef.current.push({ stream, headers });
|
|
140
152
|
};
|
|
141
|
-
|
|
142
|
-
cleanupListeners.push(() =>
|
|
153
|
+
proxy.server.on("stream", bufferStream);
|
|
154
|
+
cleanupListeners.push(() => proxy.server.off("stream", bufferStream));
|
|
143
155
|
|
|
144
156
|
const bufferRequestResponse = (
|
|
145
157
|
request: IncomingMessage,
|
|
@@ -149,9 +161,9 @@ export function usePreviewServer({
|
|
|
149
161
|
requestResponseBufferRef.current.push({ request, response });
|
|
150
162
|
};
|
|
151
163
|
|
|
152
|
-
|
|
164
|
+
proxy.server.on("request", bufferRequestResponse);
|
|
153
165
|
cleanupListeners.push(() =>
|
|
154
|
-
|
|
166
|
+
proxy.server.off("request", bufferRequestResponse)
|
|
155
167
|
);
|
|
156
168
|
return () => {
|
|
157
169
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
@@ -179,8 +191,8 @@ export function usePreviewServer({
|
|
|
179
191
|
port,
|
|
180
192
|
localProtocol
|
|
181
193
|
);
|
|
182
|
-
|
|
183
|
-
cleanupListeners.push(() =>
|
|
194
|
+
proxy.server.on("stream", handleStream);
|
|
195
|
+
cleanupListeners.push(() => proxy.server.off("stream", handleStream));
|
|
184
196
|
|
|
185
197
|
// flush and replay buffered streams
|
|
186
198
|
streamBufferRef.current.forEach((buffer) =>
|
|
@@ -236,9 +248,9 @@ export function usePreviewServer({
|
|
|
236
248
|
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
237
249
|
: handleRequest;
|
|
238
250
|
|
|
239
|
-
|
|
251
|
+
proxy.server.on("request", actualHandleRequest);
|
|
240
252
|
cleanupListeners.push(() =>
|
|
241
|
-
|
|
253
|
+
proxy.server.off("request", actualHandleRequest)
|
|
242
254
|
);
|
|
243
255
|
|
|
244
256
|
// flush and replay buffered requests
|
|
@@ -270,8 +282,8 @@ export function usePreviewServer({
|
|
|
270
282
|
remoteWebsocketClient.close();
|
|
271
283
|
});
|
|
272
284
|
};
|
|
273
|
-
|
|
274
|
-
cleanupListeners.push(() =>
|
|
285
|
+
proxy.server.on("upgrade", handleUpgrade);
|
|
286
|
+
cleanupListeners.push(() => proxy.server.off("upgrade", handleUpgrade));
|
|
275
287
|
|
|
276
288
|
return () => {
|
|
277
289
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
@@ -281,7 +293,7 @@ export function usePreviewServer({
|
|
|
281
293
|
publicRoot,
|
|
282
294
|
port,
|
|
283
295
|
localProtocol,
|
|
284
|
-
|
|
296
|
+
proxy,
|
|
285
297
|
// We use a state value as a sigil to trigger reconnecting the server.
|
|
286
298
|
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
287
299
|
// doesn't complain if it's not included in the dependency array.
|
|
@@ -293,7 +305,7 @@ export function usePreviewServer({
|
|
|
293
305
|
// containing component is mounted/unmounted.
|
|
294
306
|
useEffect(() => {
|
|
295
307
|
const abortController = new AbortController();
|
|
296
|
-
if (
|
|
308
|
+
if (proxy === undefined) {
|
|
297
309
|
return;
|
|
298
310
|
}
|
|
299
311
|
|
|
@@ -303,8 +315,10 @@ export function usePreviewServer({
|
|
|
303
315
|
abortSignal: abortController.signal,
|
|
304
316
|
})
|
|
305
317
|
.then(() => {
|
|
306
|
-
|
|
307
|
-
|
|
318
|
+
proxy.server.on("listening", () => {
|
|
319
|
+
logger.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
|
|
320
|
+
});
|
|
321
|
+
proxy.server.listen(port, ip);
|
|
308
322
|
})
|
|
309
323
|
.catch((err) => {
|
|
310
324
|
if ((err as { code: string }).code !== "ABORT_ERR") {
|
|
@@ -313,10 +327,12 @@ export function usePreviewServer({
|
|
|
313
327
|
});
|
|
314
328
|
|
|
315
329
|
return () => {
|
|
316
|
-
proxyServer.close();
|
|
317
330
|
abortController.abort();
|
|
331
|
+
// Running `proxy.server.close()` does not close open connections, preventing the process from exiting.
|
|
332
|
+
// So we use this `terminator` to close all the connections and force the server to shutdown.
|
|
333
|
+
proxy.terminator.terminate();
|
|
318
334
|
};
|
|
319
|
-
}, [port, ip,
|
|
335
|
+
}, [port, ip, proxy, localProtocol]);
|
|
320
336
|
}
|
|
321
337
|
|
|
322
338
|
function createHandleAssetsRequest(
|
|
@@ -404,7 +420,7 @@ export async function waitForPortToBeAvailable(
|
|
|
404
420
|
): Promise<void> {
|
|
405
421
|
return new Promise((resolve, reject) => {
|
|
406
422
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
407
|
-
|
|
423
|
+
options.abortSignal.addEventListener("abort", () => {
|
|
408
424
|
const abortError = new Error("waitForPortToBeAvailable() aborted");
|
|
409
425
|
(abortError as Error & { code: string }).code = "ABORT_ERR";
|
|
410
426
|
doReject(abortError);
|
package/src/publish.ts
CHANGED
|
@@ -5,8 +5,10 @@ import { URLSearchParams } from "node:url";
|
|
|
5
5
|
import tmp from "tmp-promise";
|
|
6
6
|
import { bundleWorker } from "./bundle";
|
|
7
7
|
import { fetchResult } from "./cfetch";
|
|
8
|
+
import { printBindings } from "./config";
|
|
8
9
|
import { createWorkerUploadForm } from "./create-worker-upload-form";
|
|
9
10
|
import { confirm } from "./dialogs";
|
|
11
|
+
import { getMigrationsToUpload } from "./durable";
|
|
10
12
|
import { logger } from "./logger";
|
|
11
13
|
import { syncAssets } from "./sites";
|
|
12
14
|
import type { Config } from "./config";
|
|
@@ -337,103 +339,19 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
337
339
|
}
|
|
338
340
|
);
|
|
339
341
|
|
|
340
|
-
// Some validation of durable objects + migrations
|
|
341
|
-
if (config.durable_objects.bindings.length > 0) {
|
|
342
|
-
// intrinsic [durable_objects] implies [migrations]
|
|
343
|
-
const exportedDurableObjects = config.durable_objects.bindings.filter(
|
|
344
|
-
(binding) => !binding.script_name
|
|
345
|
-
);
|
|
346
|
-
if (exportedDurableObjects.length > 0 && config.migrations.length === 0) {
|
|
347
|
-
logger.warn(
|
|
348
|
-
`In wrangler.toml, you have configured [durable_objects] exported by this Worker (${exportedDurableObjects.map(
|
|
349
|
-
(durable) => durable.class_name
|
|
350
|
-
)}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
342
|
const content = readFileSync(resolvedEntryPointPath, {
|
|
356
343
|
encoding: "utf-8",
|
|
357
344
|
});
|
|
358
345
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const scriptData = await fetchResult<{
|
|
369
|
-
script: ScriptData;
|
|
370
|
-
}>(
|
|
371
|
-
`/accounts/${accountId}/workers/services/${scriptName}/environments/${props.env}`
|
|
372
|
-
);
|
|
373
|
-
script = scriptData.script;
|
|
374
|
-
} else {
|
|
375
|
-
const scriptData = await fetchResult<{
|
|
376
|
-
default_environment: {
|
|
377
|
-
script: ScriptData;
|
|
378
|
-
};
|
|
379
|
-
}>(`/accounts/${accountId}/workers/services/${scriptName}`);
|
|
380
|
-
script = scriptData.default_environment.script;
|
|
381
|
-
}
|
|
382
|
-
} catch (err) {
|
|
383
|
-
if (
|
|
384
|
-
![
|
|
385
|
-
10090, // corresponds to workers.api.error.service_not_found, so the script wasn't previously published at all
|
|
386
|
-
10092, // workers.api.error.environment_not_found, so the script wasn't published to this environment yet
|
|
387
|
-
].includes((err as { code: number }).code)
|
|
388
|
-
) {
|
|
389
|
-
throw err;
|
|
390
|
-
}
|
|
391
|
-
// else it's a 404, no script found, and we can proceed
|
|
392
|
-
}
|
|
393
|
-
} else {
|
|
394
|
-
const scripts = await fetchResult<ScriptData[]>(
|
|
395
|
-
`/accounts/${accountId}/workers/scripts`
|
|
396
|
-
);
|
|
397
|
-
script = scripts.find(({ id }) => id === scriptName);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (script?.migration_tag) {
|
|
401
|
-
// was already published once
|
|
402
|
-
const scriptMigrationTag = script.migration_tag;
|
|
403
|
-
const foundIndex = config.migrations.findIndex(
|
|
404
|
-
(migration) => migration.tag === scriptMigrationTag
|
|
405
|
-
);
|
|
406
|
-
if (foundIndex === -1) {
|
|
407
|
-
logger.warn(
|
|
408
|
-
`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...`
|
|
409
|
-
);
|
|
410
|
-
migrations = {
|
|
411
|
-
old_tag: script.migration_tag,
|
|
412
|
-
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
413
|
-
steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
|
|
414
|
-
};
|
|
415
|
-
} else {
|
|
416
|
-
if (foundIndex !== config.migrations.length - 1) {
|
|
417
|
-
// there are new migrations to send up
|
|
418
|
-
migrations = {
|
|
419
|
-
old_tag: script.migration_tag,
|
|
420
|
-
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
421
|
-
steps: config.migrations
|
|
422
|
-
.slice(foundIndex + 1)
|
|
423
|
-
.map(({ tag: _tag, ...rest }) => rest),
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
// else, we're up to date, no migrations to send
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
// first time publishing durable objects to this script,
|
|
430
|
-
// so we send all the migrations
|
|
431
|
-
migrations = {
|
|
432
|
-
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
433
|
-
steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
}
|
|
346
|
+
// durable object migrations
|
|
347
|
+
const migrations = !props.dryRun
|
|
348
|
+
? await getMigrationsToUpload(scriptName, {
|
|
349
|
+
accountId,
|
|
350
|
+
config,
|
|
351
|
+
legacyEnv: props.legacyEnv,
|
|
352
|
+
env: props.env,
|
|
353
|
+
})
|
|
354
|
+
: undefined;
|
|
437
355
|
|
|
438
356
|
const assets = await syncAssets(
|
|
439
357
|
accountId,
|
|
@@ -493,6 +411,13 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
493
411
|
usage_model: config.usage_model,
|
|
494
412
|
};
|
|
495
413
|
|
|
414
|
+
const withoutStaticAssets = {
|
|
415
|
+
...bindings,
|
|
416
|
+
kv_namespaces: config.kv_namespaces,
|
|
417
|
+
text_blobs: config.text_blobs,
|
|
418
|
+
};
|
|
419
|
+
printBindings(withoutStaticAssets);
|
|
420
|
+
|
|
496
421
|
if (!props.dryRun) {
|
|
497
422
|
// Upload the script so it has time to propagate.
|
|
498
423
|
// We can also now tell whether available_on_subdomain is set
|
package/src/sites.tsx
CHANGED
|
@@ -187,14 +187,9 @@ export async function syncAssets(
|
|
|
187
187
|
|
|
188
188
|
await Promise.all([
|
|
189
189
|
// upload all the new assets
|
|
190
|
-
putKVBulkKeyValue(accountId, namespace, toUpload
|
|
190
|
+
putKVBulkKeyValue(accountId, namespace, toUpload),
|
|
191
191
|
// delete all the unused assets
|
|
192
|
-
deleteKVBulkKeyValue(
|
|
193
|
-
accountId,
|
|
194
|
-
namespace,
|
|
195
|
-
Array.from(namespaceKeys),
|
|
196
|
-
() => {}
|
|
197
|
-
),
|
|
192
|
+
deleteKVBulkKeyValue(accountId, namespace, Array.from(namespaceKeys)),
|
|
198
193
|
]);
|
|
199
194
|
|
|
200
195
|
logger.log("↗️ Done syncing assets");
|
package/templates/new-worker.ts
CHANGED
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
* Learn more at https://developers.cloudflare.com/workers/
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
export interface Env {
|
|
12
|
+
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
|
|
13
|
+
// MY_KV_NAMESPACE: KVNamespace;
|
|
14
|
+
//
|
|
15
|
+
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
|
|
16
|
+
// MY_DURABLE_OBJECT: DurableObjectNamespace;
|
|
17
|
+
//
|
|
18
|
+
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
|
|
19
|
+
// MY_BUCKET: R2Bucket;
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
export default {
|
|
12
|
-
async fetch(
|
|
23
|
+
async fetch(
|
|
24
|
+
request: Request,
|
|
25
|
+
env: Env,
|
|
26
|
+
ctx: ExecutionContext
|
|
27
|
+
): Promise<Response> {
|
|
13
28
|
return new Response("Hello World!");
|
|
14
29
|
},
|
|
15
30
|
};
|