wrangler 2.1.14 → 2.2.0
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/miniflare-dist/index.mjs +3 -1
- package/package.json +2 -1
- package/src/__tests__/access.test.ts +25 -0
- package/src/__tests__/api-dev.test.ts +1 -1
- package/src/__tests__/api-devregistry.test.js +2 -2
- package/src/__tests__/configuration.test.ts +119 -2
- package/src/__tests__/d1.test.ts +2 -0
- package/src/__tests__/deployments.test.ts +22 -22
- package/src/__tests__/dev.test.tsx +167 -15
- package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
- package/src/__tests__/helpers/msw/handlers/deployments.ts +22 -43
- package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
- package/src/__tests__/helpers/msw/index.ts +4 -0
- package/src/__tests__/index.test.ts +42 -33
- package/src/__tests__/init.test.ts +88 -4
- package/src/__tests__/jest.setup.ts +11 -0
- package/src/__tests__/kv.test.ts +400 -400
- package/src/__tests__/pages.test.ts +140 -28
- package/src/__tests__/publish.test.ts +1161 -647
- package/src/__tests__/pubsub.test.ts +3 -0
- package/src/__tests__/queues.test.ts +371 -0
- package/src/__tests__/r2.test.ts +57 -52
- package/src/__tests__/worker-namespace.test.ts +15 -10
- package/src/bundle-reporter.tsx +41 -2
- package/src/bundle.ts +59 -30
- package/src/cli.ts +0 -1
- package/src/config/environment.ts +50 -0
- package/src/config/index.ts +41 -0
- package/src/config/validation.ts +173 -0
- package/src/create-worker-preview.ts +10 -3
- package/src/create-worker-upload-form.ts +12 -0
- package/src/d1/backups.tsx +11 -5
- package/src/d1/execute.tsx +52 -47
- package/src/d1/index.ts +2 -1
- package/src/delete.ts +7 -10
- package/src/deployments.ts +73 -0
- package/src/deprecated/index.ts +9 -24
- package/src/dev/dev-vars.ts +11 -8
- package/src/dev/dev.tsx +12 -0
- package/src/dev/local.tsx +26 -0
- package/src/dev/remote.tsx +2 -0
- package/src/dev/start-server.ts +7 -0
- package/src/dev/use-esbuild.ts +12 -5
- package/src/dev.tsx +12 -9
- package/src/dispatch-namespace.ts +4 -3
- package/src/index.tsx +61 -45
- package/src/init.ts +4 -4
- package/src/inspect.ts +21 -1
- package/src/is-interactive.ts +4 -0
- package/src/kv/index.ts +5 -54
- package/src/logger.ts +12 -0
- package/src/pages/constants.ts +2 -0
- package/src/pages/upload.tsx +42 -15
- package/src/proxy.ts +38 -6
- package/src/publish/index.ts +11 -8
- package/src/publish/publish.ts +151 -30
- package/src/pubsub/pubsub-commands.tsx +3 -2
- package/src/queues/cli/commands/consumer/add.ts +71 -0
- package/src/queues/cli/commands/consumer/index.ts +22 -0
- package/src/queues/cli/commands/consumer/remove.ts +38 -0
- package/src/queues/cli/commands/create.ts +25 -0
- package/src/queues/cli/commands/delete.ts +26 -0
- package/src/queues/cli/commands/index.ts +33 -0
- package/src/queues/cli/commands/list.ts +25 -0
- package/src/queues/client.ts +135 -0
- package/src/secret/index.ts +14 -39
- package/src/tail/index.ts +5 -8
- package/src/user/access.ts +69 -0
- package/src/worker.ts +7 -0
- package/src/yargs-types.ts +15 -2
- package/src/zones.ts +31 -5
- package/templates/pages-template-plugin.ts +4 -0
- package/templates/pages-template-worker.ts +21 -4
- package/wrangler-dist/cli.d.ts +42 -0
- package/wrangler-dist/cli.js +4559 -3228
package/src/proxy.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
8
8
|
import serveStatic from "serve-static";
|
|
9
9
|
import { getHttpsOptions } from "./https-options";
|
|
10
10
|
import { logger } from "./logger";
|
|
11
|
+
import { getAccessToken } from "./user/access";
|
|
11
12
|
import type { CfPreviewToken } from "./create-worker-preview";
|
|
12
13
|
import type { HttpTerminator } from "http-terminator";
|
|
13
14
|
import type {
|
|
@@ -42,6 +43,27 @@ function addCfPreviewTokenHeader(
|
|
|
42
43
|
headers["cf-workers-preview-token"] = previewTokenValue;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export async function addCfAccessToken(
|
|
47
|
+
headers: IncomingHttpHeaders,
|
|
48
|
+
domain: string,
|
|
49
|
+
accessTokenRef: { current: string | undefined | null }
|
|
50
|
+
) {
|
|
51
|
+
if (accessTokenRef.current === null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (typeof accessTokenRef.current === "string") {
|
|
55
|
+
headers[
|
|
56
|
+
"cookie"
|
|
57
|
+
] = `${headers["cookie"]};CF_Authorization=${accessTokenRef.current}`;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const token = await getAccessToken(domain);
|
|
61
|
+
accessTokenRef.current = token;
|
|
62
|
+
if (token)
|
|
63
|
+
headers[
|
|
64
|
+
"cookie"
|
|
65
|
+
] = `${headers["cookie"]};CF_Authorization=${accessTokenRef.current}`;
|
|
66
|
+
}
|
|
45
67
|
/**
|
|
46
68
|
* Rewrite references in request headers
|
|
47
69
|
* from the preview host to the local host.
|
|
@@ -120,6 +142,7 @@ export async function startPreviewServer({
|
|
|
120
142
|
// We have a token. Let's proxy requests to the preview end point.
|
|
121
143
|
const streamBufferRef = { current: [] };
|
|
122
144
|
const requestResponseBufferRef = { current: [] };
|
|
145
|
+
const accessTokenRef = { current: undefined };
|
|
123
146
|
const cleanupListeners = configureProxyServer({
|
|
124
147
|
proxy,
|
|
125
148
|
previewToken,
|
|
@@ -129,6 +152,7 @@ export async function startPreviewServer({
|
|
|
129
152
|
assetDirectory,
|
|
130
153
|
localProtocol,
|
|
131
154
|
port,
|
|
155
|
+
accessTokenRef,
|
|
132
156
|
});
|
|
133
157
|
|
|
134
158
|
await waitForPortToBeAvailable(port, {
|
|
@@ -215,6 +239,7 @@ export function usePreviewServer({
|
|
|
215
239
|
const requestResponseBufferRef = useRef<
|
|
216
240
|
{ request: IncomingMessage; response: ServerResponse }[]
|
|
217
241
|
>([]);
|
|
242
|
+
const accessTokenRef = useRef<string | undefined | null>(undefined);
|
|
218
243
|
|
|
219
244
|
/**
|
|
220
245
|
* The session doesn't last forever, and will eventually drop
|
|
@@ -237,6 +262,7 @@ export function usePreviewServer({
|
|
|
237
262
|
assetDirectory,
|
|
238
263
|
localProtocol,
|
|
239
264
|
port,
|
|
265
|
+
accessTokenRef,
|
|
240
266
|
});
|
|
241
267
|
return () => {
|
|
242
268
|
cleanupListeners?.forEach((cleanup) => cleanup());
|
|
@@ -307,6 +333,7 @@ function configureProxyServer({
|
|
|
307
333
|
port,
|
|
308
334
|
localProtocol,
|
|
309
335
|
assetDirectory,
|
|
336
|
+
accessTokenRef,
|
|
310
337
|
}: {
|
|
311
338
|
proxy: PreviewProxy | undefined;
|
|
312
339
|
previewToken: CfPreviewToken | undefined;
|
|
@@ -324,6 +351,7 @@ function configureProxyServer({
|
|
|
324
351
|
port: number;
|
|
325
352
|
localProtocol: "https" | "http";
|
|
326
353
|
assetDirectory: string | undefined;
|
|
354
|
+
accessTokenRef: { current: string | null | undefined };
|
|
327
355
|
}) {
|
|
328
356
|
if (proxy === undefined) {
|
|
329
357
|
return;
|
|
@@ -376,7 +404,8 @@ function configureProxyServer({
|
|
|
376
404
|
previewToken,
|
|
377
405
|
remote,
|
|
378
406
|
port,
|
|
379
|
-
localProtocol
|
|
407
|
+
localProtocol,
|
|
408
|
+
accessTokenRef
|
|
380
409
|
);
|
|
381
410
|
proxy.server.on("stream", handleStream);
|
|
382
411
|
cleanupListeners.push(() => proxy.server.off("stream", handleStream));
|
|
@@ -389,7 +418,7 @@ function configureProxyServer({
|
|
|
389
418
|
streamBufferRef.current = [];
|
|
390
419
|
|
|
391
420
|
/** HTTP/1 -> HTTP/2 */
|
|
392
|
-
const handleRequest: RequestListener = (
|
|
421
|
+
const handleRequest: RequestListener = async (
|
|
393
422
|
message: IncomingMessage,
|
|
394
423
|
response: ServerResponse
|
|
395
424
|
) => {
|
|
@@ -397,6 +426,7 @@ function configureProxyServer({
|
|
|
397
426
|
if (httpVersionMajor >= 2) {
|
|
398
427
|
return; // Already handled by the "stream" event.
|
|
399
428
|
}
|
|
429
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
400
430
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
401
431
|
headers[":method"] = method;
|
|
402
432
|
headers[":path"] = url;
|
|
@@ -419,7 +449,6 @@ function configureProxyServer({
|
|
|
419
449
|
|
|
420
450
|
request.on("response", (responseHeaders) => {
|
|
421
451
|
const status = responseHeaders[":status"] ?? 500;
|
|
422
|
-
|
|
423
452
|
// log all requests to terminal
|
|
424
453
|
logger.log(new Date().toLocaleTimeString(), method, url, status);
|
|
425
454
|
|
|
@@ -461,12 +490,13 @@ function configureProxyServer({
|
|
|
461
490
|
requestResponseBufferRef.current = [];
|
|
462
491
|
|
|
463
492
|
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
464
|
-
const handleUpgrade = (
|
|
493
|
+
const handleUpgrade = async (
|
|
465
494
|
originalMessage: IncomingMessage,
|
|
466
495
|
originalSocket: Duplex,
|
|
467
496
|
originalHead: Buffer
|
|
468
497
|
) => {
|
|
469
498
|
const { headers, method, url } = originalMessage;
|
|
499
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
470
500
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
471
501
|
headers["host"] = previewToken.host;
|
|
472
502
|
|
|
@@ -562,12 +592,14 @@ function createStreamHandler(
|
|
|
562
592
|
previewToken: CfPreviewToken,
|
|
563
593
|
remote: ClientHttp2Session,
|
|
564
594
|
localPort: number,
|
|
565
|
-
localProtocol: "https" | "http"
|
|
595
|
+
localProtocol: "https" | "http",
|
|
596
|
+
accessTokenRef: { current: string | undefined | null }
|
|
566
597
|
) {
|
|
567
|
-
return function handleStream(
|
|
598
|
+
return async function handleStream(
|
|
568
599
|
stream: ServerHttp2Stream,
|
|
569
600
|
headers: IncomingHttpHeaders
|
|
570
601
|
) {
|
|
602
|
+
await addCfAccessToken(headers, previewToken.host, accessTokenRef);
|
|
571
603
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
572
604
|
headers[":authority"] = previewToken.host;
|
|
573
605
|
const request = stream.pipe(remote.request(headers));
|
package/src/publish/index.ts
CHANGED
|
@@ -14,18 +14,15 @@ import { requireAuth } from "../user";
|
|
|
14
14
|
import { collectKeyValues } from "../utils/collectKeyValues";
|
|
15
15
|
import publish from "./publish";
|
|
16
16
|
import type { ConfigPath } from "../index";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
CommonYargsOptions,
|
|
19
|
+
YargsOptionsToInterface,
|
|
20
|
+
} from "../yargs-types";
|
|
18
21
|
import type { Argv, ArgumentsCamelCase } from "yargs";
|
|
19
22
|
|
|
20
|
-
export function publishOptions(yargs: Argv) {
|
|
23
|
+
export function publishOptions(yargs: Argv<CommonYargsOptions>) {
|
|
21
24
|
return (
|
|
22
25
|
yargs
|
|
23
|
-
.option("env", {
|
|
24
|
-
type: "string",
|
|
25
|
-
requiresArg: true,
|
|
26
|
-
describe: "Perform on a specific environment",
|
|
27
|
-
alias: "e",
|
|
28
|
-
})
|
|
29
26
|
.positional("script", {
|
|
30
27
|
describe: "The path to an entry point for your worker",
|
|
31
28
|
type: "string",
|
|
@@ -174,6 +171,11 @@ export function publishOptions(yargs: Argv) {
|
|
|
174
171
|
describe: "Use legacy environments",
|
|
175
172
|
hidden: true,
|
|
176
173
|
})
|
|
174
|
+
.option("logpush", {
|
|
175
|
+
type: "boolean",
|
|
176
|
+
describe:
|
|
177
|
+
"Send Trace Events from this worker to Workers Logpush.\nThis will not configure a corresponding Logpush job automatically.",
|
|
178
|
+
})
|
|
177
179
|
);
|
|
178
180
|
}
|
|
179
181
|
|
|
@@ -263,5 +265,6 @@ export async function publishHandler(args: ArgumentsCamelCase<PublishArgs>) {
|
|
|
263
265
|
dryRun: args.dryRun,
|
|
264
266
|
noBundle: !(args.bundle ?? !config.no_bundle),
|
|
265
267
|
keepVars: args.keepVars,
|
|
268
|
+
logpush: args.logpush,
|
|
266
269
|
});
|
|
267
270
|
}
|
package/src/publish/publish.ts
CHANGED
|
@@ -5,7 +5,10 @@ import { URLSearchParams } from "node:url";
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import tmp from "tmp-promise";
|
|
7
7
|
import { bundleWorker } from "../bundle";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
printBundleSize,
|
|
10
|
+
printOffendingDependencies,
|
|
11
|
+
} from "../bundle-reporter";
|
|
9
12
|
import { fetchListResult, fetchResult } from "../cfetch";
|
|
10
13
|
import { printBindings } from "../config";
|
|
11
14
|
import { createWorkerUploadForm } from "../create-worker-upload-form";
|
|
@@ -14,10 +17,12 @@ import { getMigrationsToUpload } from "../durable";
|
|
|
14
17
|
import { logger } from "../logger";
|
|
15
18
|
import { getMetricsUsageHeaders } from "../metrics";
|
|
16
19
|
import { ParseError } from "../parse";
|
|
20
|
+
import { getQueue, putConsumer } from "../queues/client";
|
|
17
21
|
import { getWorkersDevSubdomain } from "../routes";
|
|
18
22
|
import { syncAssets } from "../sites";
|
|
19
23
|
import { identifyD1BindingsAsBeta } from "../worker";
|
|
20
24
|
import { getZoneForRoute } from "../zones";
|
|
25
|
+
import type { FetchError } from "../cfetch";
|
|
21
26
|
import type { Config } from "../config";
|
|
22
27
|
import type {
|
|
23
28
|
Route,
|
|
@@ -25,7 +30,9 @@ import type {
|
|
|
25
30
|
ZoneNameRoute,
|
|
26
31
|
CustomDomainRoute,
|
|
27
32
|
} from "../config/environment";
|
|
33
|
+
import type { DeploymentListRes } from "../deployments";
|
|
28
34
|
import type { Entry } from "../entry";
|
|
35
|
+
import type { PutConsumerBody } from "../queues/client";
|
|
29
36
|
import type { AssetPaths } from "../sites";
|
|
30
37
|
import type { CfWorkerInit } from "../worker";
|
|
31
38
|
|
|
@@ -54,6 +61,7 @@ type Props = {
|
|
|
54
61
|
dryRun: boolean | undefined;
|
|
55
62
|
noBundle: boolean | undefined;
|
|
56
63
|
keepVars: boolean | undefined;
|
|
64
|
+
logpush: boolean | undefined;
|
|
57
65
|
};
|
|
58
66
|
|
|
59
67
|
type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
|
|
@@ -83,6 +91,33 @@ function sleep(ms: number) {
|
|
|
83
91
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
const scriptStartupErrorRegex = /startup/i;
|
|
95
|
+
|
|
96
|
+
function errIsScriptSizeOrStartupErr(err: unknown) {
|
|
97
|
+
if (!err) return false;
|
|
98
|
+
|
|
99
|
+
// 10027 = workers.api.error.script_too_large
|
|
100
|
+
if ((err as { code: number }).code === 10027) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 10021 = validation error
|
|
105
|
+
// no explicit error code for more granular errors than "invalid script"
|
|
106
|
+
// but the error will contain a string error message directly from the
|
|
107
|
+
// validator.
|
|
108
|
+
// the error always SHOULD look like "Script startup exceeded CPU limit."
|
|
109
|
+
// (or the less likely "Script startup exceeded memory limits.")
|
|
110
|
+
if (
|
|
111
|
+
(err as { code: number }).code === 10021 &&
|
|
112
|
+
err instanceof ParseError &&
|
|
113
|
+
scriptStartupErrorRegex.test(err.notes[0]?.text)
|
|
114
|
+
) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
86
121
|
function renderRoute(route: Route): string {
|
|
87
122
|
let result = "";
|
|
88
123
|
if (typeof route === "string") {
|
|
@@ -350,6 +385,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
350
385
|
: `/accounts/${accountId}/workers/scripts/${scriptName}`;
|
|
351
386
|
|
|
352
387
|
let available_on_subdomain: boolean | undefined = undefined; // we'll set this later
|
|
388
|
+
let scriptTag: string | null = null;
|
|
353
389
|
|
|
354
390
|
const { format } = props.entry;
|
|
355
391
|
|
|
@@ -407,12 +443,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
407
443
|
|
|
408
444
|
const {
|
|
409
445
|
modules,
|
|
446
|
+
dependencies,
|
|
410
447
|
resolvedEntryPointPath,
|
|
411
448
|
bundleType,
|
|
412
449
|
}: Awaited<ReturnType<typeof bundleWorker>> = props.noBundle
|
|
413
450
|
? // we can skip the whole bundling step and mock a bundle here
|
|
414
451
|
{
|
|
415
452
|
modules: [],
|
|
453
|
+
dependencies: {},
|
|
416
454
|
resolvedEntryPointPath: props.entry.file,
|
|
417
455
|
bundleType: props.entry.format === "modules" ? "esm" : "commonjs",
|
|
418
456
|
stop: undefined,
|
|
@@ -496,6 +534,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
496
534
|
},
|
|
497
535
|
data_blobs: config.data_blobs,
|
|
498
536
|
durable_objects: config.durable_objects,
|
|
537
|
+
queues: config.queues.producers?.map((producer) => {
|
|
538
|
+
return { binding: producer.binding, queue_name: producer.queue };
|
|
539
|
+
}),
|
|
499
540
|
r2_buckets: config.r2_buckets,
|
|
500
541
|
d1_databases: identifyD1BindingsAsBeta(config.d1_databases),
|
|
501
542
|
services: config.services,
|
|
@@ -527,6 +568,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
527
568
|
props.compatibilityFlags ?? config.compatibility_flags,
|
|
528
569
|
usage_model: config.usage_model,
|
|
529
570
|
keepVars,
|
|
571
|
+
logpush: props.logpush !== undefined ? props.logpush : config.logpush,
|
|
530
572
|
};
|
|
531
573
|
|
|
532
574
|
// As this is not deterministic for testing, we detect if in a jest environment and run asynchronously
|
|
@@ -557,39 +599,50 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
557
599
|
|
|
558
600
|
printBindings({ ...withoutStaticAssets, vars: maskedVars });
|
|
559
601
|
|
|
602
|
+
await ensureQueuesExist(config);
|
|
603
|
+
|
|
560
604
|
if (!props.dryRun) {
|
|
561
605
|
// Upload the script so it has time to propagate.
|
|
562
606
|
// We can also now tell whether available_on_subdomain is set
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
607
|
+
try {
|
|
608
|
+
const result = await fetchResult<{
|
|
609
|
+
available_on_subdomain: boolean;
|
|
610
|
+
id: string | null;
|
|
611
|
+
etag: string | null;
|
|
612
|
+
pipeline_hash: string | null;
|
|
613
|
+
tag: string | null;
|
|
614
|
+
}>(
|
|
615
|
+
workerUrl,
|
|
616
|
+
{
|
|
617
|
+
method: "PUT",
|
|
618
|
+
body: createWorkerUploadForm(worker),
|
|
619
|
+
headers: await getMetricsUsageHeaders(config.send_metrics),
|
|
620
|
+
},
|
|
621
|
+
new URLSearchParams({
|
|
622
|
+
include_subdomain_availability: "true",
|
|
623
|
+
// pass excludeScript so the whole body of the
|
|
624
|
+
// script doesn't get included in the response
|
|
625
|
+
excludeScript: "true",
|
|
626
|
+
})
|
|
627
|
+
);
|
|
584
628
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
logger.log("Worker
|
|
629
|
+
available_on_subdomain = result.available_on_subdomain;
|
|
630
|
+
scriptTag = result.tag;
|
|
631
|
+
|
|
632
|
+
if (config.first_party_worker) {
|
|
633
|
+
// Print some useful information returned after publishing
|
|
634
|
+
// Not all fields will be populated for every worker
|
|
635
|
+
// These fields are likely to be scraped by tools, so do not rename
|
|
636
|
+
if (result.id) logger.log("Worker ID: ", result.id);
|
|
637
|
+
if (result.etag) logger.log("Worker ETag: ", result.etag);
|
|
638
|
+
if (result.pipeline_hash)
|
|
639
|
+
logger.log("Worker PipelineHash: ", result.pipeline_hash);
|
|
640
|
+
}
|
|
641
|
+
} catch (err) {
|
|
642
|
+
if (errIsScriptSizeOrStartupErr(err)) {
|
|
643
|
+
printOffendingDependencies(dependencies);
|
|
644
|
+
}
|
|
645
|
+
throw err;
|
|
593
646
|
}
|
|
594
647
|
}
|
|
595
648
|
} finally {
|
|
@@ -746,6 +799,10 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
746
799
|
);
|
|
747
800
|
}
|
|
748
801
|
|
|
802
|
+
if (config.queues.consumers && config.queues.consumers.length) {
|
|
803
|
+
deployments.push(...updateQueueConsumers(config));
|
|
804
|
+
}
|
|
805
|
+
|
|
749
806
|
const targets = await Promise.all(deployments);
|
|
750
807
|
const deployMs = Date.now() - start - uploadMs;
|
|
751
808
|
|
|
@@ -761,6 +818,19 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
761
818
|
} else {
|
|
762
819
|
logger.log("No publish targets for", workerName, formatTime(deployMs));
|
|
763
820
|
}
|
|
821
|
+
|
|
822
|
+
try {
|
|
823
|
+
const deploymentsList = await fetchResult<DeploymentListRes>(
|
|
824
|
+
`/accounts/${accountId}/workers/versions/by-script/${scriptTag}`
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
logger.log("Current Deployment ID:", deploymentsList.latest.id);
|
|
828
|
+
} catch (e) {
|
|
829
|
+
if ((e as { code: number }).code === 10023) {
|
|
830
|
+
// TODO: remove this try/catch once versions is completely rolled out
|
|
831
|
+
}
|
|
832
|
+
throw e;
|
|
833
|
+
}
|
|
764
834
|
}
|
|
765
835
|
|
|
766
836
|
function formatTime(duration: number) {
|
|
@@ -911,3 +981,54 @@ async function publishRoutesFallback(
|
|
|
911
981
|
function isAuthenticationError(e: unknown): e is ParseError {
|
|
912
982
|
return e instanceof ParseError && (e as { code?: number }).code === 10000;
|
|
913
983
|
}
|
|
984
|
+
|
|
985
|
+
async function ensureQueuesExist(config: Config) {
|
|
986
|
+
const producers = (config.queues.producers || []).map(
|
|
987
|
+
(producer) => producer.queue
|
|
988
|
+
);
|
|
989
|
+
const consumers = (config.queues.consumers || []).map(
|
|
990
|
+
(consumer) => consumer.queue
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
const queueNames = producers.concat(consumers);
|
|
994
|
+
for (const queue of queueNames) {
|
|
995
|
+
try {
|
|
996
|
+
await getQueue(config, queue);
|
|
997
|
+
} catch (err) {
|
|
998
|
+
const queueErr = err as FetchError;
|
|
999
|
+
if (queueErr.code === 100123) {
|
|
1000
|
+
// queue_not_found
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
`Queue "${queue}" does not exist. To create it, run: wrangler queues create ${queue}`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
throw err;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function updateQueueConsumers(config: Config): Promise<string[]>[] {
|
|
1011
|
+
const consumers = config.queues.consumers || [];
|
|
1012
|
+
return consumers.map((consumer) => {
|
|
1013
|
+
const body: PutConsumerBody = {
|
|
1014
|
+
dead_letter_queue: consumer.dead_letter_queue,
|
|
1015
|
+
settings: {
|
|
1016
|
+
batch_size: consumer.max_batch_size,
|
|
1017
|
+
max_retries: consumer.max_retries,
|
|
1018
|
+
max_wait_time_ms: consumer.max_batch_timeout
|
|
1019
|
+
? 1000 * consumer.max_batch_timeout
|
|
1020
|
+
: undefined,
|
|
1021
|
+
},
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
if (config.name === undefined) {
|
|
1025
|
+
// TODO: how can we reliably get the current script name?
|
|
1026
|
+
throw new Error("Script name is required to update queue consumers");
|
|
1027
|
+
}
|
|
1028
|
+
const scriptName = config.name;
|
|
1029
|
+
const envName = undefined; // TODO: script environment for wrangler publish?
|
|
1030
|
+
return putConsumer(config, consumer.queue, scriptName, envName, body).then(
|
|
1031
|
+
() => [`Consumer for ${consumer.queue}`]
|
|
1032
|
+
);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
@@ -7,11 +7,12 @@ import * as metrics from "../metrics";
|
|
|
7
7
|
import { parseHumanDuration } from "../parse";
|
|
8
8
|
import { requireAuth } from "../user";
|
|
9
9
|
import * as pubsub from ".";
|
|
10
|
+
import type { CommonYargsOptions } from "../yargs-types";
|
|
10
11
|
import type { Argv, CommandModule } from "yargs";
|
|
11
12
|
|
|
12
13
|
export function pubSubCommands(
|
|
13
|
-
pubsubYargs: Argv
|
|
14
|
-
subHelp: CommandModule
|
|
14
|
+
pubsubYargs: Argv<CommonYargsOptions>,
|
|
15
|
+
subHelp: CommandModule<CommonYargsOptions, CommonYargsOptions>
|
|
15
16
|
): Argv {
|
|
16
17
|
return pubsubYargs
|
|
17
18
|
.command(subHelp)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type Argv } from "yargs";
|
|
2
|
+
import { readConfig } from "../../../../config";
|
|
3
|
+
import { logger } from "../../../../logger";
|
|
4
|
+
import { postConsumer } from "../../../client";
|
|
5
|
+
import type { CommonYargsOptions } from "../../../../yargs-types";
|
|
6
|
+
import type { PostConsumerBody } from "../../../client";
|
|
7
|
+
|
|
8
|
+
type Args = CommonYargsOptions & {
|
|
9
|
+
config?: string;
|
|
10
|
+
["queue-name"]: string;
|
|
11
|
+
["script-name"]: string;
|
|
12
|
+
["batch-size"]?: number;
|
|
13
|
+
["batch-timeout"]?: number;
|
|
14
|
+
["message-retries"]?: number;
|
|
15
|
+
["dead-letter-queue"]?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function options(yargs: Argv<CommonYargsOptions>): Argv<Args> {
|
|
19
|
+
return yargs
|
|
20
|
+
.positional("queue-name", {
|
|
21
|
+
type: "string",
|
|
22
|
+
demandOption: true,
|
|
23
|
+
description: "Name of the queue to configure",
|
|
24
|
+
})
|
|
25
|
+
.positional("script-name", {
|
|
26
|
+
type: "string",
|
|
27
|
+
demandOption: true,
|
|
28
|
+
description: "Name of the consumer script",
|
|
29
|
+
})
|
|
30
|
+
.options({
|
|
31
|
+
"batch-size": {
|
|
32
|
+
type: "number",
|
|
33
|
+
describe: "Maximum number of messages per batch",
|
|
34
|
+
},
|
|
35
|
+
"batch-timeout": {
|
|
36
|
+
type: "number",
|
|
37
|
+
describe:
|
|
38
|
+
"Maximum number of seconds to wait to fill a batch with messages",
|
|
39
|
+
},
|
|
40
|
+
"message-retries": {
|
|
41
|
+
type: "number",
|
|
42
|
+
describe: "Maximum number of retries for each message",
|
|
43
|
+
},
|
|
44
|
+
"dead-letter-queue": {
|
|
45
|
+
type: "string",
|
|
46
|
+
describe: "Queue to send messages that failed to be consumed",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function handler(args: Args) {
|
|
52
|
+
const config = readConfig(args.config, args);
|
|
53
|
+
|
|
54
|
+
const body: PostConsumerBody = {
|
|
55
|
+
script_name: args["script-name"],
|
|
56
|
+
// TODO(soon) is this still the correct usage of the environment?
|
|
57
|
+
environment_name: args.env || "", // API expects empty string as default
|
|
58
|
+
settings: {
|
|
59
|
+
batch_size: args["batch-size"],
|
|
60
|
+
max_retries: args["message-retries"],
|
|
61
|
+
max_wait_time_ms: args["batch-timeout"] // API expects milliseconds
|
|
62
|
+
? 1000 * args["batch-timeout"]
|
|
63
|
+
: undefined,
|
|
64
|
+
},
|
|
65
|
+
dead_letter_queue: args["dead-letter-queue"],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
logger.log(`Adding consumer to queue ${args["queue-name"]}.`);
|
|
69
|
+
await postConsumer(config, args["queue-name"], body);
|
|
70
|
+
logger.log(`Added consumer to queue ${args["queue-name"]}.`);
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type BuilderCallback } from "yargs";
|
|
2
|
+
import { type CommonYargsOptions } from "../../../../yargs-types";
|
|
3
|
+
import { options as addOptions, handler as addHandler } from "./add";
|
|
4
|
+
import { options as removeOptions, handler as removeHandler } from "./remove";
|
|
5
|
+
|
|
6
|
+
export const consumers: BuilderCallback<CommonYargsOptions, unknown> = (
|
|
7
|
+
yargs
|
|
8
|
+
) => {
|
|
9
|
+
yargs.command(
|
|
10
|
+
"add <queue-name> <script-name>",
|
|
11
|
+
"Add a Queue Consumer",
|
|
12
|
+
addOptions,
|
|
13
|
+
addHandler
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
yargs.command(
|
|
17
|
+
"remove <queue-name> <script-name>",
|
|
18
|
+
"Remove a Queue Consumer",
|
|
19
|
+
removeOptions,
|
|
20
|
+
removeHandler
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Argv } from "yargs";
|
|
2
|
+
import { readConfig } from "../../../../config";
|
|
3
|
+
import { logger } from "../../../../logger";
|
|
4
|
+
import { deleteConsumer } from "../../../client";
|
|
5
|
+
import type { CommonYargsOptions } from "../../../../yargs-types";
|
|
6
|
+
|
|
7
|
+
type Args = CommonYargsOptions & {
|
|
8
|
+
config?: string;
|
|
9
|
+
["queue-name"]: string;
|
|
10
|
+
["script-name"]: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function options(yargs: Argv<CommonYargsOptions>): Argv<Args> {
|
|
14
|
+
return yargs
|
|
15
|
+
.positional("queue-name", {
|
|
16
|
+
type: "string",
|
|
17
|
+
demandOption: true,
|
|
18
|
+
description: "Name of the queue to configure",
|
|
19
|
+
})
|
|
20
|
+
.positional("script-name", {
|
|
21
|
+
type: "string",
|
|
22
|
+
demandOption: true,
|
|
23
|
+
description: "Name of the consumer script",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function handler(args: Args) {
|
|
28
|
+
const config = readConfig(args.config, args);
|
|
29
|
+
|
|
30
|
+
logger.log(`Removing consumer from queue ${args["queue-name"]}.`);
|
|
31
|
+
await deleteConsumer(
|
|
32
|
+
config,
|
|
33
|
+
args["queue-name"],
|
|
34
|
+
args["script-name"],
|
|
35
|
+
args.env
|
|
36
|
+
);
|
|
37
|
+
logger.log(`Removed consumer from queue ${args["queue-name"]}.`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Argv } from "yargs";
|
|
2
|
+
import { readConfig } from "../../../config";
|
|
3
|
+
import { logger } from "../../../logger";
|
|
4
|
+
import { createQueue } from "../../client";
|
|
5
|
+
|
|
6
|
+
interface Args {
|
|
7
|
+
config?: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function options(yargs: Argv): Argv<Args> {
|
|
12
|
+
return yargs.positional("name", {
|
|
13
|
+
type: "string",
|
|
14
|
+
demandOption: true,
|
|
15
|
+
description: "The name of the queue",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function handler(args: Args) {
|
|
20
|
+
const config = readConfig(args.config, args);
|
|
21
|
+
|
|
22
|
+
logger.log(`Creating queue ${args.name}.`);
|
|
23
|
+
await createQueue(config, { queue_name: args.name });
|
|
24
|
+
logger.log(`Created queue ${args.name}.`);
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Argv } from "yargs";
|
|
2
|
+
import { readConfig } from "../../../config";
|
|
3
|
+
import { logger } from "../../../logger";
|
|
4
|
+
import { deleteQueue } from "../../client";
|
|
5
|
+
|
|
6
|
+
interface Args {
|
|
7
|
+
config?: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function options(yargs: Argv): Argv<Args> {
|
|
12
|
+
// TODO(soon) --force option
|
|
13
|
+
return yargs.positional("name", {
|
|
14
|
+
type: "string",
|
|
15
|
+
demandOption: true,
|
|
16
|
+
description: "The name of the queue",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function handler(args: Args) {
|
|
21
|
+
const config = readConfig(args.config, args);
|
|
22
|
+
|
|
23
|
+
logger.log(`Deleting queue ${args.name}.`);
|
|
24
|
+
await deleteQueue(config, args.name);
|
|
25
|
+
logger.log(`Deleted queue ${args.name}.`);
|
|
26
|
+
}
|