wrangler 0.0.0-e6733a3 → 0.0.0-e88a54e
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/bin/wrangler.js +2 -2
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +35 -16
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +212 -28
- package/pages/functions/filepath-routing.ts +91 -131
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +19 -33
- package/src/__tests__/configuration.test.ts +2796 -0
- package/src/__tests__/dev.test.tsx +488 -58
- package/src/__tests__/dev2.test.tsx +154 -0
- package/src/__tests__/guess-worker-format.test.ts +58 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +48 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +55 -19
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +33 -0
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-in-tmp.ts +32 -0
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +779 -75
- package/src/__tests__/jest.setup.ts +53 -6
- package/src/__tests__/kv.test.ts +738 -454
- package/src/__tests__/logout.test.ts +52 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +44 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +2704 -0
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +425 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +564 -0
- package/src/__tests__/whoami.test.tsx +100 -0
- package/src/bundle.ts +142 -0
- package/src/cfetch/index.ts +21 -19
- package/src/cfetch/internal.ts +94 -24
- package/src/cli.ts +10 -0
- package/src/config/README.md +107 -0
- package/src/config/config.ts +218 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +366 -0
- package/src/config/index.ts +62 -0
- package/src/config/validation-helpers.ts +486 -0
- package/src/config/validation.ts +1090 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +61 -23
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +364 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +100 -0
- package/src/dialogs.tsx +2 -2
- package/src/entry.ts +208 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1198 -799
- package/src/inspect.ts +159 -66
- package/src/{kv.tsx → kv.ts} +117 -65
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +229 -35
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +126 -0
- package/src/pages.tsx +58 -42
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +224 -78
- package/src/publish.ts +281 -292
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +224 -58
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +273 -180
- package/src/whoami.tsx +69 -0
- package/src/{api/worker.ts → worker.ts} +45 -37
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +130612 -112486
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/run-in-tmp.ts +0 -19
- package/src/__tests__/run-wrangler.ts +0 -32
- package/src/api/form_data.ts +0 -131
- package/src/config.ts +0 -487
- package/src/dev.tsx +0 -771
- package/src/tail.tsx +0 -73
package/src/inspect.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
6
7
|
import { version } from "../package.json";
|
|
7
|
-
|
|
8
|
+
import { waitForPortToBeAvailable } from "./proxy";
|
|
8
9
|
import type Protocol from "devtools-protocol";
|
|
10
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
11
|
+
import type { MessageEvent } from "ws";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* `useInspector` is a hook for debugging Workers applications
|
|
@@ -51,23 +54,18 @@ interface InspectorProps {
|
|
|
51
54
|
export default function useInspector(props: InspectorProps) {
|
|
52
55
|
/** A unique ID for this session. */
|
|
53
56
|
const inspectorIdRef = useRef(randomId());
|
|
57
|
+
|
|
58
|
+
/** The websocket from the devtools instance. */
|
|
59
|
+
const [localWebSocket, setLocalWebSocket] = useState<WebSocket>();
|
|
60
|
+
/** The websocket from the edge */
|
|
61
|
+
const [remoteWebSocket, setRemoteWebSocket] = useState<WebSocket>();
|
|
62
|
+
|
|
54
63
|
/**
|
|
55
64
|
* The local proxy server that acts as the bridge between
|
|
56
65
|
* the remote websocket and the local DevTools instance.
|
|
57
66
|
*/
|
|
58
67
|
const serverRef = useRef<Server>();
|
|
59
|
-
|
|
60
|
-
const wsServerRef = useRef<WebSocketServer>();
|
|
61
|
-
|
|
62
|
-
/** The websocket from the devtools instance. */
|
|
63
|
-
const [localWebSocket, setLocalWebSocket] = useState<WebSocket | undefined>();
|
|
64
|
-
/** The websocket from the edge */
|
|
65
|
-
const [remoteWebSocket, setRemoteWebSocket] = useState<
|
|
66
|
-
WebSocket | undefined
|
|
67
|
-
>();
|
|
68
|
-
|
|
69
|
-
if (!serverRef.current) {
|
|
70
|
-
// Let's create the proxy server!
|
|
68
|
+
if (serverRef.current === undefined) {
|
|
71
69
|
serverRef.current = createServer(
|
|
72
70
|
(req: IncomingMessage, res: ServerResponse) => {
|
|
73
71
|
switch (req.url) {
|
|
@@ -78,7 +76,7 @@ export default function useInspector(props: InspectorProps) {
|
|
|
78
76
|
res.end(
|
|
79
77
|
JSON.stringify({
|
|
80
78
|
Browser: `wrangler/v${version}`,
|
|
81
|
-
// TODO: (someday): The DevTools protocol should match that of
|
|
79
|
+
// TODO: (someday): The DevTools protocol should match that of Edge Worker.
|
|
82
80
|
// This could be exposed by the preview API.
|
|
83
81
|
"Protocol-Version": "1.3",
|
|
84
82
|
})
|
|
@@ -118,44 +116,59 @@ export default function useInspector(props: InspectorProps) {
|
|
|
118
116
|
}
|
|
119
117
|
}
|
|
120
118
|
);
|
|
119
|
+
}
|
|
120
|
+
const server = serverRef.current;
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The websocket server that runs on top of the proxy server.
|
|
124
|
+
*/
|
|
125
|
+
const wsServerRef = useRef<WebSocketServer>();
|
|
126
|
+
if (wsServerRef.current === undefined) {
|
|
123
127
|
wsServerRef.current = new WebSocketServer({
|
|
124
|
-
server
|
|
128
|
+
server,
|
|
125
129
|
clientTracking: true,
|
|
126
130
|
});
|
|
127
|
-
wsServerRef.current.on("connection", (ws: WebSocket) => {
|
|
128
|
-
if (wsServerRef.current.clients.size > 1) {
|
|
129
|
-
/** We only want to have one active Devtools instance at a time. */
|
|
130
|
-
console.error(
|
|
131
|
-
"Tried to open a new devtools window when a previous one was already open."
|
|
132
|
-
);
|
|
133
|
-
ws.close(1013, "Too many clients; only one can be connected at a time");
|
|
134
|
-
} else {
|
|
135
|
-
// As promised, save the created websocket in a state hook
|
|
136
|
-
setLocalWebSocket(ws);
|
|
137
|
-
|
|
138
|
-
ws.addEventListener("close", () => {
|
|
139
|
-
// And and cleanup when devtools closes
|
|
140
|
-
setLocalWebSocket(undefined);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
131
|
}
|
|
132
|
+
const wsServer = wsServerRef.current;
|
|
133
|
+
|
|
134
|
+
wsServer.on("connection", (ws: WebSocket) => {
|
|
135
|
+
if (wsServer.clients.size > 1) {
|
|
136
|
+
/** We only want to have one active Devtools instance at a time. */
|
|
137
|
+
console.error(
|
|
138
|
+
"Tried to open a new devtools window when a previous one was already open."
|
|
139
|
+
);
|
|
140
|
+
ws.close(1013, "Too many clients; only one can be connected at a time");
|
|
141
|
+
} else {
|
|
142
|
+
// As promised, save the created websocket in a state hook
|
|
143
|
+
setLocalWebSocket(ws);
|
|
144
|
+
|
|
145
|
+
ws.addEventListener("close", () => {
|
|
146
|
+
// And and cleanup when devtools closes
|
|
147
|
+
setLocalWebSocket(undefined);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
145
151
|
|
|
146
152
|
/**
|
|
147
153
|
* We start and stop the server in an effect to take advantage
|
|
148
154
|
* of the component lifecycle. Convenient.
|
|
149
155
|
*/
|
|
150
156
|
useEffect(() => {
|
|
151
|
-
|
|
157
|
+
async function startInspectorProxy() {
|
|
158
|
+
await waitForPortToBeAvailable(props.port, {
|
|
159
|
+
retryPeriod: 200,
|
|
160
|
+
timeout: 2000,
|
|
161
|
+
});
|
|
162
|
+
server.listen(props.port);
|
|
163
|
+
}
|
|
164
|
+
startInspectorProxy().catch((err) => console.error(err));
|
|
152
165
|
return () => {
|
|
153
|
-
|
|
166
|
+
server.close();
|
|
154
167
|
// Also disconnect any open websockets/devtools connections
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
wsServer.clients.forEach((ws) => ws.close());
|
|
169
|
+
wsServer.close();
|
|
157
170
|
};
|
|
158
|
-
}, [props.port]);
|
|
171
|
+
}, [props.port, server, wsServer]);
|
|
159
172
|
|
|
160
173
|
/**
|
|
161
174
|
* When connecting to the remote websocket, if we don't start either
|
|
@@ -238,7 +251,7 @@ export default function useInspector(props: InspectorProps) {
|
|
|
238
251
|
"🚨", // cheesy, but it works
|
|
239
252
|
// maybe we could use color here too.
|
|
240
253
|
params.exceptionDetails.text,
|
|
241
|
-
params.exceptionDetails.exception
|
|
254
|
+
params.exceptionDetails.exception?.description ?? ""
|
|
242
255
|
);
|
|
243
256
|
}
|
|
244
257
|
if (evt.method === "Runtime.consoleAPICalled") {
|
|
@@ -284,7 +297,7 @@ export default function useInspector(props: InspectorProps) {
|
|
|
284
297
|
clearInterval(keepAliveInterval);
|
|
285
298
|
// Then we'll send a message to the devtools instance to
|
|
286
299
|
// tell it to clear the console.
|
|
287
|
-
|
|
300
|
+
wsServer.clients.forEach((client) => {
|
|
288
301
|
// We could've used `localSocket` here, but
|
|
289
302
|
// then we would have had to add it to the effect
|
|
290
303
|
// change detection array, which would have made a
|
|
@@ -311,8 +324,14 @@ export default function useInspector(props: InspectorProps) {
|
|
|
311
324
|
};
|
|
312
325
|
}, [
|
|
313
326
|
props.inspectorUrl,
|
|
314
|
-
retryRemoteWebSocketConnectionSigil,
|
|
315
327
|
props.logToTerminal,
|
|
328
|
+
wsServer,
|
|
329
|
+
// We use a state value as a sigil to trigger a retry of the
|
|
330
|
+
// remote websocket connection. It's not used inside the effect,
|
|
331
|
+
// so react-hooks/exhaustive-deps doesn't complain if it's not
|
|
332
|
+
// included in the dependency array. But its presence is critical,
|
|
333
|
+
// so do NOT remove it from the dependency list.
|
|
334
|
+
retryRemoteWebSocketConnectionSigil,
|
|
316
335
|
]);
|
|
317
336
|
|
|
318
337
|
/**
|
|
@@ -350,9 +369,16 @@ export default function useInspector(props: InspectorProps) {
|
|
|
350
369
|
/** Send a message from the local websocket to the remote websocket */
|
|
351
370
|
function sendMessageToRemoteWebSocket(event: MessageEvent) {
|
|
352
371
|
try {
|
|
372
|
+
assert(
|
|
373
|
+
remoteWebSocket,
|
|
374
|
+
"Trying to send a message to an undefined `remoteWebSocket`"
|
|
375
|
+
);
|
|
353
376
|
remoteWebSocket.send(event.data);
|
|
354
377
|
} catch (e) {
|
|
355
|
-
if (
|
|
378
|
+
if (
|
|
379
|
+
(e as Error).message !==
|
|
380
|
+
"WebSocket is not open: readyState 0 (CONNECTING)"
|
|
381
|
+
) {
|
|
356
382
|
/**
|
|
357
383
|
* ^ this just means we haven't opened a websocket yet
|
|
358
384
|
* usually happens until there's at least one request
|
|
@@ -367,6 +393,10 @@ export default function useInspector(props: InspectorProps) {
|
|
|
367
393
|
|
|
368
394
|
/** Send a message from the local websocket to the remote websocket */
|
|
369
395
|
function sendMessageToLocalWebSocket(event: MessageEvent) {
|
|
396
|
+
assert(
|
|
397
|
+
localWebSocket,
|
|
398
|
+
"Trying to send a message to an undefined `localWebSocket`"
|
|
399
|
+
);
|
|
370
400
|
localWebSocket.send(event.data);
|
|
371
401
|
}
|
|
372
402
|
|
|
@@ -419,8 +449,35 @@ function randomId(): string {
|
|
|
419
449
|
* we're just doing a little bit of the work of the devtools console,
|
|
420
450
|
* directly in the terminal.
|
|
421
451
|
*/
|
|
452
|
+
|
|
453
|
+
export const mapConsoleAPIMessageTypeToConsoleMethod: {
|
|
454
|
+
[key in Protocol.Runtime.ConsoleAPICalledEvent["type"]]: Exclude<
|
|
455
|
+
keyof Console,
|
|
456
|
+
"Console"
|
|
457
|
+
>;
|
|
458
|
+
} = {
|
|
459
|
+
log: "log",
|
|
460
|
+
debug: "debug",
|
|
461
|
+
info: "info",
|
|
462
|
+
warning: "warn",
|
|
463
|
+
error: "error",
|
|
464
|
+
dir: "dir",
|
|
465
|
+
dirxml: "dirxml",
|
|
466
|
+
table: "table",
|
|
467
|
+
trace: "trace",
|
|
468
|
+
clear: "clear",
|
|
469
|
+
count: "count",
|
|
470
|
+
assert: "assert",
|
|
471
|
+
profile: "profile",
|
|
472
|
+
profileEnd: "profileEnd",
|
|
473
|
+
timeEnd: "timeEnd",
|
|
474
|
+
startGroup: "group",
|
|
475
|
+
startGroupCollapsed: "groupCollapsed",
|
|
476
|
+
endGroup: "groupEnd",
|
|
477
|
+
};
|
|
478
|
+
|
|
422
479
|
function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
423
|
-
const args = [];
|
|
480
|
+
const args: string[] = [];
|
|
424
481
|
for (const ro of evt.args) {
|
|
425
482
|
switch (ro.type) {
|
|
426
483
|
case "string":
|
|
@@ -432,13 +489,17 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
432
489
|
args.push(ro.value);
|
|
433
490
|
break;
|
|
434
491
|
case "function":
|
|
435
|
-
args.push(`[Function: ${ro.description}]`);
|
|
492
|
+
args.push(`[Function: ${ro.description ?? "<no-description>"}]`);
|
|
436
493
|
break;
|
|
437
494
|
case "object":
|
|
438
495
|
if (!ro.preview) {
|
|
439
|
-
args.push(
|
|
496
|
+
args.push(
|
|
497
|
+
ro.subtype === "null"
|
|
498
|
+
? "null"
|
|
499
|
+
: ro.description ?? "<no-description>"
|
|
500
|
+
);
|
|
440
501
|
} else {
|
|
441
|
-
args.push(ro.preview.description);
|
|
502
|
+
args.push(ro.preview.description ?? "<no-description>");
|
|
442
503
|
|
|
443
504
|
switch (ro.preview.subtype) {
|
|
444
505
|
case "array":
|
|
@@ -454,12 +515,17 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
454
515
|
);
|
|
455
516
|
|
|
456
517
|
break;
|
|
518
|
+
case "weakmap":
|
|
457
519
|
case "map":
|
|
458
520
|
args.push(
|
|
459
521
|
"{\n" +
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
522
|
+
// Maps always have entries
|
|
523
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
524
|
+
ro.preview
|
|
525
|
+
.entries!.map(({ key, value }) => {
|
|
526
|
+
return ` ${key?.description ?? "<unknown>"} => ${
|
|
527
|
+
value.description
|
|
528
|
+
}`;
|
|
463
529
|
})
|
|
464
530
|
.join(",\n") +
|
|
465
531
|
(ro.preview.overflow ? "\n ..." : "") +
|
|
@@ -467,38 +533,47 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
467
533
|
);
|
|
468
534
|
|
|
469
535
|
break;
|
|
536
|
+
case "weakset":
|
|
470
537
|
case "set":
|
|
471
538
|
args.push(
|
|
472
539
|
"{ " +
|
|
473
|
-
|
|
474
|
-
|
|
540
|
+
// Sets always have entries
|
|
541
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
542
|
+
ro.preview
|
|
543
|
+
.entries!.map(({ value }) => {
|
|
475
544
|
return `${value.description}`;
|
|
476
545
|
})
|
|
477
546
|
.join(", ") +
|
|
478
547
|
(ro.preview.overflow ? ", ..." : "") +
|
|
479
548
|
" }"
|
|
480
549
|
);
|
|
481
|
-
|
|
482
|
-
break;
|
|
483
|
-
case "null":
|
|
484
|
-
args.push("null");
|
|
485
550
|
break;
|
|
486
|
-
case "node":
|
|
487
551
|
case "regexp":
|
|
552
|
+
break;
|
|
488
553
|
case "date":
|
|
489
|
-
|
|
490
|
-
case "weakset":
|
|
491
|
-
case "iterator":
|
|
554
|
+
break;
|
|
492
555
|
case "generator":
|
|
493
|
-
|
|
494
|
-
|
|
556
|
+
args.push(ro.preview.properties[0].value || "");
|
|
557
|
+
break;
|
|
495
558
|
case "promise":
|
|
559
|
+
if (ro.preview.properties[0].value === "pending") {
|
|
560
|
+
args.push(`{<${ro.preview.properties[0].value}>}`);
|
|
561
|
+
} else {
|
|
562
|
+
args.push(
|
|
563
|
+
`{<${ro.preview.properties[0].value}>: ${ro.preview.properties[1].value}}`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
567
|
+
case "node":
|
|
568
|
+
case "iterator":
|
|
569
|
+
case "proxy":
|
|
496
570
|
case "typedarray":
|
|
497
571
|
case "arraybuffer":
|
|
498
572
|
case "dataview":
|
|
499
573
|
case "webassemblymemory":
|
|
500
574
|
case "wasmvalue":
|
|
501
575
|
break;
|
|
576
|
+
case "error":
|
|
502
577
|
default:
|
|
503
578
|
// just a pojo
|
|
504
579
|
args.push(
|
|
@@ -520,5 +595,23 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
|
|
|
520
595
|
}
|
|
521
596
|
}
|
|
522
597
|
|
|
523
|
-
|
|
598
|
+
const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
|
|
599
|
+
|
|
600
|
+
if (method in console) {
|
|
601
|
+
switch (method) {
|
|
602
|
+
case "dir":
|
|
603
|
+
console.dir(args);
|
|
604
|
+
break;
|
|
605
|
+
case "table":
|
|
606
|
+
console.table(args);
|
|
607
|
+
break;
|
|
608
|
+
default:
|
|
609
|
+
// eslint-disable-next-line prefer-spread
|
|
610
|
+
console[method].apply(console, args);
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
console.warn(`Unsupported console method: ${method}`);
|
|
615
|
+
console.log("console event:", evt);
|
|
616
|
+
}
|
|
524
617
|
}
|
package/src/{kv.tsx → kv.ts}
RENAMED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { URLSearchParams } from "node:url";
|
|
2
|
+
import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
|
|
2
3
|
import type { Config } from "./config";
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
/** The largest number of kv items we can pass to the API in a single request. */
|
|
6
|
+
const API_MAX = 10000;
|
|
7
|
+
// The const below are halved from the API's true capacity to help avoid
|
|
8
|
+
// hammering it with large requests.
|
|
9
|
+
const BATCH_KEY_MAX = API_MAX / 2;
|
|
4
10
|
|
|
5
11
|
type KvArgs = {
|
|
6
12
|
binding?: string;
|
|
7
13
|
"namespace-id"?: string;
|
|
8
|
-
env?: string;
|
|
9
14
|
preview?: boolean;
|
|
10
|
-
config?: Config;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -82,7 +86,7 @@ export interface NamespaceKeyInfo {
|
|
|
82
86
|
export async function listNamespaceKeys(
|
|
83
87
|
accountId: string,
|
|
84
88
|
namespaceId: string,
|
|
85
|
-
prefix
|
|
89
|
+
prefix = ""
|
|
86
90
|
) {
|
|
87
91
|
return await fetchListResult<NamespaceKeyInfo>(
|
|
88
92
|
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/keys`,
|
|
@@ -91,62 +95,130 @@ export async function listNamespaceKeys(
|
|
|
91
95
|
);
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Information about a key-value pair, including its "metadata" fields.
|
|
100
|
+
*/
|
|
101
|
+
export interface KeyValue {
|
|
102
|
+
key: string;
|
|
103
|
+
value: string;
|
|
104
|
+
expiration?: number;
|
|
105
|
+
expiration_ttl?: number;
|
|
106
|
+
metadata?: object;
|
|
107
|
+
base64?: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const KeyValueKeys = new Set([
|
|
111
|
+
"key",
|
|
112
|
+
"value",
|
|
113
|
+
"expiration",
|
|
114
|
+
"expiration_ttl",
|
|
115
|
+
"metadata",
|
|
116
|
+
"base64",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Is the given object a valid `KeyValue` type?
|
|
121
|
+
*/
|
|
122
|
+
export function isKeyValue(keyValue: object): keyValue is KeyValue {
|
|
123
|
+
const props = Object.keys(keyValue);
|
|
124
|
+
if (!props.includes("key") || !props.includes("value")) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get all the properties on the `keyValue` that are not expected.
|
|
132
|
+
*/
|
|
133
|
+
export function unexpectedKeyValueProps(keyValue: KeyValue): string[] {
|
|
134
|
+
const props = Object.keys(keyValue);
|
|
135
|
+
return props.filter((prop) => !KeyValueKeys.has(prop));
|
|
136
|
+
}
|
|
137
|
+
|
|
94
138
|
export async function putKeyValue(
|
|
95
139
|
accountId: string,
|
|
96
140
|
namespaceId: string,
|
|
97
|
-
|
|
98
|
-
value: string,
|
|
99
|
-
args?: { expiration?: number; expiration_ttl?: number }
|
|
141
|
+
keyValue: KeyValue
|
|
100
142
|
) {
|
|
143
|
+
let searchParams: URLSearchParams | undefined;
|
|
144
|
+
if (keyValue.expiration || keyValue.expiration_ttl) {
|
|
145
|
+
searchParams = new URLSearchParams();
|
|
146
|
+
if (keyValue.expiration) {
|
|
147
|
+
searchParams.set("expiration", `${keyValue.expiration}`);
|
|
148
|
+
}
|
|
149
|
+
if (keyValue.expiration_ttl) {
|
|
150
|
+
searchParams.set("expiration_ttl", `${keyValue.expiration_ttl}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
101
153
|
return await fetchResult(
|
|
102
|
-
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
|
|
103
|
-
{ method: "PUT", body: value },
|
|
104
|
-
|
|
105
|
-
? new URLSearchParams({
|
|
106
|
-
expiration: args.expiration?.toString(),
|
|
107
|
-
expiration_ttl: args.expiration_ttl?.toString(),
|
|
108
|
-
})
|
|
109
|
-
: undefined
|
|
154
|
+
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${keyValue.key}`,
|
|
155
|
+
{ method: "PUT", body: keyValue.value },
|
|
156
|
+
searchParams
|
|
110
157
|
);
|
|
111
158
|
}
|
|
112
159
|
|
|
160
|
+
export async function getKeyValue(
|
|
161
|
+
accountId: string,
|
|
162
|
+
namespaceId: string,
|
|
163
|
+
key: string
|
|
164
|
+
): Promise<string> {
|
|
165
|
+
return await fetchKVGetValue(accountId, namespaceId, key);
|
|
166
|
+
}
|
|
167
|
+
|
|
113
168
|
export async function putBulkKeyValue(
|
|
114
169
|
accountId: string,
|
|
115
170
|
namespaceId: string,
|
|
116
|
-
|
|
171
|
+
keyValues: KeyValue[],
|
|
172
|
+
progressCallback: (index: number, total: number) => void
|
|
117
173
|
) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
method: "PUT",
|
|
122
|
-
body: keyvalueStr,
|
|
123
|
-
headers: { "Content-Type": "application/json" },
|
|
174
|
+
for (let index = 0; index < keyValues.length; index += BATCH_KEY_MAX) {
|
|
175
|
+
if (progressCallback && keyValues.length > BATCH_KEY_MAX) {
|
|
176
|
+
progressCallback(index, keyValues.length);
|
|
124
177
|
}
|
|
125
|
-
|
|
178
|
+
|
|
179
|
+
await fetchResult(
|
|
180
|
+
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
181
|
+
{
|
|
182
|
+
method: "PUT",
|
|
183
|
+
body: JSON.stringify(keyValues.slice(index, index + BATCH_KEY_MAX)),
|
|
184
|
+
headers: { "Content-Type": "application/json" },
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (progressCallback && keyValues.length > BATCH_KEY_MAX) {
|
|
189
|
+
progressCallback(keyValues.length, keyValues.length);
|
|
190
|
+
}
|
|
126
191
|
}
|
|
127
192
|
|
|
128
193
|
export async function deleteBulkKeyValue(
|
|
129
194
|
accountId: string,
|
|
130
195
|
namespaceId: string,
|
|
131
|
-
|
|
196
|
+
keys: string[],
|
|
197
|
+
progressCallback: (index: number, total: number) => void
|
|
132
198
|
) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
method: "DELETE",
|
|
137
|
-
body: keyStr,
|
|
138
|
-
headers: { "Content-Type": "application/json" },
|
|
199
|
+
for (let index = 0; index < keys.length; index += BATCH_KEY_MAX) {
|
|
200
|
+
if (progressCallback && keys.length > BATCH_KEY_MAX) {
|
|
201
|
+
progressCallback(index, keys.length);
|
|
139
202
|
}
|
|
140
|
-
|
|
203
|
+
|
|
204
|
+
await fetchResult(
|
|
205
|
+
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
206
|
+
{
|
|
207
|
+
method: "DELETE",
|
|
208
|
+
body: JSON.stringify(keys.slice(index, index + BATCH_KEY_MAX)),
|
|
209
|
+
headers: { "Content-Type": "application/json" },
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (progressCallback && keys.length > BATCH_KEY_MAX) {
|
|
214
|
+
progressCallback(keys.length, keys.length);
|
|
215
|
+
}
|
|
141
216
|
}
|
|
142
217
|
|
|
143
|
-
export function getNamespaceId(
|
|
144
|
-
preview,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"namespace-id": namespaceId,
|
|
148
|
-
env,
|
|
149
|
-
}: KvArgs): string {
|
|
218
|
+
export function getNamespaceId(
|
|
219
|
+
{ preview, binding, "namespace-id": namespaceId }: KvArgs,
|
|
220
|
+
config: Config
|
|
221
|
+
): string {
|
|
150
222
|
// nice
|
|
151
223
|
if (namespaceId) {
|
|
152
224
|
return namespaceId;
|
|
@@ -154,7 +226,7 @@ export function getNamespaceId({
|
|
|
154
226
|
|
|
155
227
|
// begin pre-flight checks
|
|
156
228
|
|
|
157
|
-
// `--binding` is only valid if there's a wrangler configuration
|
|
229
|
+
// `--binding` is only valid if there's a wrangler configuration.
|
|
158
230
|
if (binding && !config) {
|
|
159
231
|
throw new Error("--binding specified, but no config file was found.");
|
|
160
232
|
}
|
|
@@ -167,30 +239,6 @@ export function getNamespaceId({
|
|
|
167
239
|
);
|
|
168
240
|
}
|
|
169
241
|
|
|
170
|
-
// they want to use an environment, actually
|
|
171
|
-
if (env) {
|
|
172
|
-
if (!config.env || !config.env[env]) {
|
|
173
|
-
throw new Error(
|
|
174
|
-
`Failed to find environment "${env}" in configuration file!`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// TODO: either a bespoke arg type for this function to avoid `undefined`s or an `EnvOrConfig` type
|
|
179
|
-
return getNamespaceId({
|
|
180
|
-
binding,
|
|
181
|
-
"namespace-id": namespaceId,
|
|
182
|
-
env: undefined,
|
|
183
|
-
preview,
|
|
184
|
-
config: {
|
|
185
|
-
env: undefined,
|
|
186
|
-
build: undefined,
|
|
187
|
-
name: undefined,
|
|
188
|
-
account_id: undefined,
|
|
189
|
-
...config.env[env],
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
242
|
// there's no KV namespaces
|
|
195
243
|
if (!config.kv_namespaces || config.kv_namespaces.length === 0) {
|
|
196
244
|
throw new Error(
|
|
@@ -262,6 +310,10 @@ export function getNamespaceId({
|
|
|
262
310
|
/**
|
|
263
311
|
* KV namespace binding names must be valid JS identifiers.
|
|
264
312
|
*/
|
|
265
|
-
export function isValidNamespaceBinding(
|
|
266
|
-
|
|
313
|
+
export function isValidNamespaceBinding(
|
|
314
|
+
binding: string | undefined
|
|
315
|
+
): binding is string {
|
|
316
|
+
return (
|
|
317
|
+
typeof binding === "string" && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(binding)
|
|
318
|
+
);
|
|
267
319
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Custom Miniflare CLI
|
|
2
|
+
|
|
3
|
+
This directory contains a simple wrapper around the programmatic Miniflare API,
|
|
4
|
+
which Wrangler spawns when running `wrangler dev` in local mode.
|
|
5
|
+
|
|
6
|
+
## Building
|
|
7
|
+
|
|
8
|
+
This CLI is built at the same time as Wrangler by running
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
npm run -w wrangler build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The output of the build is `miniflare-dist/index.mjs`.
|
|
15
|
+
|
|
16
|
+
## Running
|
|
17
|
+
|
|
18
|
+
The CLI expects a single command line argument which is the Miniflare options formatted as a string of JSON.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
node --no-warnings ./packages/wrangler/miniflare-dist/index.mjs '{"watch": true, "script": ""}' --log VERBOSE
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `--log` argument is optional and takes one of Miniflare's LogLevels: "NONE", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE".
|
|
25
|
+
It defaults to `INFO`.
|
|
26
|
+
|
|
27
|
+
## Debugging
|
|
28
|
+
|
|
29
|
+
Simply place a breakpoint in the code and run the above command in the VS Code "JavaScript Debug Terminal".
|
|
30
|
+
The code will stop at the breakpoint as expected.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type EnumKeys<Enum> = Exclude<keyof Enum, number>;
|
|
2
|
+
|
|
3
|
+
export const enumObject = <Enum extends Record<string, number | string>>(
|
|
4
|
+
e: Enum
|
|
5
|
+
) => {
|
|
6
|
+
const copy = { ...e } as { [K in EnumKeys<Enum>]: Enum[K] };
|
|
7
|
+
Object.values(e).forEach(
|
|
8
|
+
(value) => typeof value === "number" && delete copy[value]
|
|
9
|
+
);
|
|
10
|
+
return copy;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const enumKeys = <Enum extends Record<string, number | string>>(
|
|
14
|
+
e: Enum
|
|
15
|
+
) => {
|
|
16
|
+
return Object.keys(enumObject(e)) as EnumKeys<Enum>[];
|
|
17
|
+
};
|