wrangler 0.0.0-e22f9d7 → 0.0.0-e273e09

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 (154) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +48 -4
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +60 -31
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +223 -28
  9. package/pages/functions/filepath-routing.ts +100 -132
  10. package/pages/functions/routes.ts +16 -18
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +37 -38
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +3487 -0
  15. package/src/__tests__/dev.test.tsx +977 -61
  16. package/src/__tests__/guess-worker-format.test.ts +85 -0
  17. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  18. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  19. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  20. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  21. package/src/__tests__/helpers/mock-bin.ts +36 -0
  22. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +56 -16
  23. package/src/__tests__/helpers/mock-console.ts +87 -0
  24. package/src/__tests__/helpers/mock-dialogs.ts +130 -0
  25. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  26. package/src/__tests__/helpers/mock-istty.ts +27 -0
  27. package/src/__tests__/helpers/mock-kv.ts +33 -0
  28. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  29. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  30. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  31. package/src/__tests__/helpers/run-in-tmp.ts +39 -0
  32. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  33. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  34. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  35. package/src/__tests__/https-options.test.ts +130 -0
  36. package/src/__tests__/index.test.ts +179 -209
  37. package/src/__tests__/init.test.ts +1637 -0
  38. package/src/__tests__/jest.setup.ts +61 -6
  39. package/src/__tests__/kv.test.ts +842 -472
  40. package/src/__tests__/logger.test.ts +115 -0
  41. package/src/__tests__/package-manager.test.ts +353 -0
  42. package/src/__tests__/pages.test.ts +754 -0
  43. package/src/__tests__/parse.test.ts +330 -0
  44. package/src/__tests__/publish.test.ts +5755 -0
  45. package/src/__tests__/r2.test.ts +233 -0
  46. package/src/__tests__/route.test.ts +45 -0
  47. package/src/__tests__/secret.test.ts +481 -0
  48. package/src/__tests__/tail.test.ts +690 -0
  49. package/src/__tests__/user.test.ts +131 -0
  50. package/src/__tests__/whoami.test.tsx +139 -0
  51. package/src/abort.d.ts +3 -0
  52. package/src/bundle.ts +197 -0
  53. package/src/cfetch/index.ts +40 -21
  54. package/src/cfetch/internal.ts +102 -29
  55. package/src/cli.ts +6 -2
  56. package/src/config/README.md +107 -0
  57. package/src/config/config.ts +234 -0
  58. package/src/config/diagnostics.ts +80 -0
  59. package/src/config/environment.ts +420 -0
  60. package/src/config/index.ts +225 -0
  61. package/src/config/validation-helpers.ts +584 -0
  62. package/src/config/validation.ts +1548 -0
  63. package/src/config-cache.ts +45 -0
  64. package/src/create-worker-preview.ts +185 -0
  65. package/src/create-worker-upload-form.ts +254 -0
  66. package/src/dev/dev-vars.ts +36 -0
  67. package/src/dev/dev.tsx +401 -0
  68. package/src/dev/local.tsx +326 -0
  69. package/src/dev/remote.tsx +237 -0
  70. package/src/dev/use-esbuild.ts +118 -0
  71. package/src/dialogs.tsx +66 -8
  72. package/src/durable.ts +102 -0
  73. package/src/entry.ts +322 -0
  74. package/src/environment-variables.ts +35 -0
  75. package/src/errors.ts +11 -0
  76. package/src/https-options.ts +123 -0
  77. package/src/index.tsx +1848 -976
  78. package/src/inspect.ts +156 -33
  79. package/src/intl-polyfill.d.ts +139 -0
  80. package/src/{kv.tsx → kv.ts} +199 -67
  81. package/src/logger.ts +66 -0
  82. package/src/miniflare-cli/README.md +30 -0
  83. package/src/miniflare-cli/enum-keys.ts +17 -0
  84. package/src/miniflare-cli/index.ts +41 -0
  85. package/src/miniflare-cli/request-context.ts +40 -0
  86. package/src/module-collection.ts +229 -35
  87. package/src/open-in-browser.ts +19 -0
  88. package/src/package-manager.ts +174 -0
  89. package/src/pages.tsx +1162 -100
  90. package/src/parse.ts +203 -0
  91. package/src/paths.ts +26 -0
  92. package/src/proxy.ts +227 -51
  93. package/src/publish.ts +633 -276
  94. package/src/r2.ts +50 -0
  95. package/src/selfsigned.d.ts +29 -0
  96. package/src/sites.tsx +270 -62
  97. package/src/tail/filters.ts +279 -0
  98. package/src/tail/index.ts +299 -0
  99. package/src/tail/printing.ts +67 -0
  100. package/src/update-check.ts +19 -0
  101. package/src/user.tsx +382 -195
  102. package/src/whoami.tsx +71 -0
  103. package/src/{api/worker.ts → worker.ts} +66 -40
  104. package/src/zones.ts +73 -0
  105. package/templates/gitignore +171 -0
  106. package/templates/new-worker-scheduled.js +17 -0
  107. package/templates/new-worker-scheduled.ts +32 -0
  108. package/templates/new-worker.js +15 -0
  109. package/templates/new-worker.ts +30 -0
  110. package/{static-asset-facade.js → templates/static-asset-facade.js} +1 -5
  111. package/templates/tsconfig.json +105 -0
  112. package/wrangler-dist/cli.js +106332 -103741
  113. package/src/__tests__/mock-dialogs.ts +0 -65
  114. package/src/__tests__/run-in-tmp.ts +0 -19
  115. package/src/__tests__/run-wrangler.ts +0 -32
  116. package/src/api/form_data.ts +0 -131
  117. package/src/api/preview.ts +0 -128
  118. package/src/config.ts +0 -487
  119. package/src/dev.tsx +0 -784
  120. package/src/tail.tsx +0 -73
  121. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  122. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  123. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  124. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  125. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  126. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  127. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  128. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  129. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  130. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  131. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  132. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  133. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  134. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  135. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  136. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  137. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  138. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  139. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  140. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  141. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  142. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  143. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  144. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  145. package/vendor/wrangler-mime/LICENSE +0 -21
  146. package/vendor/wrangler-mime/Mime.js +0 -97
  147. package/vendor/wrangler-mime/README.md +0 -187
  148. package/vendor/wrangler-mime/cli.js +0 -46
  149. package/vendor/wrangler-mime/index.js +0 -4
  150. package/vendor/wrangler-mime/lite.js +0 -4
  151. package/vendor/wrangler-mime/package.json +0 -52
  152. package/vendor/wrangler-mime/types/other.js +0 -1
  153. package/vendor/wrangler-mime/types/standard.js +0 -1
  154. package/wrangler-dist/cli.js.map +0 -7
@@ -1,23 +1,28 @@
1
- import { writeFileSync } from "fs";
1
+ import { writeFileSync } from "node:fs";
2
+ import { Headers } from "undici";
3
+ import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
2
4
  import {
3
5
  setMockResponse,
4
- setMockRawResponse,
5
6
  unsetAllMocks,
6
- createFetchResult,
7
- } from "./mock-cfetch";
8
- import { runWrangler } from "./run-wrangler";
9
- import { runInTempDir } from "./run-in-tmp";
10
-
11
- interface KVNamespaceInfo {
12
- title: string;
13
- id: string;
14
- }
7
+ unsetMockFetchKVGetValues,
8
+ setMockFetchKVGetValue,
9
+ } from "./helpers/mock-cfetch";
10
+ import { mockConsoleMethods } from "./helpers/mock-console";
11
+ import { clearConfirmMocks, mockConfirm } from "./helpers/mock-dialogs";
12
+ import { mockKeyListRequest } from "./helpers/mock-kv";
13
+ import { runInTempDir } from "./helpers/run-in-tmp";
14
+ import { runWrangler } from "./helpers/run-wrangler";
15
+ import type { KeyValue, KVNamespaceInfo, NamespaceKeyInfo } from "../kv";
15
16
 
