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.
Files changed (99) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/miniflare-dist/index.mjs +79 -0
  3. package/miniflare-dist/index.mjs.map +7 -0
  4. package/package.json +35 -16
  5. package/pages/functions/buildWorker.ts +1 -1
  6. package/pages/functions/filepath-routing.test.ts +212 -28
  7. package/pages/functions/filepath-routing.ts +91 -131
  8. package/pages/functions/routes.ts +11 -18
  9. package/pages/functions/template-worker.ts +19 -33
  10. package/src/__tests__/configuration.test.ts +2796 -0
  11. package/src/__tests__/dev.test.tsx +488 -58
  12. package/src/__tests__/dev2.test.tsx +154 -0
  13. package/src/__tests__/guess-worker-format.test.ts +58 -0
  14. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  15. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  16. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  17. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  18. package/src/__tests__/helpers/mock-bin.ts +36 -0
  19. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +55 -19
  20. package/src/__tests__/helpers/mock-console.ts +70 -0
  21. package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
  22. package/src/__tests__/helpers/mock-kv.ts +33 -0
  23. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  24. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  25. package/src/__tests__/helpers/run-in-tmp.ts +32 -0
  26. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  27. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  28. package/src/__tests__/https-options.test.ts +127 -0
  29. package/src/__tests__/index.test.ts +779 -75
  30. package/src/__tests__/jest.setup.ts +53 -6
  31. package/src/__tests__/kv.test.ts +738 -454
  32. package/src/__tests__/logout.test.ts +52 -0
  33. package/src/__tests__/package-manager.test.ts +206 -0
  34. package/src/__tests__/pages.test.ts +44 -0
  35. package/src/__tests__/parse.test.ts +322 -0
  36. package/src/__tests__/publish.test.ts +2704 -0
  37. package/src/__tests__/r2.test.ts +198 -0
  38. package/src/__tests__/route.test.ts +45 -0
  39. package/src/__tests__/secret.test.ts +425 -0
  40. package/src/__tests__/sentry.test.ts +146 -0
  41. package/src/__tests__/tail.test.ts +564 -0
  42. package/src/__tests__/whoami.test.tsx +100 -0
  43. package/src/bundle.ts +142 -0
  44. package/src/cfetch/index.ts +21 -19
  45. package/src/cfetch/internal.ts +94 -24
  46. package/src/cli.ts +10 -0
  47. package/src/config/README.md +107 -0
  48. package/src/config/config.ts +218 -0
  49. package/src/config/diagnostics.ts +80 -0
  50. package/src/config/environment.ts +366 -0
  51. package/src/config/index.ts +62 -0
  52. package/src/config/validation-helpers.ts +486 -0
  53. package/src/config/validation.ts +1090 -0
  54. package/src/{api/preview.ts → create-worker-preview.ts} +61 -23
  55. package/src/create-worker-upload-form.ts +217 -0
  56. package/src/dev/dev.tsx +364 -0
  57. package/src/dev/local.tsx +232 -0
  58. package/src/dev/remote.tsx +203 -0
  59. package/src/dev/use-esbuild.ts +100 -0
  60. package/src/dialogs.tsx +2 -2
  61. package/src/entry.ts +208 -0
  62. package/src/environment-variables.ts +33 -0
  63. package/src/https-options.ts +122 -0
  64. package/src/index.tsx +1198 -799
  65. package/src/inspect.ts +159 -66
  66. package/src/{kv.tsx → kv.ts} +117 -65
  67. package/src/miniflare-cli/README.md +30 -0
  68. package/src/miniflare-cli/enum-keys.ts +17 -0
  69. package/src/miniflare-cli/index.ts +37 -0
  70. package/src/module-collection.ts +229 -35
  71. package/src/open-in-browser.ts +25 -0
  72. package/src/package-manager.ts +126 -0
  73. package/src/pages.tsx +58 -42
  74. package/src/parse.ts +183 -0
  75. package/src/paths.ts +26 -0
  76. package/src/proxy.ts +224 -78
  77. package/src/publish.ts +281 -292
  78. package/src/r2.ts +50 -0
  79. package/src/reporting.ts +131 -0
  80. package/src/selfsigned.d.ts +29 -0
  81. package/src/sites.tsx +224 -58
  82. package/src/tail/filters.ts +279 -0
  83. package/src/tail/index.ts +299 -0
  84. package/src/tail/printing.ts +62 -0
  85. package/src/user.tsx +273 -180
  86. package/src/whoami.tsx +69 -0
  87. package/src/{api/worker.ts → worker.ts} +45 -37
  88. package/templates/new-worker.js +15 -0
  89. package/templates/new-worker.ts +15 -0
  90. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  91. package/templates/tsconfig.json +105 -0
  92. package/wrangler-dist/cli.js +130612 -112486
  93. package/wrangler-dist/cli.js.map +3 -3
  94. package/src/__tests__/run-in-tmp.ts +0 -19
  95. package/src/__tests__/run-wrangler.ts +0 -32
  96. package/src/api/form_data.ts +0 -131
  97. package/src/config.ts +0 -487
  98. package/src/dev.tsx +0 -771
  99. package/src/tail.tsx +0 -73
package/src/inspect.ts CHANGED
@@ -1,11 +1,14 @@
1
- import type { MessageEvent } from "ws";
2
- import WebSocket, { WebSocketServer } from "ws";
3
- import type { IncomingMessage, Server, ServerResponse } from "http";
4
- import { createServer } from "http";
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
- /** The websocket server that runs on top of the proxy server. */
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 edgeworker.
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
- // Let's create the websocket server on top of the proxy server
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: serverRef.current,
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
- serverRef.current.listen(props.port);
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
- serverRef.current.close();
166
+ server.close();
154
167
  // Also disconnect any open websockets/devtools connections
155
- wsServerRef.current.clients.forEach((ws) => ws.close());
156
- wsServerRef.current.close();
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.description
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
- wsServerRef.current.clients.forEach((client) => {
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 (e.message !== "WebSocket is not open: readyState 0 (CONNECTING)") {
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(ro.description);
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
- ro.preview.entries
461
- .map(({ key, value }) => {
462
- return ` ${key.description} => ${value.description}`;
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
- ro.preview.entries
474
- .map(({ value }) => {
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
- case "weakmap":
490
- case "weakset":
491
- case "iterator":
554
+ break;
492
555
  case "generator":
493
- case "error":
494
- case "proxy":
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
- console[evt.type](...args);
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
  }
@@ -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
- import { fetchListResult, fetchResult } from "./cfetch";
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?: string
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
- key: string,
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
- args
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
- keyvalueStr: string
171
+ keyValues: KeyValue[],
172
+ progressCallback: (index: number, total: number) => void
117
173
  ) {
118
- return await fetchResult(
119
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
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
- keyStr: string
196
+ keys: string[],
197
+ progressCallback: (index: number, total: number) => void
132
198
  ) {
133
- return await fetchResult(
134
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
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
- binding,
146
- config,
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 file.
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(binding: string): boolean {
266
- return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(binding);
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
+ };