16
17
  describe("wrangler", () => {
18
+ mockAccountId();
19
+ mockApiToken();
17
20
  runInTempDir();
21
+ const std = mockConsoleMethods();
18
22
 
19
23
  afterEach(() => {
20
24
  unsetAllMocks();
25
+ clearConfirmMocks();
21
26
  });
22
27
 
23
28
  describe("kv:namespace", () => {
@@ -36,12 +41,14 @@ describe("wrangler", () => {
36
41
  }
37
42
 
38
43
  it("should error if no namespace is given", async () => {
39
- const { error, stdout, stderr } = await runWrangler(
40
- "kv:namespace create"
44
+ await expect(
45
+ runWrangler("kv:namespace create")
46
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
47
+ `"Not enough non-option arguments: got 0, need at least 1"`
41
48
  );
42
- expect(stdout).toMatchInlineSnapshot(`""`);
43
- expect(stderr).toMatchInlineSnapshot(`
44
- "wrangler kv:namespace create <namespace>
49
+ expect(std.out).toMatchInlineSnapshot(`
50
+ "
51
+ wrangler kv:namespace create <namespace>
45
52
 
46
53
  Create a new namespace
47
54
 
@@ -54,24 +61,25 @@ describe("wrangler", () => {
54
61
  -v, --version Show version number [boolean]
55
62
 
56
63
  Options:
57
- -l, --local Run on my machine [boolean] [default: false]
58
- --env Perform on a specific environment [string]
59
- --preview Interact with a preview namespace [boolean]
64
+ -e, --env Perform on a specific environment [string]
65
+ --preview Interact with a preview namespace [boolean]"
66
+ `);
67
+ expect(std.err).toMatchInlineSnapshot(`
68
+ "X [ERROR] Not enough non-option arguments: got 0, need at least 1
60
69
 
61
- Not enough non-option arguments: got 0, need at least 1"
70
+ "
62
71
  `);
63
- expect(error).toMatchInlineSnapshot(
64
- `[Error: Not enough non-option arguments: got 0, need at least 1]`
65
- );
66
72
  });
67
73
 
68
74
  it("should error if the namespace to create contains spaces", async () => {
69
- const { error, stdout, stderr } = await runWrangler(
70
- "kv:namespace create abc def ghi"
75
+ await expect(
76
+ runWrangler("kv:namespace create abc def ghi")
77
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
78
+ `"Unknown arguments: def, ghi"`
71
79
  );
72
- expect(stdout).toMatchInlineSnapshot(`""`);
73
- expect(stderr).toMatchInlineSnapshot(`
74
- "wrangler kv:namespace create <namespace>
80
+ expect(std.out).toMatchInlineSnapshot(`
81
+ "
82
+ wrangler kv:namespace create <namespace>
75
83
 
76
84
  Create a new namespace
77
85
 
@@ -84,24 +92,26 @@ describe("wrangler", () => {
84
92
  -v, --version Show version number [boolean]
85
93
 
86
94
  Options:
87
- -l, --local Run on my machine [boolean] [default: false]
88
- --env Perform on a specific environment [string]
89
- --preview Interact with a preview namespace [boolean]
95
+ -e, --env Perform on a specific environment [string]
96
+ --preview Interact with a preview namespace [boolean]"
97
+ `);
98
+ expect(std.err).toMatchInlineSnapshot(`
99
+ "X [ERROR] Unknown arguments: def, ghi
90
100
 
91
- Unexpected additional positional arguments \\"def ghi\\"."
101
+ "
92
102
  `);
93
- expect(error).toMatchInlineSnapshot(
94
- `[Error: Unexpected additional positional arguments "def ghi".]`
95
- );
96
103
  });
97
104
 
98
105
  it("should error if the namespace to create is not valid", async () => {
99
- const { error, stdout, stderr } = await runWrangler(
100
- "kv:namespace create abc-def"
106
+ await expect(
107
+ runWrangler("kv:namespace create abc-def")
108
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
109
+ `"The namespace binding name \\"abc-def\\" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number."`
101
110
  );
102
- expect(stdout).toMatchInlineSnapshot(`""`);
103
- expect(stderr).toMatchInlineSnapshot(`
104
- "wrangler kv:namespace create <namespace>
111
+
112
+ expect(std.out).toMatchInlineSnapshot(`
113
+ "
114
+ wrangler kv:namespace create <namespace>
105
115
 
106
116
  Create a new namespace
107
117
 
@@ -114,23 +124,20 @@ describe("wrangler", () => {
114
124
  -v, --version Show version number [boolean]
115
125
 
116
126
  Options:
117
- -l, --local Run on my machine [boolean] [default: false]
118
- --env Perform on a specific environment [string]
119
- --preview Interact with a preview namespace [boolean]
127
+ -e, --env Perform on a specific environment [string]
128
+ --preview Interact with a preview namespace [boolean]"
129
+ `);
130
+ expect(std.err).toMatchInlineSnapshot(`
131
+ "X [ERROR] The namespace binding name \\"abc-def\\" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.
120
132
 
121
- The namespace binding name \\"abc-def\\" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number."
133
+ "
122
134
  `);
123
- expect(error).toMatchInlineSnapshot(
124
- `[Error: The namespace binding name "abc-def" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.]`
125
- );
126
135
  });
127
136
 
128
137
  it("should create a namespace", async () => {
129
138
  mockCreateRequest("worker-UnitTestNamespace");
130
- const { stdout } = await runWrangler(
131
- "kv:namespace create UnitTestNamespace"
132
- );
133
- expect(stdout).toMatchInlineSnapshot(`
139
+ await runWrangler("kv:namespace create UnitTestNamespace");
140
+ expect(std.out).toMatchInlineSnapshot(`
134
141
  "🌀 Creating namespace with title \\"worker-UnitTestNamespace\\"
135
142
  ✨ Success!
136
143
  Add the following to your configuration file in your kv_namespaces array:
@@ -140,10 +147,8 @@ describe("wrangler", () => {
140
147
 
141
148
  it("should create a preview namespace if configured to do so", async () => {
142
149
  mockCreateRequest("worker-UnitTestNamespace_preview");
143
- const { stdout } = await runWrangler(
144
- "kv:namespace create UnitTestNamespace --preview"
145
- );
146
- expect(stdout).toMatchInlineSnapshot(`
150
+ await runWrangler("kv:namespace create UnitTestNamespace --preview");
151
+ expect(std.out).toMatchInlineSnapshot(`
147
152
  "🌀 Creating namespace with title \\"worker-UnitTestNamespace_preview\\"
148
153
  ✨ Success!
149
154
  Add the following to your configuration file in your kv_namespaces array:
@@ -152,13 +157,11 @@ describe("wrangler", () => {
152
157
  });
153
158
 
154
159
  it("should create a namespace using configured worker name", async () => {
155
- writeFileSync("./wrangler.toml", 'name = "otherWorker"', "utf-8");
156
- mockCreateRequest("otherWorker-UnitTestNamespace");
157
- const { stdout } = await runWrangler(
158
- "kv:namespace create UnitTestNamespace"
159
- );
160
- expect(stdout).toMatchInlineSnapshot(`
161
- "🌀 Creating namespace with title \\"otherWorker-UnitTestNamespace\\"
160
+ writeFileSync("./wrangler.toml", 'name = "other-worker"', "utf-8");
161
+ mockCreateRequest("other-worker-UnitTestNamespace");
162
+ await runWrangler("kv:namespace create UnitTestNamespace");
163
+ expect(std.out).toMatchInlineSnapshot(`
164
+ "🌀 Creating namespace with title \\"other-worker-UnitTestNamespace\\"
162
165
  ✨ Success!
163
166
  Add the following to your configuration file in your kv_namespaces array:
164
167
  { binding = \\"UnitTestNamespace\\", id = \\"some-namespace-id\\" }"
@@ -167,10 +170,10 @@ describe("wrangler", () => {
167
170
 
168
171
  it("should create a namespace in an environment if configured to do so", async () => {
169
172
  mockCreateRequest("worker-customEnv-UnitTestNamespace");
170
- const { stdout } = await runWrangler(
173
+ await runWrangler(
171
174
  "kv:namespace create UnitTestNamespace --env customEnv"
172
175
  );
173
- expect(stdout).toMatchInlineSnapshot(`
176
+ expect(std.out).toMatchInlineSnapshot(`
174
177
  "🌀 Creating namespace with title \\"worker-customEnv-UnitTestNamespace\\"
175
178
  ✨ Success!
176
179
  Add the following to your configuration file in your kv_namespaces array under [env.customEnv]:
@@ -206,12 +209,10 @@ describe("wrangler", () => {
206
209
  { title: "title-2", id: "id-2" },
207
210
  ];
208
211
  mockListRequest(kvNamespaces);
209
- const { error, stdout, stderr } = await runWrangler(
210
- "kv:namespace list"
211
- );
212
- expect(error).toMatchInlineSnapshot(`undefined`);
213
- expect(stderr).toMatchInlineSnapshot(`""`);
214
- const namespaces = JSON.parse(stdout);
212
+ await runWrangler("kv:namespace list");
213
+
214
+ expect(std.err).toMatchInlineSnapshot(`""`);
215
+ const namespaces = JSON.parse(std.out);
215
216
  expect(namespaces).toEqual(kvNamespaces);
216
217
  });
217
218
 
@@ -222,8 +223,8 @@ describe("wrangler", () => {
222
223
  kvNamespaces.push({ title: "title-" + i, id: "id-" + i });
223
224
  }
224
225
  const requests = mockListRequest(kvNamespaces);
225
- const { stdout } = await runWrangler("kv:namespace list");
226
- const namespaces = JSON.parse(stdout);
226
+ await runWrangler("kv:namespace list");
227
+ const namespaces = JSON.parse(std.out);
227
228
  expect(namespaces).toEqual(kvNamespaces);
228
229
  expect(requests.count).toEqual(6);
229
230
  });
@@ -273,40 +274,29 @@ describe("wrangler", () => {
273
274
 
274
275
  it("should error if a given binding name is not in the configured kv namespaces", async () => {
275
276
  writeWranglerConfig();
276
- const { stderr } = await runWrangler(
277
- `kv:namespace delete --binding otherBinding`
278
- );
279
- expect(stderr).toMatchInlineSnapshot(`
280
- "wrangler kv:namespace delete
277
+ await expect(runWrangler("kv:namespace delete --binding otherBinding"))
278
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
279
+ "Not able to delete namespace.
280
+ A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."
281
+ `);
282
+ expect(std.err).toMatchInlineSnapshot(`
283
+ "X [ERROR] Not able to delete namespace.
281
284
 
282
- Deletes a given namespace.
285
+ A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\".
283
286
 
284
- Flags:
285
- -c, --config Path to .toml configuration file [string]
286
- -h, --help Show help [boolean]
287
- -v, --version Show version number [boolean]
288
-
289
- Options:
290
- -l, --local Run on my machine [boolean] [default: false]
291
- --binding The name of the namespace to delete [string]
292
- --namespace-id The id of the namespace to delete [string]
293
- --env Perform on a specific environment [string]
294
- --preview Interact with a preview namespace [boolean]
295
-
296
- Not able to delete namespace.
297
- A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."
287
+ "
298
288
  `);
299
289
  });
300
290
 
301
291
  it("should delete a namespace specified by binding name in a given environment", async () => {
302
292
  writeWranglerConfig();
303
293
  const requests = mockDeleteRequest("env-bound-id");
304
- const { stdout, stderr, error } = await runWrangler(
305
- `kv:namespace delete --binding someBinding --env some-environment --preview false`
294
+ await runWrangler(
295
+ "kv:namespace delete --binding someBinding --env some-environment --preview false"
306
296
  );
307
- expect(stdout).toMatchInlineSnapshot(`""`);
308
- expect(stderr).toMatchInlineSnapshot(`""`);
309
- expect(error).toMatchInlineSnapshot(`undefined`);
297
+
298
+ expect(std.out).toMatchInlineSnapshot(`""`);
299
+ expect(std.err).toMatchInlineSnapshot(`""`);
310
300
  expect(requests.count).toEqual(1);
311
301
  });
312
302
 
@@ -325,10 +315,7 @@ describe("wrangler", () => {
325
315
  describe("put", () => {
326
316
  function mockKeyPutRequest(
327
317
  expectedNamespaceId: string,
328
- expectedKey: string,
329
- expectedValue: string,
330
- expiration?: number,
331
- expirationTtl?: number
318
+ expectedKV: KeyValue
332
319
  ) {
333
320
  const requests = { count: 0 };
334
321
  setMockResponse(
@@ -338,15 +325,19 @@ describe("wrangler", () => {
338
325
  requests.count++;
339
326
  expect(accountId).toEqual("some-account-id");
340
327
  expect(namespaceId).toEqual(expectedNamespaceId);
341
- expect(key).toEqual(expectedKey);
342
- expect(body).toEqual(expectedValue);
343
- if (expiration !== undefined) {
344
- expect(query.get("expiration")).toEqual(`${expiration}`);
328
+ expect(key).toEqual(expectedKV.key);
329
+ expect(body).toEqual(expectedKV.value);
330
+ if (expectedKV.expiration !== undefined) {
331
+ expect(query.get("expiration")).toEqual(
332
+ `${expectedKV.expiration}`
333
+ );
345
334
  } else {
346
335
  expect(query.has("expiration")).toBe(false);
347
336
  }
348
- if (expirationTtl) {
349
- expect(query.get("expiration_ttl")).toEqual(`${expirationTtl}`);
337
+ if (expectedKV.expiration_ttl) {
338
+ expect(query.get("expiration_ttl")).toEqual(
339
+ `${expectedKV.expiration_ttl}`
340
+ );
350
341
  } else {
351
342
  expect(query.has("expiration_ttl")).toBe(false);
352
343
  }
@@ -357,121 +348,137 @@ describe("wrangler", () => {
357
348
  }
358
349
 
359
350
  it("should put a key in a given namespace specified by namespace-id", async () => {
360
- const requests = mockKeyPutRequest(
361
- "some-namespace-id",
362
- "my-key",
363
- "my-value"
364
- );
365
- const { error, stderr, stdout } = await runWrangler(
351
+ const requests = mockKeyPutRequest("some-namespace-id", {
352
+ key: "my-key",
353
+ value: "my-value",
354
+ });
355
+
356
+ await runWrangler(
366
357
  "kv:key put my-key my-value --namespace-id some-namespace-id"
367
358
  );
359
+
368
360
  expect(requests.count).toEqual(1);
369
- expect(stdout).toMatchInlineSnapshot(
370
- `"writing the value \\"my-value\\" to key \\"my-key\\" on namespace some-namespace-id"`
361
+ expect(std.out).toMatchInlineSnapshot(
362
+ `"Writing the value \\"my-value\\" to key \\"my-key\\" on namespace some-namespace-id."`
371
363
  );
372
- expect(stderr).toMatchInlineSnapshot(`""`);
373
- expect(error).toMatchInlineSnapshot(`undefined`);
364
+ expect(std.err).toMatchInlineSnapshot(`""`);
365
+ });
366
+
367
+ it("should encode the key in the api request to put a value", async () => {
368
+ const requests = mockKeyPutRequest("DS9", {
369
+ key: "%2Fmy-key",
370
+ value: "my-value",
371
+ });
372
+
373
+ await runWrangler("kv:key put /my-key my-value --namespace-id DS9");
374
+
375
+ expect(requests.count).toEqual(1);
376
+ expect(std.out).toMatchInlineSnapshot(
377
+ `"Writing the value \\"my-value\\" to key \\"/my-key\\" on namespace DS9."`
378
+ );
379
+ expect(std.err).toMatchInlineSnapshot(`""`);
374
380
  });
375
381
 
376
382
  it("should put a key in a given namespace specified by binding", async () => {
377
383
  writeWranglerConfig();
378
- const requests = mockKeyPutRequest("bound-id", "my-key", "my-value");
379
- const { error, stderr, stdout } = await runWrangler(
384
+ const requests = mockKeyPutRequest("bound-id", {
385
+ key: "my-key",
386
+ value: "my-value",
387
+ });
388
+ await runWrangler(
380
389
  "kv:key put my-key my-value --binding someBinding --preview false"
381
390
  );
382
- expect(stdout).toMatchInlineSnapshot(
383
- `"writing the value \\"my-value\\" to key \\"my-key\\" on namespace bound-id"`
391
+
392
+ expect(std.out).toMatchInlineSnapshot(
393
+ `"Writing the value \\"my-value\\" to key \\"my-key\\" on namespace bound-id."`
384
394
  );
385
- expect(stderr).toMatchInlineSnapshot(`""`);
386
- expect(error).toMatchInlineSnapshot(`undefined`);
395
+ expect(std.err).toMatchInlineSnapshot(`""`);
387
396
  expect(requests.count).toEqual(1);
388
397
  });
389
398
 
390
399
  it("should put a key in a given preview namespace specified by binding", async () => {
391
400
  writeWranglerConfig();
392
- const requests = mockKeyPutRequest(
393
- "preview-bound-id",
394
- "my-key",
395
- "my-value"
396
- );
397
- const { error, stderr, stdout } = await runWrangler(
401
+ const requests = mockKeyPutRequest("preview-bound-id", {
402
+ key: "my-key",
403
+ value: "my-value",
404
+ });
405
+
406
+ await runWrangler(
398
407
  "kv:key put my-key my-value --binding someBinding --preview"
399
408
  );
400
- expect(stdout).toMatchInlineSnapshot(
401
- `"writing the value \\"my-value\\" to key \\"my-key\\" on namespace preview-bound-id"`
409
+
410
+ expect(std.out).toMatchInlineSnapshot(
411
+ `"Writing the value \\"my-value\\" to key \\"my-key\\" on namespace preview-bound-id."`
402
412
  );
403
- expect(stderr).toMatchInlineSnapshot(`""`);
404
- expect(error).toMatchInlineSnapshot(`undefined`);
413
+ expect(std.err).toMatchInlineSnapshot(`""`);
405
414
  expect(requests.count).toEqual(1);
406
415
  });
407
416
 
408
417
  it("should add expiration and ttl properties when putting a key", async () => {
409
- const requests = mockKeyPutRequest(
410
- "some-namespace-id",
411
- "my-key",
412
- "my-value",
413
- 10,
414
- 20
415
- );
416
- const { error, stderr, stdout } = await runWrangler(
418
+ const requests = mockKeyPutRequest("some-namespace-id", {
419
+ key: "my-key",
420
+ value: "my-value",
421
+ expiration: 10,
422
+ expiration_ttl: 20,
423
+ });
424
+ await runWrangler(
417
425
  "kv:key put my-key my-value --namespace-id some-namespace-id --expiration 10 --ttl 20"
418
426
  );
419
427
  expect(requests.count).toEqual(1);
420
- expect(stdout).toMatchInlineSnapshot(
421
- `"writing the value \\"my-value\\" to key \\"my-key\\" on namespace some-namespace-id"`
428
+ expect(std.out).toMatchInlineSnapshot(
429
+ `"Writing the value \\"my-value\\" to key \\"my-key\\" on namespace some-namespace-id."`
422
430
  );
423
- expect(stderr).toMatchInlineSnapshot(`""`);
424
- expect(error).toMatchInlineSnapshot(`undefined`);
431
+ expect(std.err).toMatchInlineSnapshot(`""`);
425
432
  });
426
433
 
427
434
  it("should put a key to the specified environment in a given namespace", async () => {
428
435
  writeWranglerConfig();
429
- const requests = mockKeyPutRequest(
430
- "env-bound-id",
431
- "my-key",
432
- "my-value"
433
- );
434
- const { error, stderr, stdout } = await runWrangler(
436
+ const requests = mockKeyPutRequest("env-bound-id", {
437
+ key: "my-key",
438
+ value: "my-value",
439
+ });
440
+ await runWrangler(
435
441
  "kv:key put my-key my-value --binding someBinding --env some-environment --preview false"
436
442
  );
437
- expect(stdout).toMatchInlineSnapshot(
438
- `"writing the value \\"my-value\\" to key \\"my-key\\" on namespace env-bound-id"`
443
+ expect(std.out).toMatchInlineSnapshot(
444
+ `"Writing the value \\"my-value\\" to key \\"my-key\\" on namespace env-bound-id."`
439
445
  );
440
- expect(stderr).toMatchInlineSnapshot(`""`);
441
- expect(error).toMatchInlineSnapshot(`undefined`);
446
+ expect(std.err).toMatchInlineSnapshot(`""`);
442
447
  expect(requests.count).toEqual(1);
443
448
  });
444
449
 
445
450
  it("should put a key with a value loaded from a given path", async () => {
446
451
  writeFileSync("foo.txt", "file-contents", "utf-8");
447
- const requests = mockKeyPutRequest(
448
- "some-namespace-id",
449
- "my-key",
450
- "file-contents"
451
- );
452
- const { error, stderr, stdout } = await runWrangler(
452
+ const requests = mockKeyPutRequest("some-namespace-id", {
453
+ key: "my-key",
454
+ value: "file-contents",
455
+ });
456
+ await runWrangler(
453
457
  "kv:key put my-key --namespace-id some-namespace-id --path foo.txt"
454
458
  );
455
- expect(stdout).toMatchInlineSnapshot(
456
- `"writing the contents of foo.txt to the key \\"my-key\\" on namespace some-namespace-id"`
459
+ expect(std.out).toMatchInlineSnapshot(
460
+ `"Writing the contents of foo.txt to the key \\"my-key\\" on namespace some-namespace-id."`
457
461
  );
458
- expect(stderr).toMatchInlineSnapshot(`""`);
459
- expect(error).toMatchInlineSnapshot(`undefined`);
462
+ expect(std.err).toMatchInlineSnapshot(`""`);
460
463
  expect(requests.count).toEqual(1);
461
464
  });
462
465
 
463
466
  it("should error if no key is provided", async () => {
464
- const { error, stdout, stderr } = await runWrangler("kv:key put");
467
+ await expect(
468
+ runWrangler("kv:key put")
469
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
470
+ `"Not enough non-option arguments: got 0, need at least 1"`
471
+ );
465
472
 
466
- expect(stdout).toMatchInlineSnapshot(`""`);
467
- expect(stderr).toMatchInlineSnapshot(`
468
- "wrangler kv:key put <key> [value]
473
+ expect(std.out).toMatchInlineSnapshot(`
474
+ "
475
+ wrangler kv:key put <key> [value]
469
476
 
470
477
  Writes a single key/value pair to the given namespace.
471
478
 
472
479
  Positionals:
473
- key The key to write to. [string] [required]
474
- value The value to write. [string]
480
+ key The key to write to [string] [required]
481
+ value The value to write [string]
475
482
 
476
483
  Flags:
477
484
  -c, --config Path to .toml configuration file [string]
@@ -479,36 +486,37 @@ describe("wrangler", () => {
479
486
  -v, --version Show version number [boolean]
480
487
 
481
488
  Options:
482
- -l, --local Run on my machine [boolean] [default: false]
483
- --binding The binding of the namespace to write to. [string]
484
- --namespace-id The id of the namespace to write to. [string]
485
- --env Perform on a specific environment [string]
489
+ --binding The binding of the namespace to write to [string]
490
+ --namespace-id The id of the namespace to write to [string]
491
+ -e, --env Perform on a specific environment [string]
486
492
  --preview Interact with a preview namespace [boolean]
487
- --ttl Time for which the entries should be visible. [number]
493
+ --ttl Time for which the entries should be visible [number]
488
494
  --expiration Time since the UNIX epoch after which the entry expires [number]
489
- --path Read value from the file at a given path. [string]
495
+ --path Read value from the file at a given path [string]"
496
+ `);
497
+ expect(std.err).toMatchInlineSnapshot(`
498
+ "X [ERROR] Not enough non-option arguments: got 0, need at least 1
490
499
 
491
- Not enough non-option arguments: got 0, need at least 1"
500
+ "
492
501
  `);
493
- expect(error).toMatchInlineSnapshot(
494
- `[Error: Not enough non-option arguments: got 0, need at least 1]`
495
- );
496
502
  });
497
503
 
498
504
  it("should error if no binding nor namespace is provided", async () => {
499
- const { error, stdout, stderr } = await runWrangler(
500
- "kv:key put foo bar"
505
+ await expect(
506
+ runWrangler("kv:key put foo bar")
507
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
508
+ `"Exactly one of the arguments binding and namespace-id is required"`
501
509
  );
502
510
 
503
- expect(stdout).toMatchInlineSnapshot(`""`);
504
- expect(stderr).toMatchInlineSnapshot(`
505
- "wrangler kv:key put <key> [value]
511
+ expect(std.out).toMatchInlineSnapshot(`
512
+ "
513
+ wrangler kv:key put <key> [value]
506
514
 
507
515
  Writes a single key/value pair to the given namespace.
508
516
 
509
517
  Positionals:
510
- key The key to write to. [string] [required]
511
- value The value to write. [string]
518
+ key The key to write to [string] [required]
519
+ value The value to write [string]
512
520
 
513
521
  Flags:
514
522
  -c, --config Path to .toml configuration file [string]
@@ -516,36 +524,37 @@ describe("wrangler", () => {
516
524
  -v, --version Show version number [boolean]
517
525
 
518
526
  Options:
519
- -l, --local Run on my machine [boolean] [default: false]
520
- --binding The binding of the namespace to write to. [string]
521
- --namespace-id The id of the namespace to write to. [string]
522
- --env Perform on a specific environment [string]
527
+ --binding The binding of the namespace to write to [string]
528
+ --namespace-id The id of the namespace to write to [string]
529
+ -e, --env Perform on a specific environment [string]
523
530
  --preview Interact with a preview namespace [boolean]
524
- --ttl Time for which the entries should be visible. [number]
531
+ --ttl Time for which the entries should be visible [number]
525
532
  --expiration Time since the UNIX epoch after which the entry expires [number]
526
- --path Read value from the file at a given path. [string]
533
+ --path Read value from the file at a given path [string]"
534
+ `);
535
+ expect(std.err).toMatchInlineSnapshot(`
536
+ "X [ERROR] Exactly one of the arguments binding and namespace-id is required
527
537
 
528
- Exactly one of the arguments binding and namespace-id is required"
538
+ "
529
539
  `);
530
- expect(error).toMatchInlineSnapshot(
531
- `[Error: Exactly one of the arguments binding and namespace-id is required]`
532
- );
533
540
  });
534
541
 
535
542
  it("should error if both binding and namespace is provided", async () => {
536
- const { error, stdout, stderr } = await runWrangler(
537
- "kv:key put foo bar --binding x --namespace-id y"
543
+ await expect(
544
+ runWrangler("kv:key put foo bar --binding x --namespace-id y")
545
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
546
+ `"Arguments binding and namespace-id are mutually exclusive"`
538
547
  );
539
548
 
540
- expect(stdout).toMatchInlineSnapshot(`""`);
541
- expect(stderr).toMatchInlineSnapshot(`
542
- "wrangler kv:key put <key> [value]
549
+ expect(std.out).toMatchInlineSnapshot(`
550
+ "
551
+ wrangler kv:key put <key> [value]
543
552
 
544
553
  Writes a single key/value pair to the given namespace.
545
554
 
546
555
  Positionals:
547
- key The key to write to. [string] [required]
548
- value The value to write. [string]
556
+ key The key to write to [string] [required]
557
+ value The value to write [string]
549
558
 
550
559
  Flags:
551
560
  -c, --config Path to .toml configuration file [string]
@@ -553,36 +562,37 @@ describe("wrangler", () => {
553
562
  -v, --version Show version number [boolean]
554
563
 
555
564
  Options:
556
- -l, --local Run on my machine [boolean] [default: false]
557
- --binding The binding of the namespace to write to. [string]
558
- --namespace-id The id of the namespace to write to. [string]
559
- --env Perform on a specific environment [string]
565
+ --binding The binding of the namespace to write to [string]
566
+ --namespace-id The id of the namespace to write to [string]
567
+ -e, --env Perform on a specific environment [string]
560
568
  --preview Interact with a preview namespace [boolean]
561
- --ttl Time for which the entries should be visible. [number]
569
+ --ttl Time for which the entries should be visible [number]
562
570
  --expiration Time since the UNIX epoch after which the entry expires [number]
563
- --path Read value from the file at a given path. [string]
571
+ --path Read value from the file at a given path [string]"
572
+ `);
573
+ expect(std.err).toMatchInlineSnapshot(`
574
+ "X [ERROR] Arguments binding and namespace-id are mutually exclusive
564
575
 
565
- Arguments binding and namespace-id are mutually exclusive"
576
+ "
566
577
  `);
567
- expect(error).toMatchInlineSnapshot(
568
- `[Error: Arguments binding and namespace-id are mutually exclusive]`
569
- );
570
578
  });
571
579
 
572
580
  it("should error if no value nor path is provided", async () => {
573
- const { error, stdout, stderr } = await runWrangler(
574
- "kv:key put key --namespace-id 12345"
581
+ await expect(
582
+ runWrangler("kv:key put key --namespace-id 12345")
583
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
584
+ `"Exactly one of the arguments value and path is required"`
575
585
  );
576
586
 
577
- expect(stdout).toMatchInlineSnapshot(`""`);
578
- expect(stderr).toMatchInlineSnapshot(`
579
- "wrangler kv:key put <key> [value]
587
+ expect(std.out).toMatchInlineSnapshot(`
588
+ "
589
+ wrangler kv:key put <key> [value]
580
590
 
581
591
  Writes a single key/value pair to the given namespace.
582
592
 
583
593
  Positionals:
584
- key The key to write to. [string] [required]
585
- value The value to write. [string]
594
+ key The key to write to [string] [required]
595
+ value The value to write [string]
586
596
 
587
597
  Flags:
588
598
  -c, --config Path to .toml configuration file [string]
@@ -590,36 +600,37 @@ describe("wrangler", () => {
590
600
  -v, --version Show version number [boolean]
591
601
 
592
602
  Options:
593
- -l, --local Run on my machine [boolean] [default: false]
594
- --binding The binding of the namespace to write to. [string]
595
- --namespace-id The id of the namespace to write to. [string]
596
- --env Perform on a specific environment [string]
603
+ --binding The binding of the namespace to write to [string]
604
+ --namespace-id The id of the namespace to write to [string]
605
+ -e, --env Perform on a specific environment [string]
597
606
  --preview Interact with a preview namespace [boolean]
598
- --ttl Time for which the entries should be visible. [number]
607
+ --ttl Time for which the entries should be visible [number]
599
608
  --expiration Time since the UNIX epoch after which the entry expires [number]
600
- --path Read value from the file at a given path. [string]
609
+ --path Read value from the file at a given path [string]"
610
+ `);
611
+ expect(std.err).toMatchInlineSnapshot(`
612
+ "X [ERROR] Exactly one of the arguments value and path is required
601
613
 
602
- Exactly one of the arguments value and path is required"
614
+ "
603
615
  `);
604
- expect(error).toMatchInlineSnapshot(
605
- `[Error: Exactly one of the arguments value and path is required]`
606
- );
607
616
  });
608
617
 
609
618
  it("should error if both value and path is provided", async () => {
610
- const { error, stdout, stderr } = await runWrangler(
611
- "kv:key put key value --path xyz --namespace-id 12345"
619
+ await expect(
620
+ runWrangler("kv:key put key value --path xyz --namespace-id 12345")
621
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
622
+ `"Arguments value and path are mutually exclusive"`
612
623
  );
613
624
 
614
- expect(stdout).toMatchInlineSnapshot(`""`);
615
- expect(stderr).toMatchInlineSnapshot(`
616
- "wrangler kv:key put <key> [value]
625
+ expect(std.out).toMatchInlineSnapshot(`
626
+ "
627
+ wrangler kv:key put <key> [value]
617
628
 
618
629
  Writes a single key/value pair to the given namespace.
619
630
 
620
631
  Positionals:
621
- key The key to write to. [string] [required]
622
- value The value to write. [string]
632
+ key The key to write to [string] [required]
633
+ value The value to write [string]
623
634
 
624
635
  Flags:
625
636
  -c, --config Path to .toml configuration file [string]
@@ -627,285 +638,312 @@ describe("wrangler", () => {
627
638
  -v, --version Show version number [boolean]
628
639
 
629
640
  Options:
630
- -l, --local Run on my machine [boolean] [default: false]
631
- --binding The binding of the namespace to write to. [string]
632
- --namespace-id The id of the namespace to write to. [string]
633
- --env Perform on a specific environment [string]
641
+ --binding The binding of the namespace to write to [string]
642
+ --namespace-id The id of the namespace to write to [string]
643
+ -e, --env Perform on a specific environment [string]
634
644
  --preview Interact with a preview namespace [boolean]
635
- --ttl Time for which the entries should be visible. [number]
645
+ --ttl Time for which the entries should be visible [number]
636
646
  --expiration Time since the UNIX epoch after which the entry expires [number]
637
- --path Read value from the file at a given path. [string]
647
+ --path Read value from the file at a given path [string]"
648
+ `);
649
+ expect(std.err).toMatchInlineSnapshot(`
650
+ "X [ERROR] Arguments value and path are mutually exclusive
638
651
 
639
- Arguments value and path are mutually exclusive"
652
+ "
640
653
  `);
641
- expect(error).toMatchInlineSnapshot(
642
- `[Error: Arguments value and path are mutually exclusive]`
643
- );
644
654
  });
645
655
 
646
656
  it("should error if a given binding name is not in the configured kv namespaces", async () => {
647
657
  writeWranglerConfig();
648
- const { error, stdout, stderr } = await runWrangler(
649
- `kv:key put key value --binding otherBinding`
650
- );
651
- expect(stdout).toMatchInlineSnapshot(`""`);
652
- expect(stderr).toMatchInlineSnapshot(
658
+ await expect(
659
+ runWrangler("kv:key put key value --binding otherBinding")
660
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
653
661
  `"A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."`
654
662
  );
655
- expect(error).toMatchInlineSnapshot(
656
- `[Error: A namespace with binding name "otherBinding" was not found in the configured "kv_namespaces".]`
657
- );
663
+
664
+ expect(std.out).toMatchInlineSnapshot(`
665
+ "
666
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
667
+ `);
668
+ expect(std.err).toMatchInlineSnapshot(`
669
+ "X [ERROR] A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\".
670
+
671
+ "
672
+ `);
658
673
  });
659
674
 
660
675
  it("should error if a given binding has both preview and non-preview and --preview is not specified", async () => {
661
676
  writeWranglerConfig();
662
- const requests = mockKeyPutRequest(
663
- "preview-bound-id",
664
- "my-key",
665
- "my-value"
666
- );
667
- const { error, stderr, stdout } = await runWrangler(
668
- "kv:key put my-key my-value --binding someBinding"
669
- );
670
- expect(stdout).toMatchInlineSnapshot(`""`);
671
- expect(stderr).toMatchInlineSnapshot(
677
+ const requests = mockKeyPutRequest("preview-bound-id", {
678
+ key: "my-key",
679
+ value: "my-value",
680
+ });
681
+ await expect(
682
+ runWrangler("kv:key put my-key my-value --binding someBinding")
683
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
672
684
  `"someBinding has both a namespace ID and a preview ID. Specify \\"--preview\\" or \\"--preview false\\" to avoid writing data to the wrong namespace."`
673
685
  );
674
- expect(error).toMatchInlineSnapshot(
675
- `[Error: someBinding has both a namespace ID and a preview ID. Specify "--preview" or "--preview false" to avoid writing data to the wrong namespace.]`
676
- );
686
+ expect(std.out).toMatchInlineSnapshot(`
687
+ "
688
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
689
+ `);
690
+ expect(std.err).toMatchInlineSnapshot(`
691
+ "X [ERROR] someBinding has both a namespace ID and a preview ID. Specify \\"--preview\\" or \\"--preview false\\" to avoid writing data to the wrong namespace.
692
+
693
+ "
694
+ `);
677
695
  expect(requests.count).toEqual(0);
678
696
  });
679
697
  });
680
698
 
681
699
  describe("list", () => {
682
- function mockKeyListRequest(
683
- expectedNamespaceId: string,
684
- expectedKeys: string[],
685
- keysPerRequest = 1000
686
- ) {
687
- const requests = { count: 0 };
688
- setMockRawResponse(
689
- "/accounts/:accountId/storage/kv/namespaces/:namespaceId/keys",
690
- ([_url, accountId, namespaceId], _init, query) => {
691
- requests.count++;
692
- expect(accountId).toEqual("some-account-id");
693
- expect(namespaceId).toEqual(expectedNamespaceId);
694
- if (expectedKeys.length <= keysPerRequest) {
695
- return createFetchResult(expectedKeys);
696
- } else {
697
- const start = parseInt(query.get("cursor") ?? "0") || 0;
698
- const end = start + keysPerRequest;
699
- const cursor = end < expectedKeys.length ? end : undefined;
700
- return createFetchResult(
701
- expectedKeys.slice(start, end),
702
- true,
703
- [],
704
- [],
705
- { cursor }
706
- );
707
- }
708
- }
709
- );
710
- return requests;
711
- }
712
-
713
700
  it("should list the keys of a namespace specified by namespace-id", async () => {
714
- const keys = ["key-1", "key-2", "key-3"];
701
+ const keys = [
702
+ { name: "key-1" },
703
+ { name: "key-2", expiration: 123456789 },
704
+ { name: "key-3", expiration_ttl: 666 },
705
+ ];
715
706
  mockKeyListRequest("some-namespace-id", keys);
716
- const { error, stdout, stderr } = await runWrangler(
717
- "kv:key list --namespace-id some-namespace-id"
718
- );
719
- expect(error).toMatchInlineSnapshot(`undefined`);
720
- expect(stderr).toMatchInlineSnapshot(`""`);
721
- expect(stdout).toMatchInlineSnapshot(`
722
- "key-1
723
- key-2
724
- key-3"
707
+ await runWrangler("kv:key list --namespace-id some-namespace-id");
708
+ expect(std.err).toMatchInlineSnapshot(`""`);
709
+ expect(std.out).toMatchInlineSnapshot(`
710
+ "[
711
+ {
712
+ \\"name\\": \\"key-1\\"
713
+ },
714
+ {
715
+ \\"name\\": \\"key-2\\",
716
+ \\"expiration\\": 123456789
717
+ },
718
+ {
719
+ \\"name\\": \\"key-3\\",
720
+ \\"expiration_ttl\\": 666
721
+ }
722
+ ]"
725
723
  `);
726
724
  });
727
725
 
728
726
  it("should list the keys of a namespace specified by binding", async () => {
729
727
  writeWranglerConfig();
730
- const keys = ["key-1", "key-2", "key-3"];
728
+ const keys = [{ name: "key-1" }, { name: "key-2" }, { name: "key-3" }];
731
729
  mockKeyListRequest("bound-id", keys);
732
- const { error, stdout, stderr } = await runWrangler(
733
- "kv:key list --binding someBinding"
734
- );
735
- expect(error).toMatchInlineSnapshot(`undefined`);
736
- expect(stderr).toMatchInlineSnapshot(`""`);
737
- expect(stdout).toMatchInlineSnapshot(`
738
- "key-1
739
- key-2
740
- key-3"
730
+
731
+ await runWrangler("kv:key list --binding someBinding");
732
+ expect(std.err).toMatchInlineSnapshot(`""`);
733
+ expect(std.out).toMatchInlineSnapshot(`
734
+ "[
735
+ {
736
+ \\"name\\": \\"key-1\\"
737
+ },
738
+ {
739
+ \\"name\\": \\"key-2\\"
740
+ },
741
+ {
742
+ \\"name\\": \\"key-3\\"
743
+ }
744
+ ]"
741
745
  `);
742
746
  });
743
747
 
744
748
  it("should list the keys of a preview namespace specified by binding", async () => {
745
749
  writeWranglerConfig();
746
- const keys = ["key-1", "key-2", "key-3"];
750
+ const keys = [{ name: "key-1" }, { name: "key-2" }, { name: "key-3" }];
747
751
  mockKeyListRequest("preview-bound-id", keys);
748
- const { error, stdout, stderr } = await runWrangler(
749
- "kv:key list --binding someBinding --preview"
750
- );
751
- expect(error).toMatchInlineSnapshot(`undefined`);
752
- expect(stderr).toMatchInlineSnapshot(`""`);
753
- expect(stdout).toMatchInlineSnapshot(`
754
- "key-1
755
- key-2
756
- key-3"
752
+ await runWrangler("kv:key list --binding someBinding --preview");
753
+ expect(std.err).toMatchInlineSnapshot(`""`);
754
+ expect(std.out).toMatchInlineSnapshot(`
755
+ "[
756
+ {
757
+ \\"name\\": \\"key-1\\"
758
+ },
759
+ {
760
+ \\"name\\": \\"key-2\\"
761
+ },
762
+ {
763
+ \\"name\\": \\"key-3\\"
764
+ }
765
+ ]"
757
766
  `);
758
767
  });
759
768
 
760
769
  it("should list the keys of a namespace specified by binding, in a given environment", async () => {
761
770
  writeWranglerConfig();
762
- const keys = ["key-1", "key-2", "key-3"];
771
+ const keys = [{ name: "key-1" }, { name: "key-2" }, { name: "key-3" }];
763
772
  mockKeyListRequest("env-bound-id", keys);
764
- const { error, stdout, stderr } = await runWrangler(
773
+ await runWrangler(
765
774
  "kv:key list --binding someBinding --env some-environment"
766
775
  );
767
- expect(error).toMatchInlineSnapshot(`undefined`);
768
- expect(stderr).toMatchInlineSnapshot(`""`);
769
- expect(stdout).toMatchInlineSnapshot(`
770
- "key-1
771
- key-2
772
- key-3"
776
+ expect(std.err).toMatchInlineSnapshot(`""`);
777
+ expect(std.out).toMatchInlineSnapshot(`
778
+ "[
779
+ {
780
+ \\"name\\": \\"key-1\\"
781
+ },
782
+ {
783
+ \\"name\\": \\"key-2\\"
784
+ },
785
+ {
786
+ \\"name\\": \\"key-3\\"
787
+ }
788
+ ]"
773
789
  `);
774
790
  });
775
791
 
776
792
  it("should list the keys of a preview namespace specified by binding, in a given environment", async () => {
777
793
  writeWranglerConfig();
778
- const keys = ["key-1", "key-2", "key-3"];
794
+ const keys = [{ name: "key-1" }, { name: "key-2" }, { name: "key-3" }];
779
795
  mockKeyListRequest("preview-env-bound-id", keys);
780
- const { error, stdout, stderr } = await runWrangler(
796
+ await runWrangler(
781
797
  "kv:key list --binding someBinding --preview --env some-environment"
782
798
  );
783
- expect(error).toMatchInlineSnapshot(`undefined`);
784
- expect(stderr).toMatchInlineSnapshot(`""`);
785
- expect(stdout).toMatchInlineSnapshot(`
786
- "key-1
787
- key-2
788
- key-3"
799
+ expect(std.err).toMatchInlineSnapshot(`""`);
800
+ expect(std.out).toMatchInlineSnapshot(`
801
+ "[
802
+ {
803
+ \\"name\\": \\"key-1\\"
804
+ },
805
+ {
806
+ \\"name\\": \\"key-2\\"
807
+ },
808
+ {
809
+ \\"name\\": \\"key-3\\"
810
+ }
811
+ ]"
789
812
  `);
790
813
  });
791
814
 
792
- it("should make multiple requests for paginated results", async () => {
793
- // Create a lot of mock keys, so that the fetch requests will be paginated
794
- const keys: string[] = [];
795
- for (let i = 0; i < 550; i++) {
796
- keys.push("key-" + i);
797
- }
798
- // Ask for the keys in pages of size 100.
799
- const requests = mockKeyListRequest("some-namespace-id", keys, 100);
800
- const { stdout, stderr, error } = await runWrangler(
801
- "kv:key list --namespace-id some-namespace-id --limit 100"
802
- );
803
- expect(error).toMatchInlineSnapshot(`undefined`);
804
- expect(stderr).toMatchInlineSnapshot(`""`);
805
- expect(stdout).toEqual(keys.join("\n"));
806
- expect(requests.count).toEqual(6);
807
- });
815
+ // We'll run the next test with variations on the cursor
816
+ // that's returned on cloudflare's API after all results
817
+ // have been drained.
818
+ for (const blankCursorValue of [undefined, null, ""] as [
819
+ undefined,
820
+ null,
821
+ ""
822
+ ]) {
823
+ describe(`cursor - ${blankCursorValue}`, () => {
824
+ it("should make multiple requests for paginated results", async () => {
825
+ // Create a lot of mock keys, so that the fetch requests will be paginated
826
+ const keys: NamespaceKeyInfo[] = [];
827
+ for (let i = 0; i < 550; i++) {
828
+ keys.push({ name: "key-" + i });
829
+ }
830
+ // Ask for the keys in pages of size 100.
831
+ const requests = mockKeyListRequest(
832
+ "some-namespace-id",
833
+ keys,
834
+ 100,
835
+ blankCursorValue
836
+ );
837
+ await runWrangler("kv:key list --namespace-id some-namespace-id");
838
+ expect(std.err).toMatchInlineSnapshot(`""`);
839
+ expect(JSON.parse(std.out)).toEqual(keys);
840
+ expect(requests.count).toEqual(6);
841
+ });
842
+ });
843
+ }
808
844
 
809
845
  it("should error if a given binding name is not in the configured kv namespaces", async () => {
810
846
  writeWranglerConfig();
811
- const { error, stdout, stderr } = await runWrangler(
812
- "kv:key list --binding otherBinding"
813
- );
814
- expect(error).toMatchInlineSnapshot(
815
- `[Error: A namespace with binding name "otherBinding" was not found in the configured "kv_namespaces".]`
816
- );
817
- expect(stderr).toMatchInlineSnapshot(
847
+ await expect(
848
+ runWrangler("kv:key list --binding otherBinding")
849
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
818
850
  `"A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."`
819
851
  );
820
- expect(stdout).toMatchInlineSnapshot(`""`);
852
+ expect(std.err).toMatchInlineSnapshot(`
853
+ "X [ERROR] A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\".
854
+
855
+ "
856
+ `);
857
+ expect(std.out).toMatchInlineSnapshot(`
858
+ "
859
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
860
+ `);
821
861
  });
822
862
  });
823
863
 
824
864
  describe("get", () => {
825
- function mockKeyGetRequest(
826
- expectedNamespaceId: string,
827
- expectedKey: string,
828
- expectedValue: string
829
- ) {
830
- const requests = { count: 0 };
831
- setMockRawResponse(
832
- "/accounts/:accountId/storage/kv/namespaces/:namespaceId/values/:key",
833
- ([_url, accountId, namespaceId, key]) => {
834
- requests.count++;
835
- expect(accountId).toEqual("some-account-id");
836
- expect(namespaceId).toEqual(expectedNamespaceId);
837
- expect(key).toEqual(expectedKey);
838
- return expectedValue;
839
- }
840
- );
841
- return requests;
842
- }
865
+ afterEach(() => {
866
+ unsetMockFetchKVGetValues();
867
+ });
843
868
 
844
869
  it("should get a key in a given namespace specified by namespace-id", async () => {
845
- const requests = mockKeyGetRequest(
870
+ setMockFetchKVGetValue(
871
+ "some-account-id",
846
872
  "some-namespace-id",
847
873
  "my-key",
848
874
  "my-value"
849
875
  );
850
- const { error, stderr, stdout } = await runWrangler(
851
- "kv:key get my-key --namespace-id some-namespace-id"
852
- );
853
- expect(stdout).toMatchInlineSnapshot(`"my-value"`);
854
- expect(stderr).toMatchInlineSnapshot(`""`);
855
- expect(error).toMatchInlineSnapshot(`undefined`);
856
- expect(requests.count).toEqual(1);
876
+ await runWrangler("kv:key get my-key --namespace-id some-namespace-id");
877
+ expect(std.out).toMatchInlineSnapshot(`"my-value"`);
878
+ expect(std.err).toMatchInlineSnapshot(`""`);
857
879
  });
858
880
 
859
881
  it("should get a key in a given namespace specified by binding", async () => {
860
882
  writeWranglerConfig();
861
- const requests = mockKeyGetRequest("bound-id", "my-key", "my-value");
862
- const { error, stderr, stdout } = await runWrangler(
883
+ setMockFetchKVGetValue(
884
+ "some-account-id",
885
+ "bound-id",
886
+ "my-key",
887
+ "my-value"
888
+ );
889
+ await runWrangler(
863
890
  "kv:key get my-key --binding someBinding --preview false"
864
891
  );
865
- expect(stdout).toMatchInlineSnapshot(`"my-value"`);
866
- expect(stderr).toMatchInlineSnapshot(`""`);
867
- expect(error).toMatchInlineSnapshot(`undefined`);
868
- expect(requests.count).toEqual(1);
892
+ expect(std.out).toMatchInlineSnapshot(`"my-value"`);
893
+ expect(std.err).toMatchInlineSnapshot(`""`);
869
894
  });
870
895
 
871
896
  it("should get a key in a given preview namespace specified by binding", async () => {
872
897
  writeWranglerConfig();
873
- const requests = mockKeyGetRequest(
898
+ setMockFetchKVGetValue(
899
+ "some-account-id",
874
900
  "preview-bound-id",
875
901
  "my-key",
876
902
  "my-value"
877
903
  );
878
- const { error, stderr, stdout } = await runWrangler(
879
- "kv:key get my-key --binding someBinding --preview"
880
- );
881
- expect(stdout).toMatchInlineSnapshot(`"my-value"`);
882
- expect(stderr).toMatchInlineSnapshot(`""`);
883
- expect(error).toMatchInlineSnapshot(`undefined`);
884
- expect(requests.count).toEqual(1);
904
+ await runWrangler("kv:key get my-key --binding someBinding --preview");
905
+ expect(std.out).toMatchInlineSnapshot(`"my-value"`);
906
+ expect(std.err).toMatchInlineSnapshot(`""`);
885
907
  });
886
908
 
887
909
  it("should get a key for the specified environment in a given namespace", async () => {
888
910
  writeWranglerConfig();
889
- const requests = mockKeyGetRequest(
911
+ setMockFetchKVGetValue(
912
+ "some-account-id",
890
913
  "env-bound-id",
891
914
  "my-key",
892
915
  "my-value"
893
916
  );
894
- const { error, stderr, stdout } = await runWrangler(
895
- "kv:key get my-key my-value --binding someBinding --env some-environment --preview false"
917
+ await runWrangler(
918
+ "kv:key get my-key --binding someBinding --env some-environment --preview false"
896
919
  );
897
- expect(stdout).toMatchInlineSnapshot(`"my-value"`);
898
- expect(stderr).toMatchInlineSnapshot(`""`);
899
- expect(error).toMatchInlineSnapshot(`undefined`);
900
- expect(requests.count).toEqual(1);
920
+ expect(std.out).toMatchInlineSnapshot(`"my-value"`);
921
+ expect(std.err).toMatchInlineSnapshot(`""`);
901
922
  });
902
923
 
903
- it("should error if no key is provided", async () => {
904
- const { error, stdout, stderr } = await runWrangler("kv:key get");
924
+ it("should encode the key in the api request to get a value", async () => {
925
+ setMockFetchKVGetValue(
926
+ "some-account-id",
927
+ "some-namespace-id",
928
+ "%2Fmy%2Ckey",
929
+ "my-value"
930
+ );
931
+ await runWrangler(
932
+ "kv:key get /my,key --namespace-id some-namespace-id"
933
+ );
934
+ expect(std.out).toMatchInlineSnapshot(`"my-value"`);
935
+ expect(std.err).toMatchInlineSnapshot(`""`);
936
+ });
905
937
 
906
- expect(stdout).toMatchInlineSnapshot(`""`);
907
- expect(stderr).toMatchInlineSnapshot(`
908
- "wrangler kv:key get <key>
938
+ it("should error if no key is provided", async () => {
939
+ await expect(
940
+ runWrangler("kv:key get")
941
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
942
+ `"Not enough non-option arguments: got 0, need at least 1"`
943
+ );
944
+ expect(std.out).toMatchInlineSnapshot(`
945
+ "
946
+ wrangler kv:key get <key>
909
947
 
910
948
  Reads a single value by key from the given namespace.
911
949
 
@@ -918,25 +956,27 @@ describe("wrangler", () => {
918
956
  -v, --version Show version number [boolean]
919
957
 
920
958
  Options:
921
- -l, --local Run on my machine [boolean] [default: false]
922
959
  --binding The name of the namespace to get from [string]
923
960
  --namespace-id The id of the namespace to get from [string]
924
- --env Perform on a specific environment [string]
925
- --preview Interact with a preview namespace [boolean] [default: false]
961
+ -e, --env Perform on a specific environment [string]
962
+ --preview Interact with a preview namespace [boolean] [default: false]"
963
+ `);
964
+ expect(std.err).toMatchInlineSnapshot(`
965
+ "X [ERROR] Not enough non-option arguments: got 0, need at least 1
926
966
 
927
- Not enough non-option arguments: got 0, need at least 1"
967
+ "
928
968
  `);
929
- expect(error).toMatchInlineSnapshot(
930
- `[Error: Not enough non-option arguments: got 0, need at least 1]`
931
- );
932
969
  });
933
970
 
934
971
  it("should error if no binding nor namespace is provided", async () => {
935
- const { error, stdout, stderr } = await runWrangler("kv:key get foo");
936
-
937
- expect(stdout).toMatchInlineSnapshot(`""`);
938
- expect(stderr).toMatchInlineSnapshot(`
939
- "wrangler kv:key get <key>
972
+ await expect(
973
+ runWrangler("kv:key get foo")
974
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
975
+ `"Exactly one of the arguments binding and namespace-id is required"`
976
+ );
977
+ expect(std.out).toMatchInlineSnapshot(`
978
+ "
979
+ wrangler kv:key get <key>
940
980
 
941
981
  Reads a single value by key from the given namespace.
942
982
 
@@ -949,27 +989,28 @@ describe("wrangler", () => {
949
989
  -v, --version Show version number [boolean]
950
990
 
951
991
  Options:
952
- -l, --local Run on my machine [boolean] [default: false]
953
992
  --binding The name of the namespace to get from [string]
954
993
  --namespace-id The id of the namespace to get from [string]
955
- --env Perform on a specific environment [string]
956
- --preview Interact with a preview namespace [boolean] [default: false]
994
+ -e, --env Perform on a specific environment [string]
995
+ --preview Interact with a preview namespace [boolean] [default: false]"
996
+ `);
997
+ expect(std.err).toMatchInlineSnapshot(`
998
+ "X [ERROR] Exactly one of the arguments binding and namespace-id is required
957
999
 
958
- Exactly one of the arguments binding and namespace-id is required"
1000
+ "
959
1001
  `);
960
- expect(error).toMatchInlineSnapshot(
961
- `[Error: Exactly one of the arguments binding and namespace-id is required]`
962
- );
963
1002
  });
964
1003
 
965
1004
  it("should error if both binding and namespace is provided", async () => {
966
- const { error, stdout, stderr } = await runWrangler(
967
- "kv:key get foo --binding x --namespace-id y"
1005
+ await expect(
1006
+ runWrangler("kv:key get foo --binding x --namespace-id y")
1007
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1008
+ `"Arguments binding and namespace-id are mutually exclusive"`
968
1009
  );
969
1010
 
970
- expect(stdout).toMatchInlineSnapshot(`""`);
971
- expect(stderr).toMatchInlineSnapshot(`
972
- "wrangler kv:key get <key>
1011
+ expect(std.out).toMatchInlineSnapshot(`
1012
+ "
1013
+ wrangler kv:key get <key>
973
1014
 
974
1015
  Reads a single value by key from the given namespace.
975
1016
 
@@ -982,31 +1023,34 @@ describe("wrangler", () => {
982
1023
  -v, --version Show version number [boolean]
983
1024
 
984
1025
  Options:
985
- -l, --local Run on my machine [boolean] [default: false]
986
1026
  --binding The name of the namespace to get from [string]
987
1027
  --namespace-id The id of the namespace to get from [string]
988
- --env Perform on a specific environment [string]
989
- --preview Interact with a preview namespace [boolean] [default: false]
1028
+ -e, --env Perform on a specific environment [string]
1029
+ --preview Interact with a preview namespace [boolean] [default: false]"
1030
+ `);
1031
+ expect(std.err).toMatchInlineSnapshot(`
1032
+ "X [ERROR] Arguments binding and namespace-id are mutually exclusive
990
1033
 
991
- Arguments binding and namespace-id are mutually exclusive"
1034
+ "
992
1035
  `);
993
- expect(error).toMatchInlineSnapshot(
994
- `[Error: Arguments binding and namespace-id are mutually exclusive]`
995
- );
996
1036
  });
997
1037
 
998
1038
  it("should error if a given binding name is not in the configured kv namespaces", async () => {
999
1039
  writeWranglerConfig();
1000
- const { error, stdout, stderr } = await runWrangler(
1001
- `kv:key get key --binding otherBinding`
1002
- );
1003
- expect(stdout).toMatchInlineSnapshot(`""`);
1004
- expect(stderr).toMatchInlineSnapshot(
1040
+ await expect(
1041
+ runWrangler("kv:key get key --binding otherBinding")
1042
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1005
1043
  `"A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."`
1006
1044
  );
1007
- expect(error).toMatchInlineSnapshot(
1008
- `[Error: A namespace with binding name "otherBinding" was not found in the configured "kv_namespaces".]`
1009
- );
1045
+ expect(std.out).toMatchInlineSnapshot(`
1046
+ "
1047
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
1048
+ `);
1049
+ expect(std.err).toMatchInlineSnapshot(`
1050
+ "X [ERROR] A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\".
1051
+
1052
+ "
1053
+ `);
1010
1054
  });
1011
1055
  });
1012
1056
 
@@ -1038,6 +1082,16 @@ describe("wrangler", () => {
1038
1082
  expect(requests.count).toEqual(1);
1039
1083
  });
1040
1084
 
1085
+ it("should encode the key in the api request to delete a value", async () => {
1086
+ const requests = mockDeleteRequest("voyager", "%2FNCC-74656");
1087
+ await runWrangler(`kv:key delete --namespace-id voyager /NCC-74656`);
1088
+ expect(requests.count).toEqual(1);
1089
+ expect(std.out).toMatchInlineSnapshot(
1090
+ `"Deleting the key \\"/NCC-74656\\" on namespace voyager."`
1091
+ );
1092
+ expect(std.err).toMatchInlineSnapshot(`""`);
1093
+ });
1094
+
1041
1095
  it("should delete a key in a namespace specified by binding name", async () => {
1042
1096
  writeWranglerConfig();
1043
1097
  const requests = mockDeleteRequest("bound-id", "someKey");
@@ -1058,25 +1112,29 @@ describe("wrangler", () => {
1058
1112
 
1059
1113
  it("should error if a given binding name is not in the configured kv namespaces", async () => {
1060
1114
  writeWranglerConfig();
1061
- const { stderr } = await runWrangler(
1062
- `kv:key delete --binding otherBinding someKey`
1063
- );
1064
- expect(stderr).toMatchInlineSnapshot(
1115
+ await expect(
1116
+ runWrangler(`kv:key delete --binding otherBinding someKey`)
1117
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1065
1118
  `"A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\"."`
1066
1119
  );
1120
+
1121
+ expect(std.err).toMatchInlineSnapshot(`
1122
+ "X [ERROR] A namespace with binding name \\"otherBinding\\" was not found in the configured \\"kv_namespaces\\".
1123
+
1124
+ "
1125
+ `);
1067
1126
  });
1068
1127
 
1069
1128
  it("should delete a key in a namespace specified by binding name in a given environment", async () => {
1070
1129
  writeWranglerConfig();
1071
1130
  const requests = mockDeleteRequest("env-bound-id", "someKey");
1072
- const { stdout, stderr, error } = await runWrangler(
1131
+ await runWrangler(
1073
1132
  `kv:key delete --binding someBinding --env some-environment --preview false someKey`
1074
1133
  );
1075
- expect(stdout).toMatchInlineSnapshot(
1076
- `"deleting the key \\"someKey\\" on namespace env-bound-id"`
1134
+ expect(std.out).toMatchInlineSnapshot(
1135
+ `"Deleting the key \\"someKey\\" on namespace env-bound-id."`
1077
1136
  );
1078
- expect(stderr).toMatchInlineSnapshot(`""`);
1079
- expect(error).toMatchInlineSnapshot(`undefined`);
1137
+ expect(std.err).toMatchInlineSnapshot(`""`);
1080
1138
  expect(requests.count).toEqual(1);
1081
1139
  });
1082
1140
 
@@ -1090,13 +1148,325 @@ describe("wrangler", () => {
1090
1148
  });
1091
1149
  });
1092
1150
  });
1151
+
1152
+ describe("kv:bulk", () => {
1153
+ describe("put", () => {
1154
+ function mockPutRequest(
1155
+ expectedNamespaceId: string,
1156
+ expectedKeyValues: KeyValue[]
1157
+ ) {
1158
+ const requests = { count: 0 };
1159
+ setMockResponse(
1160
+ "/accounts/:accountId/storage/kv/namespaces/:namespaceId/bulk",
1161
+ "PUT",
1162
+ ([_url, accountId, namespaceId], { body }) => {
1163
+ requests.count++;
1164
+ expect(accountId).toEqual("some-account-id");
1165
+ expect(namespaceId).toEqual(expectedNamespaceId);
1166
+ expect(JSON.parse(body as string)).toEqual(
1167
+ expectedKeyValues.slice(
1168
+ (requests.count - 1) * 5000,
1169
+ requests.count * 5000
1170
+ )
1171
+ );
1172
+ return null;
1173
+ }
1174
+ );
1175
+ return requests;
1176
+ }
1177
+
1178
+ it("should put the key-values parsed from a file", async () => {
1179
+ const keyValues: KeyValue[] = [
1180
+ { key: "someKey1", value: "someValue1" },
1181
+ { key: "ns:someKey2", value: "123", base64: true },
1182
+ { key: "someKey3", value: "someValue3", expiration: 100 },
1183
+ { key: "someKey4", value: "someValue4", expiration_ttl: 500 },
1184
+ ];
1185
+ writeFileSync("./keys.json", JSON.stringify(keyValues));
1186
+ const requests = mockPutRequest("some-namespace-id", keyValues);
1187
+ await runWrangler(
1188
+ `kv:bulk put --namespace-id some-namespace-id keys.json`
1189
+ );
1190
+ expect(requests.count).toEqual(1);
1191
+ expect(std.out).toMatchInlineSnapshot(`"Success!"`);
1192
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1193
+ expect(std.err).toMatchInlineSnapshot(`""`);
1194
+ });
1195
+
1196
+ it("should put the key-values in batches of 5000 parsed from a file", async () => {
1197
+ const keyValues: KeyValue[] = new Array(12000).fill({
1198
+ key: "someKey1",
1199
+ value: "someValue1",
1200
+ });
1201
+ writeFileSync("./keys.json", JSON.stringify(keyValues));
1202
+ const requests = mockPutRequest("some-namespace-id", keyValues);
1203
+ await runWrangler(
1204
+ `kv:bulk put --namespace-id some-namespace-id keys.json`
1205
+ );
1206
+ expect(requests.count).toEqual(3);
1207
+ expect(std.out).toMatchInlineSnapshot(`
1208
+ "Uploaded 0% (0 out of 12,000)
1209
+ Uploaded 41% (5,000 out of 12,000)
1210
+ Uploaded 83% (10,000 out of 12,000)
1211
+ Uploaded 100% (12,000 out of 12,000)
1212
+ Success!"
1213
+ `);
1214
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1215
+ expect(std.err).toMatchInlineSnapshot(`""`);
1216
+ });
1217
+
1218
+ it("should error if the file is not a JSON array", async () => {
1219
+ const keyValues = { key: "someKey1", value: "someValue1" };
1220
+ writeFileSync("./keys.json", JSON.stringify(keyValues));
1221
+ await expect(
1222
+ runWrangler(`kv:bulk put --namespace-id some-namespace-id keys.json`)
1223
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1224
+ "Unexpected JSON input from \\"keys.json\\".
1225
+ Expected an array of key-value objects but got type \\"object\\"."
1226
+ `);
1227
+ expect(std.out).toMatchInlineSnapshot(`
1228
+ "
1229
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
1230
+ `);
1231
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1232
+ });
1233
+
1234
+ it("should error if the array contains items that are not key-value objects", async () => {
1235
+ const keyValues = [
1236
+ 123,
1237
+ "a string",
1238
+ { key: "someKey" },
1239
+ { value: "someValue" },
1240
+ // add a valid object here to make sure it's not included
1241
+ { key: "someKey1", value: "someValue1" },
1242
+ // this one will only add a warning
1243
+ { key: "someKey1", value: "someValue1", invalid: true },
1244
+ // back to the invalid ones
1245
+ { key: 123, value: "somevalue" },
1246
+ { key: "somekey", value: 123 },
1247
+ { key: "someKey1", value: "someValue1", expiration: "string" },
1248
+ { key: "someKey1", value: "someValue1", expiration_ttl: "string" },
1249
+ {
1250
+ key: 123,
1251
+ value: {
1252
+ a: {
1253
+ nested: "object",
1254
+ },
1255
+ },
1256
+ },
1257
+ { key: "someKey1", value: "someValue1", metadata: 123 },
1258
+ { key: "someKey1", value: "someValue1", base64: "string" },
1259
+ ];
1260
+ writeFileSync("./keys.json", JSON.stringify(keyValues));
1261
+ await expect(
1262
+ runWrangler(`kv:bulk put --namespace-id some-namespace-id keys.json`)
1263
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1264
+ "Unexpected JSON input from \\"keys.json\\".
1265
+ Each item in the array should be an object that matches:
1266
+
1267
+ interface KeyValue {
1268
+ key: string;
1269
+ value: string;
1270
+ expiration?: number;
1271
+ expiration_ttl?: number;
1272
+ metadata?: object;
1273
+ base64?: boolean;
1274
+ }
1275
+
1276
+ The item at index 0 is 123
1277
+ The item at index 1 is \\"a string\\"
1278
+ The item at index 2 is {\\"key\\":\\"someKey\\"}
1279
+ The item at index 3 is {\\"value\\":\\"someValue\\"}
1280
+ The item at index 6 is {\\"key\\":123,\\"value\\":\\"somevalue\\"}
1281
+ The item at index 7 is {\\"key\\":\\"somekey\\",\\"value\\":123}
1282
+ The item at index 8 is {\\"key\\":\\"someKey1\\",\\"value\\":\\"someValue1\\",\\"expiration\\":\\"string\\"}
1283
+ The item at index 9 is {\\"key\\":\\"someKey1\\",\\"value\\":\\"someValue1\\",\\"expiration_ttl\\":\\"string\\"}
1284
+ The item at index 10 is {\\"key\\":123,\\"value\\":{\\"a\\":{\\"nested\\":\\"object\\"}}}
1285
+ The item at index 11 is {\\"key\\":\\"someKey1\\",\\"value\\":\\"someValue1\\",\\"metadata\\":123}
1286
+ The item at index 12 is {\\"key\\":\\"someKey1\\",\\"value\\":\\"someValue1\\",\\"base64\\":\\"string\\"}"
1287
+ `);
1288
+
1289
+ expect(std.out).toMatchInlineSnapshot(`
1290
+ "
1291
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
1292
+ `);
1293
+ expect(std.warn).toMatchInlineSnapshot(`
1294
+ "▲ [WARNING] Unexpected key-value properties in \\"keys.json\\".
1295
+
1296
+ The item at index 5 contains unexpected properties: [\\"invalid\\"].
1297
+
1298
+ "
1299
+ `);
1300
+ });
1301
+ });
1302
+
1303
+ describe("delete", () => {
1304
+ function mockDeleteRequest(
1305
+ expectedNamespaceId: string,
1306
+ expectedKeys: string[]
1307
+ ) {
1308
+ const requests = { count: 0 };
1309
+ setMockResponse(
1310
+ "/accounts/:accountId/storage/kv/namespaces/:namespaceId/bulk",
1311
+ "DELETE",
1312
+ ([_url, accountId, namespaceId], { headers, body }) => {
1313
+ requests.count++;
1314
+ expect(accountId).toEqual("some-account-id");
1315
+ expect(namespaceId).toEqual(expectedNamespaceId);
1316
+ expect(new Headers(headers ?? []).get("Content-Type")).toEqual(
1317
+ "application/json"
1318
+ );
1319
+ expect(JSON.parse(body as string)).toEqual(
1320
+ expectedKeys.slice(
1321
+ (requests.count - 1) * 5000,
1322
+ requests.count * 5000
1323
+ )
1324
+ );
1325
+ return null;
1326
+ }
1327
+ );
1328
+ return requests;
1329
+ }
1330
+
1331
+ it("should delete the keys parsed from a file", async () => {
1332
+ const keys = ["someKey1", "ns:someKey2"];
1333
+ writeFileSync("./keys.json", JSON.stringify(keys));
1334
+ mockConfirm({
1335
+ text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
1336
+ result: true,
1337
+ });
1338
+ const requests = mockDeleteRequest("some-namespace-id", keys);
1339
+ await runWrangler(
1340
+ `kv:bulk delete --namespace-id some-namespace-id keys.json`
1341
+ );
1342
+ expect(requests.count).toEqual(1);
1343
+ expect(std.out).toMatchInlineSnapshot(`"Success!"`);
1344
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1345
+ expect(std.err).toMatchInlineSnapshot(`""`);
1346
+ });
1347
+
1348
+ it("should delete the keys in batches of 5000 parsed from a file", async () => {
1349
+ const keys = new Array(12000).fill("some-key");
1350
+ writeFileSync("./keys.json", JSON.stringify(keys));
1351
+ mockConfirm({
1352
+ text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
1353
+ result: true,
1354
+ });
1355
+ const requests = mockDeleteRequest("some-namespace-id", keys);
1356
+ await runWrangler(
1357
+ `kv:bulk delete --namespace-id some-namespace-id keys.json`
1358
+ );
1359
+ expect(requests.count).toEqual(3);
1360
+ expect(std.out).toMatchInlineSnapshot(`
1361
+ "Deleted 0% (0 out of 12,000)
1362
+ Deleted 41% (5,000 out of 12,000)
1363
+ Deleted 83% (10,000 out of 12,000)
1364
+ Deleted 100% (12,000 out of 12,000)
1365
+ Success!"
1366
+ `);
1367
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1368
+ expect(std.err).toMatchInlineSnapshot(`""`);
1369
+ });
1370
+
1371
+ it("should not delete the keys if the user confirms no", async () => {
1372
+ const keys = ["someKey1", "ns:someKey2"];
1373
+ writeFileSync("./keys.json", JSON.stringify(keys));
1374
+ mockConfirm({
1375
+ text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
1376
+ result: false,
1377
+ });
1378
+ await runWrangler(
1379
+ `kv:bulk delete --namespace-id some-namespace-id keys.json`
1380
+ );
1381
+ expect(std.out).toMatchInlineSnapshot(
1382
+ `"Not deleting keys read from \\"keys.json\\"."`
1383
+ );
1384
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1385
+ expect(std.err).toMatchInlineSnapshot(`""`);
1386
+ });
1387
+
1388
+ it("should delete the keys without asking if --force is provided", async () => {
1389
+ const keys = ["someKey1", "ns:someKey2"];
1390
+ writeFileSync("./keys.json", JSON.stringify(keys));
1391
+ const requests = mockDeleteRequest("some-namespace-id", keys);
1392
+ await runWrangler(
1393
+ `kv:bulk delete --namespace-id some-namespace-id keys.json --force`
1394
+ );
1395
+ expect(requests.count).toEqual(1);
1396
+ expect(std.out).toMatchInlineSnapshot(`"Success!"`);
1397
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1398
+ expect(std.err).toMatchInlineSnapshot(`""`);
1399
+ });
1400
+
1401
+ it("should delete the keys without asking if -f is provided", async () => {
1402
+ const keys = ["someKey1", "ns:someKey2"];
1403
+ writeFileSync("./keys.json", JSON.stringify(keys));
1404
+ const requests = mockDeleteRequest("some-namespace-id", keys);
1405
+ await runWrangler(
1406
+ `kv:bulk delete --namespace-id some-namespace-id keys.json -f`
1407
+ );
1408
+ expect(requests.count).toEqual(1);
1409
+ expect(std.out).toMatchInlineSnapshot(`"Success!"`);
1410
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1411
+ expect(std.err).toMatchInlineSnapshot(`""`);
1412
+ });
1413
+
1414
+ it("should error if the file is not a JSON array", async () => {
1415
+ const keys = 12354;
1416
+ writeFileSync("./keys.json", JSON.stringify(keys));
1417
+ mockConfirm({
1418
+ text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
1419
+ result: true,
1420
+ });
1421
+ await expect(
1422
+ runWrangler(
1423
+ `kv:bulk delete --namespace-id some-namespace-id keys.json`
1424
+ )
1425
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1426
+ "Unexpected JSON input from \\"keys.json\\".
1427
+ Expected an array of strings but got:
1428
+ 12354"
1429
+ `);
1430
+ expect(std.out).toMatchInlineSnapshot(`
1431
+ "
1432
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
1433
+ `);
1434
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1435
+ });
1436
+
1437
+ it("should error if the file contains non-string items", async () => {
1438
+ const keys = ["good", 12354, { key: "someKey" }, null];
1439
+ writeFileSync("./keys.json", JSON.stringify(keys));
1440
+ mockConfirm({
1441
+ text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
1442
+ result: true,
1443
+ });
1444
+ await expect(
1445
+ runWrangler(
1446
+ `kv:bulk delete --namespace-id some-namespace-id keys.json`
1447
+ )
1448
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
1449
+ "Unexpected JSON input from \\"keys.json\\".
1450
+ Expected an array of strings.
1451
+ The item at index 1 is type: \\"number\\" - 12354
1452
+ The item at index 2 is type: \\"object\\" - {\\"key\\":\\"someKey\\"}
1453
+ The item at index 3 is type: \\"object\\" - null"
1454
+ `);
1455
+ expect(std.out).toMatchInlineSnapshot(`
1456
+ "
1457
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
1458
+ `);
1459
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1460
+ });
1461
+ });
1462
+ });
1093
1463
  });
1094
1464
 
1095
1465
  function writeWranglerConfig() {
1096
1466
  writeFileSync(
1097
1467
  "./wrangler.toml",
1098
1468
  [
1099
- 'name = "otherWorker"',
1469
+ 'name = "other-worker"',
1100
1470
  "kv_namespaces = [",
1101
1471
  ' { binding = "someBinding", id = "bound-id", preview_id = "preview-bound-id" }',
1102
1472
  "]",