wrangler 0.0.0-ece06ea → 0.0.0-ef13735

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 (63) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/package.json +35 -21
  3. package/pages/functions/buildWorker.ts +62 -0
  4. package/pages/functions/filepath-routing.test.ts +66 -0
  5. package/pages/functions/filepath-routing.ts +219 -0
  6. package/pages/functions/identifiers.ts +78 -0
  7. package/pages/functions/routes.ts +151 -0
  8. package/pages/functions/template-worker.ts +130 -0
  9. package/src/__tests__/dev.test.tsx +73 -0
  10. package/src/__tests__/guess-worker-format.test.ts +51 -0
  11. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  14. package/src/__tests__/helpers/mock-bin.ts +33 -0
  15. package/src/__tests__/helpers/mock-cfetch.ts +207 -0
  16. package/src/__tests__/helpers/mock-console.ts +56 -0
  17. package/src/__tests__/helpers/mock-dialogs.ts +65 -0
  18. package/src/__tests__/helpers/mock-kv.ts +40 -0
  19. package/src/__tests__/helpers/mock-user.ts +27 -0
  20. package/src/__tests__/helpers/run-in-tmp.ts +29 -0
  21. package/src/__tests__/helpers/run-wrangler.ts +14 -0
  22. package/src/__tests__/index.test.ts +715 -119
  23. package/src/__tests__/jest.setup.ts +37 -0
  24. package/src/__tests__/kv.test.ts +1139 -0
  25. package/src/__tests__/logout.test.ts +50 -0
  26. package/src/__tests__/package-manager.test.ts +206 -0
  27. package/src/__tests__/publish.test.ts +1381 -0
  28. package/src/__tests__/secret.test.ts +210 -0
  29. package/src/__tests__/whoami.test.tsx +86 -0
  30. package/src/api/form_data.ts +93 -100
  31. package/src/api/preview.ts +29 -24
  32. package/src/api/worker.ts +55 -47
  33. package/src/cfetch/index.ts +102 -0
  34. package/src/cfetch/internal.ts +101 -0
  35. package/src/cli.ts +6 -7
  36. package/src/config.ts +641 -112
  37. package/src/dev.tsx +476 -355
  38. package/src/dialogs.tsx +6 -14
  39. package/src/guess-worker-format.ts +66 -0
  40. package/src/index.tsx +828 -496
  41. package/src/inspect.ts +596 -0
  42. package/src/kv.tsx +118 -45
  43. package/src/module-collection.ts +63 -0
  44. package/src/open-in-browser.ts +13 -0
  45. package/src/package-manager.ts +120 -0
  46. package/src/pages.tsx +915 -215
  47. package/src/paths.ts +26 -0
  48. package/src/proxy.ts +370 -0
  49. package/src/publish.ts +349 -290
  50. package/src/sites.tsx +184 -55
  51. package/src/tail.tsx +5 -3
  52. package/src/user.tsx +81 -91
  53. package/src/whoami.tsx +69 -0
  54. package/templates/new-worker.js +15 -0
  55. package/templates/new-worker.ts +15 -0
  56. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  57. package/wrangler-dist/cli.js +119231 -130682
  58. package/wrangler-dist/cli.js.map +3 -3
  59. package/src/__tests__/fixtures/init/.gitkeep +0 -0
  60. package/src/__tests__/mock-cfetch.js +0 -46
  61. package/src/api/inspect.ts +0 -441
  62. package/src/cfetch.ts +0 -72
  63. package/src/util/fetch.ts +0 -74
@@ -0,0 +1,1381 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as TOML from "@iarna/toml";
4
+ import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
5
+ import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
6
+ import { mockConsoleMethods } from "./helpers/mock-console";
7
+ import { mockKeyListRequest } from "./helpers/mock-kv";
8
+ import { runInTempDir } from "./helpers/run-in-tmp";
9
+ import { runWrangler } from "./helpers/run-wrangler";
10
+ import type { WorkerMetadata } from "../api/form_data";
11
+ import type { Config } from "../config";
12
+ import type { KVNamespaceInfo } from "../kv";
13
+ import type { FormData, File } from "undici";
14
+
15
+ describe("publish", () => {
16
+ mockAccountId();
17
+ mockApiToken();
18
+ runInTempDir();
19
+ const std = mockConsoleMethods();
20
+
21
+ afterEach(() => {
22
+ unsetAllMocks();
23
+ });
24
+
25
+ describe("entry-points", () => {
26
+ it("should be able to use `index` with no extension as the entry-point (esm)", async () => {
27
+ writeWranglerToml();
28
+ writeWorkerSource();
29
+ mockUploadWorkerRequest({ expectedType: "esm" });
30
+ mockSubDomainRequest();
31
+
32
+ await runWrangler("publish ./index");
33
+
34
+ expect(std.out).toMatchInlineSnapshot(`
35
+ "Uploaded
36
+ test-name
37
+ (TIMINGS)
38
+ Deployed
39
+ test-name
40
+ (TIMINGS)
41
+
42
+ test-name.test-sub-domain.workers.dev"
43
+ `);
44
+ expect(std.err).toMatchInlineSnapshot(`""`);
45
+ });
46
+
47
+ it("should be able to use `index` with no extension as the entry-point (sw)", async () => {
48
+ writeWranglerToml();
49
+ writeWorkerSource({ type: "sw" });
50
+ mockUploadWorkerRequest({ expectedType: "sw" });
51
+ mockSubDomainRequest();
52
+
53
+ await runWrangler("publish ./index");
54
+
55
+ expect(std.out).toMatchInlineSnapshot(`
56
+ "Uploaded
57
+ test-name
58
+ (TIMINGS)
59
+ Deployed
60
+ test-name
61
+ (TIMINGS)
62
+
63
+ test-name.test-sub-domain.workers.dev"
64
+ `);
65
+ expect(std.err).toMatchInlineSnapshot(`""`);
66
+ });
67
+
68
+ it("should be able to use the `build.upload.main` config as the entry-point for ESM sources", async () => {
69
+ writeWranglerToml({ build: { upload: { main: "./index.js" } } });
70
+ writeWorkerSource();
71
+ mockUploadWorkerRequest();
72
+ mockSubDomainRequest();
73
+
74
+ await runWrangler("publish");
75
+
76
+ expect(std.out).toMatchInlineSnapshot(`
77
+ "Uploaded
78
+ test-name
79
+ (TIMINGS)
80
+ Deployed
81
+ test-name
82
+ (TIMINGS)
83
+
84
+ test-name.test-sub-domain.workers.dev"
85
+ `);
86
+ expect(std.err).toMatchInlineSnapshot(`""`);
87
+ });
88
+
89
+ it("should use `build.upload.main` relative to the wrangler.toml not cwd", async () => {
90
+ writeWranglerToml({
91
+ build: {
92
+ upload: {
93
+ main: "./foo/index.js",
94
+ },
95
+ },
96
+ });
97
+ writeWorkerSource({ basePath: "foo" });
98
+ mockUploadWorkerRequest({ expectedEntry: "var foo = 100;" });
99
+ mockSubDomainRequest();
100
+ process.chdir("foo");
101
+ await runWrangler("publish");
102
+
103
+ expect(std.out).toMatchInlineSnapshot(`
104
+ "Uploaded
105
+ test-name
106
+ (TIMINGS)
107
+ Deployed
108
+ test-name
109
+ (TIMINGS)
110
+
111
+ test-name.test-sub-domain.workers.dev"
112
+ `);
113
+ expect(std.err).toMatchInlineSnapshot(`""`);
114
+ });
115
+
116
+ it("should use `build.upload.main` relative to `build.upload.dir` if format is `modules`", async () => {
117
+ writeWranglerToml({
118
+ build: {
119
+ upload: {
120
+ format: "modules",
121
+ main: "./index.js",
122
+ dir: "./foo",
123
+ },
124
+ },
125
+ });
126
+ writeWorkerSource({ basePath: "./foo" });
127
+ mockUploadWorkerRequest({ expectedEntry: "var foo = 100;" });
128
+ mockSubDomainRequest();
129
+ process.chdir("foo");
130
+ await runWrangler("publish");
131
+
132
+ expect(std.out).toMatchInlineSnapshot(`
133
+ "Uploaded
134
+ test-name
135
+ (TIMINGS)
136
+ Deployed
137
+ test-name
138
+ (TIMINGS)
139
+
140
+ test-name.test-sub-domain.workers.dev"
141
+ `);
142
+ expect(std.err).toMatchInlineSnapshot(`""`);
143
+ });
144
+
145
+ it("should be able to transpile TypeScript (esm)", async () => {
146
+ writeWranglerToml();
147
+ writeWorkerSource({ format: "ts" });
148
+ mockUploadWorkerRequest({ expectedEntry: "var foo = 100;" });
149
+ mockSubDomainRequest();
150
+ await runWrangler("publish index.ts");
151
+
152
+ expect(std.out).toMatchInlineSnapshot(`
153
+ "Uploaded
154
+ test-name
155
+ (TIMINGS)
156
+ Deployed
157
+ test-name
158
+ (TIMINGS)
159
+
160
+ test-name.test-sub-domain.workers.dev"
161
+ `);
162
+ expect(std.err).toMatchInlineSnapshot(`""`);
163
+ });
164
+
165
+ it("should be able to transpile TypeScript (sw)", async () => {
166
+ writeWranglerToml();
167
+ writeWorkerSource({ format: "ts", type: "sw" });
168
+ mockUploadWorkerRequest({
169
+ expectedEntry: "var foo = 100;",
170
+ expectedType: "sw",
171
+ });
172
+ mockSubDomainRequest();
173
+ await runWrangler("publish index.ts");
174
+
175
+ expect(std.out).toMatchInlineSnapshot(`
176
+ "Uploaded
177
+ test-name
178
+ (TIMINGS)
179
+ Deployed
180
+ test-name
181
+ (TIMINGS)
182
+
183
+ test-name.test-sub-domain.workers.dev"
184
+ `);
185
+ expect(std.err).toMatchInlineSnapshot(`""`);
186
+ });
187
+
188
+ it("should inline referenced text modules into the worker", async () => {
189
+ writeWranglerToml();
190
+ fs.writeFileSync(
191
+ "./index.js",
192
+ `
193
+ import txt from './textfile.txt';
194
+ export default{
195
+ fetch(){
196
+ return new Response(txt);
197
+ }
198
+ }
199
+ `
200
+ );
201
+ fs.writeFileSync("./textfile.txt", "Hello, World!");
202
+ mockUploadWorkerRequest({ expectedEntry: "Hello, World!" });
203
+ mockSubDomainRequest();
204
+ await runWrangler("publish index.js");
205
+ expect(std.out).toMatchInlineSnapshot(`
206
+ "Uploaded
207
+ test-name
208
+ (TIMINGS)
209
+ Deployed
210
+ test-name
211
+ (TIMINGS)
212
+
213
+ test-name.test-sub-domain.workers.dev"
214
+ `);
215
+ expect(std.err).toMatchInlineSnapshot(`""`);
216
+ });
217
+
218
+ it("should be able to transpile entry-points in sub-directories (esm)", async () => {
219
+ writeWranglerToml();
220
+ writeWorkerSource({ basePath: "./src" });
221
+ mockUploadWorkerRequest({ expectedEntry: "var foo = 100;" });
222
+ mockSubDomainRequest();
223
+
224
+ await runWrangler("publish ./src/index.js");
225
+
226
+ expect(std.out).toMatchInlineSnapshot(`
227
+ "Uploaded
228
+ test-name
229
+ (TIMINGS)
230
+ Deployed
231
+ test-name
232
+ (TIMINGS)
233
+
234
+ test-name.test-sub-domain.workers.dev"
235
+ `);
236
+ expect(std.err).toMatchInlineSnapshot(`""`);
237
+ });
238
+
239
+ it("should be able to transpile entry-points in sub-directories (sw)", async () => {
240
+ writeWranglerToml();
241
+ writeWorkerSource({ basePath: "./src", type: "sw" });
242
+ mockUploadWorkerRequest({
243
+ expectedEntry: "var foo = 100;",
244
+ expectedType: "sw",
245
+ });
246
+ mockSubDomainRequest();
247
+
248
+ await runWrangler("publish ./src/index.js");
249
+
250
+ expect(std.out).toMatchInlineSnapshot(`
251
+ "Uploaded
252
+ test-name
253
+ (TIMINGS)
254
+ Deployed
255
+ test-name
256
+ (TIMINGS)
257
+
258
+ test-name.test-sub-domain.workers.dev"
259
+ `);
260
+ expect(std.err).toMatchInlineSnapshot(`""`);
261
+ });
262
+
263
+ it('should error if a site definition doesn\'t have a "bucket" field', async () => {
264
+ writeWranglerToml({
265
+ // @ts-expect-error we're purposely missing the required `site.bucket` field
266
+ site: {
267
+ "entry-point": "./index.js",
268
+ },
269
+ });
270
+ writeWorkerSource();
271
+ mockUploadWorkerRequest();
272
+ mockSubDomainRequest();
273
+
274
+ await expect(
275
+ runWrangler("publish ./index.js")
276
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
277
+ `"A [site] definition requires a \`bucket\` field with a path to the site's public directory."`
278
+ );
279
+
280
+ expect(std.out).toMatchInlineSnapshot(`""`);
281
+ expect(std.err).toMatchInlineSnapshot(`
282
+ "A [site] definition requires a \`bucket\` field with a path to the site's public directory.
283
+
284
+ %s
285
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
286
+ `);
287
+ expect(std.warn).toMatchInlineSnapshot(`
288
+ "Deprecation notice: The \`site.entry-point\` config field is no longer used.
289
+ The entry-point should be specified via the command line (e.g. \`wrangler publish path/to/script\`) or the \`build.upload.main\` config field.
290
+ Please remove the \`site.entry-point\` field from the \`wrangler.toml\` file."
291
+ `);
292
+ });
293
+
294
+ it("should warn if there is a `site.entry-point` configuration", async () => {
295
+ const assets = [
296
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
297
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
298
+ ];
299
+ const kvNamespace = {
300
+ title: "__test-name-workers_sites_assets",
301
+ id: "__test-name-workers_sites_assets-id",
302
+ };
303
+
304
+ writeWranglerToml({
305
+ site: {
306
+ "entry-point": "./index.js",
307
+ bucket: "assets",
308
+ },
309
+ });
310
+ writeWorkerSource();
311
+ writeAssets(assets);
312
+ mockUploadWorkerRequest();
313
+ mockSubDomainRequest();
314
+ mockListKVNamespacesRequest(kvNamespace);
315
+ mockKeyListRequest(kvNamespace.id, []);
316
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets);
317
+
318
+ await runWrangler("publish ./index.js");
319
+
320
+ expect(std.out).toMatchInlineSnapshot(`
321
+ "reading assets/file-1.txt...
322
+ uploading as assets/file-1.2ca234f380.txt...
323
+ reading assets/file-2.txt...
324
+ uploading as assets/file-2.5938485188.txt...
325
+ Uploaded
326
+ test-name
327
+ (TIMINGS)
328
+ Deployed
329
+ test-name
330
+ (TIMINGS)
331
+
332
+ test-name.test-sub-domain.workers.dev"
333
+ `);
334
+ expect(std.err).toMatchInlineSnapshot(`""`);
335
+ expect(std.warn).toMatchInlineSnapshot(`
336
+ "Deprecation notice: The \`site.entry-point\` config field is no longer used.
337
+ The entry-point should be specified via the command line (e.g. \`wrangler publish path/to/script\`) or the \`build.upload.main\` config field.
338
+ Please remove the \`site.entry-point\` field from the \`wrangler.toml\` file."
339
+ `);
340
+ });
341
+
342
+ it("should error if there is no entry-point specified", async () => {
343
+ writeWranglerToml();
344
+ writeWorkerSource();
345
+ mockUploadWorkerRequest();
346
+ mockSubDomainRequest();
347
+ await expect(
348
+ runWrangler("publish")
349
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
350
+ `"Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler publish path/to/script\`) or the \`build.upload.main\` config field."`
351
+ );
352
+
353
+ expect(std.out).toMatchInlineSnapshot(`""`);
354
+ expect(std.err).toMatchInlineSnapshot(`
355
+ "Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler publish path/to/script\`) or the \`build.upload.main\` config field.
356
+
357
+ %s
358
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
359
+ `);
360
+ });
361
+ });
362
+
363
+ describe("asset upload", () => {
364
+ it("should upload all the files in the directory specified by `config.site.bucket`", async () => {
365
+ const assets = [
366
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
367
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
368
+ ];
369
+ const kvNamespace = {
370
+ title: "__test-name-workers_sites_assets",
371
+ id: "__test-name-workers_sites_assets-id",
372
+ };
373
+ writeWranglerToml({
374
+ build: { upload: { main: "./index.js" } },
375
+ site: {
376
+ bucket: "assets",
377
+ },
378
+ });
379
+ writeWorkerSource();
380
+ writeAssets(assets);
381
+ mockUploadWorkerRequest();
382
+ mockSubDomainRequest();
383
+ mockListKVNamespacesRequest(kvNamespace);
384
+ mockKeyListRequest(kvNamespace.id, []);
385
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets);
386
+ await runWrangler("publish");
387
+
388
+ expect(std.out).toMatchInlineSnapshot(`
389
+ "reading assets/file-1.txt...
390
+ uploading as assets/file-1.2ca234f380.txt...
391
+ reading assets/file-2.txt...
392
+ uploading as assets/file-2.5938485188.txt...
393
+ Uploaded
394
+ test-name
395
+ (TIMINGS)
396
+ Deployed
397
+ test-name
398
+ (TIMINGS)
399
+
400
+ test-name.test-sub-domain.workers.dev"
401
+ `);
402
+ expect(std.err).toMatchInlineSnapshot(`""`);
403
+ });
404
+
405
+ it("when using a service worker type, it should inline an asset manifest, and bind to a namespace", async () => {
406
+ const assets = [
407
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
408
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
409
+ ];
410
+ const kvNamespace = {
411
+ title: "__test-name-workers_sites_assets",
412
+ id: "__test-name-workers_sites_assets-id",
413
+ };
414
+ writeWranglerToml({
415
+ build: { upload: { main: "./index.js" } },
416
+ site: {
417
+ bucket: "assets",
418
+ },
419
+ });
420
+ writeWorkerSource({ type: "sw" });
421
+ writeAssets(assets);
422
+ mockUploadWorkerRequest({
423
+ expectedType: "sw",
424
+ expectedEntry: `const __STATIC_CONTENT_MANIFEST = {"file-1.txt":"assets/file-1.2ca234f380.txt","file-2.txt":"assets/file-2.5938485188.txt"};`,
425
+ expectedBindings: [
426
+ {
427
+ name: "__STATIC_CONTENT",
428
+ namespace_id: "__test-name-workers_sites_assets-id",
429
+ type: "kv_namespace",
430
+ },
431
+ ],
432
+ });
433
+ mockSubDomainRequest();
434
+ mockListKVNamespacesRequest(kvNamespace);
435
+ mockKeyListRequest(kvNamespace.id, []);
436
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets);
437
+
438
+ await runWrangler("publish");
439
+
440
+ expect(std.out).toMatchInlineSnapshot(`
441
+ "reading assets/file-1.txt...
442
+ uploading as assets/file-1.2ca234f380.txt...
443
+ reading assets/file-2.txt...
444
+ uploading as assets/file-2.5938485188.txt...
445
+ Uploaded
446
+ test-name
447
+ (TIMINGS)
448
+ Deployed
449
+ test-name
450
+ (TIMINGS)
451
+
452
+ test-name.test-sub-domain.workers.dev"
453
+ `);
454
+ expect(std.err).toMatchInlineSnapshot(`""`);
455
+ });
456
+
457
+ it("when using a module worker type, it should add an asset manifest module, and bind to a namespace", async () => {
458
+ const assets = [
459
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
460
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
461
+ ];
462
+ const kvNamespace = {
463
+ title: "__test-name-workers_sites_assets",
464
+ id: "__test-name-workers_sites_assets-id",
465
+ };
466
+ writeWranglerToml({
467
+ build: { upload: { main: "./index.js" } },
468
+ site: {
469
+ bucket: "assets",
470
+ },
471
+ });
472
+ writeWorkerSource({ type: "esm" });
473
+ writeAssets(assets);
474
+ mockUploadWorkerRequest({
475
+ expectedBindings: [
476
+ {
477
+ name: "__STATIC_CONTENT",
478
+ namespace_id: "__test-name-workers_sites_assets-id",
479
+ type: "kv_namespace",
480
+ },
481
+ ],
482
+ expectedModules: {
483
+ __STATIC_CONTENT_MANIFEST:
484
+ '{"file-1.txt":"assets/file-1.2ca234f380.txt","file-2.txt":"assets/file-2.5938485188.txt"}',
485
+ },
486
+ });
487
+ mockSubDomainRequest();
488
+ mockListKVNamespacesRequest(kvNamespace);
489
+ mockKeyListRequest(kvNamespace.id, []);
490
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets);
491
+
492
+ await runWrangler("publish");
493
+
494
+ expect(std.out).toMatchInlineSnapshot(`
495
+ "reading assets/file-1.txt...
496
+ uploading as assets/file-1.2ca234f380.txt...
497
+ reading assets/file-2.txt...
498
+ uploading as assets/file-2.5938485188.txt...
499
+ Uploaded
500
+ test-name
501
+ (TIMINGS)
502
+ Deployed
503
+ test-name
504
+ (TIMINGS)
505
+
506
+ test-name.test-sub-domain.workers.dev"
507
+ `);
508
+ expect(std.err).toMatchInlineSnapshot(`""`);
509
+ });
510
+
511
+ it("should make environment specific kv namespace for assets", async () => {
512
+ // This is the same test as the one before this, but with an env arg
513
+ const assets = [
514
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
515
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
516
+ ];
517
+ const kvNamespace = {
518
+ title: "__test-name-some-env-workers_sites_assets",
519
+ id: "__test-name-some-env-workers_sites_assets-id",
520
+ };
521
+ writeWranglerToml({
522
+ build: { upload: { main: "./index.js" } },
523
+ site: {
524
+ bucket: "assets",
525
+ },
526
+ });
527
+ writeWorkerSource();
528
+ writeAssets(assets);
529
+ mockUploadWorkerRequest({
530
+ env: "some-env",
531
+ expectedBindings: [
532
+ {
533
+ name: "__STATIC_CONTENT",
534
+ namespace_id: "__test-name-some-env-workers_sites_assets-id",
535
+ type: "kv_namespace",
536
+ },
537
+ ],
538
+ });
539
+ mockSubDomainRequest();
540
+ mockListKVNamespacesRequest(kvNamespace);
541
+ mockKeyListRequest(kvNamespace.id, []);
542
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets);
543
+ await runWrangler("publish --env some-env");
544
+
545
+ expect(std.out).toMatchInlineSnapshot(`
546
+ "reading assets/file-1.txt...
547
+ uploading as assets/file-1.2ca234f380.txt...
548
+ reading assets/file-2.txt...
549
+ uploading as assets/file-2.5938485188.txt...
550
+ Uploaded
551
+ test-name (some-env)
552
+ (TIMINGS)
553
+ Deployed
554
+ test-name (some-env)
555
+ (TIMINGS)
556
+
557
+ some-env.test-name.test-sub-domain.workers.dev"
558
+ `);
559
+ expect(std.err).toMatchInlineSnapshot(`""`);
560
+ });
561
+
562
+ it("should only upload files that are not already in the KV namespace", async () => {
563
+ const assets = [
564
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
565
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
566
+ ];
567
+ const kvNamespace = {
568
+ title: "__test-name-workers_sites_assets",
569
+ id: "__test-name-workers_sites_assets-id",
570
+ };
571
+ writeWranglerToml({
572
+ build: { upload: { main: "./index.js" } },
573
+ site: {
574
+ bucket: "assets",
575
+ },
576
+ });
577
+ writeWorkerSource();
578
+ writeAssets(assets);
579
+ mockUploadWorkerRequest();
580
+ mockSubDomainRequest();
581
+ mockListKVNamespacesRequest(kvNamespace);
582
+ // Put file-1 in the KV namespace
583
+ mockKeyListRequest(kvNamespace.id, ["assets/file-1.2ca234f380.txt"]);
584
+ // Check we do not upload file-1
585
+ mockUploadAssetsToKVRequest(
586
+ kvNamespace.id,
587
+ assets.filter((a) => a.filePath !== "assets/file-1.txt")
588
+ );
589
+ await runWrangler("publish");
590
+
591
+ expect(std.out).toMatchInlineSnapshot(`
592
+ "reading assets/file-1.txt...
593
+ skipping - already uploaded
594
+ reading assets/file-2.txt...
595
+ uploading as assets/file-2.5938485188.txt...
596
+ Uploaded
597
+ test-name
598
+ (TIMINGS)
599
+ Deployed
600
+ test-name
601
+ (TIMINGS)
602
+
603
+ test-name.test-sub-domain.workers.dev"
604
+ `);
605
+ expect(std.err).toMatchInlineSnapshot(`""`);
606
+ });
607
+
608
+ it("should only upload files that match the `site-include` arg", async () => {
609
+ const assets = [
610
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
611
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
612
+ ];
613
+ const kvNamespace = {
614
+ title: "__test-name-workers_sites_assets",
615
+ id: "__test-name-workers_sites_assets-id",
616
+ };
617
+ writeWranglerToml({
618
+ build: { upload: { main: "./index.js" } },
619
+ site: {
620
+ bucket: "assets",
621
+ },
622
+ });
623
+ writeWorkerSource();
624
+ writeAssets(assets);
625
+ mockUploadWorkerRequest();
626
+ mockSubDomainRequest();
627
+ mockListKVNamespacesRequest(kvNamespace);
628
+ mockKeyListRequest(kvNamespace.id, []);
629
+ // Check we only upload file-1
630
+ mockUploadAssetsToKVRequest(
631
+ kvNamespace.id,
632
+ assets.filter((a) => a.filePath === "assets/file-1.txt")
633
+ );
634
+ await runWrangler("publish --site-include file-1.txt");
635
+
636
+ expect(std.out).toMatchInlineSnapshot(`
637
+ "reading assets/file-1.txt...
638
+ uploading as assets/file-1.2ca234f380.txt...
639
+ Uploaded
640
+ test-name
641
+ (TIMINGS)
642
+ Deployed
643
+ test-name
644
+ (TIMINGS)
645
+
646
+ test-name.test-sub-domain.workers.dev"
647
+ `);
648
+ expect(std.err).toMatchInlineSnapshot(`""`);
649
+ });
650
+
651
+ it("should not upload files that match the `site-exclude` arg", async () => {
652
+ const assets = [
653
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
654
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
655
+ ];
656
+ const kvNamespace = {
657
+ title: "__test-name-workers_sites_assets",
658
+ id: "__test-name-workers_sites_assets-id",
659
+ };
660
+ writeWranglerToml({
661
+ build: { upload: { main: "./index.js" } },
662
+ site: {
663
+ bucket: "assets",
664
+ },
665
+ });
666
+ writeWorkerSource();
667
+ writeAssets(assets);
668
+ mockUploadWorkerRequest();
669
+ mockSubDomainRequest();
670
+ mockListKVNamespacesRequest(kvNamespace);
671
+ mockKeyListRequest(kvNamespace.id, []);
672
+ // Check we only upload file-1
673
+ mockUploadAssetsToKVRequest(
674
+ kvNamespace.id,
675
+ assets.filter((a) => a.filePath === "assets/file-1.txt")
676
+ );
677
+ await runWrangler("publish --site-exclude file-2.txt");
678
+
679
+ expect(std.out).toMatchInlineSnapshot(`
680
+ "reading assets/file-1.txt...
681
+ uploading as assets/file-1.2ca234f380.txt...
682
+ Uploaded
683
+ test-name
684
+ (TIMINGS)
685
+ Deployed
686
+ test-name
687
+ (TIMINGS)
688
+
689
+ test-name.test-sub-domain.workers.dev"
690
+ `);
691
+ expect(std.err).toMatchInlineSnapshot(`""`);
692
+ });
693
+
694
+ it("should only upload files that match the `site.include` config", async () => {
695
+ const assets = [
696
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
697
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
698
+ ];
699
+ const kvNamespace = {
700
+ title: "__test-name-workers_sites_assets",
701
+ id: "__test-name-workers_sites_assets-id",
702
+ };
703
+ writeWranglerToml({
704
+ build: { upload: { main: "./index.js" } },
705
+ site: {
706
+ bucket: "assets",
707
+ include: ["file-1.txt"],
708
+ },
709
+ });
710
+ writeWorkerSource();
711
+ writeAssets(assets);
712
+ mockUploadWorkerRequest();
713
+ mockSubDomainRequest();
714
+ mockListKVNamespacesRequest(kvNamespace);
715
+ mockKeyListRequest(kvNamespace.id, []);
716
+ // Check we only upload file-1
717
+ mockUploadAssetsToKVRequest(
718
+ kvNamespace.id,
719
+ assets.filter((a) => a.filePath === "assets/file-1.txt")
720
+ );
721
+ await runWrangler("publish");
722
+
723
+ expect(std.out).toMatchInlineSnapshot(`
724
+ "reading assets/file-1.txt...
725
+ uploading as assets/file-1.2ca234f380.txt...
726
+ Uploaded
727
+ test-name
728
+ (TIMINGS)
729
+ Deployed
730
+ test-name
731
+ (TIMINGS)
732
+
733
+ test-name.test-sub-domain.workers.dev"
734
+ `);
735
+ expect(std.err).toMatchInlineSnapshot(`""`);
736
+ });
737
+
738
+ it("should not upload files that match the `site.exclude` config", async () => {
739
+ const assets = [
740
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
741
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
742
+ ];
743
+ const kvNamespace = {
744
+ title: "__test-name-workers_sites_assets",
745
+ id: "__test-name-workers_sites_assets-id",
746
+ };
747
+ writeWranglerToml({
748
+ build: { upload: { main: "./index.js" } },
749
+ site: {
750
+ bucket: "assets",
751
+ exclude: ["file-2.txt"],
752
+ },
753
+ });
754
+ writeWorkerSource();
755
+ writeAssets(assets);
756
+ mockUploadWorkerRequest();
757
+ mockSubDomainRequest();
758
+ mockListKVNamespacesRequest(kvNamespace);
759
+ mockKeyListRequest(kvNamespace.id, []);
760
+ // Check we only upload file-1
761
+ mockUploadAssetsToKVRequest(
762
+ kvNamespace.id,
763
+ assets.filter((a) => a.filePath === "assets/file-1.txt")
764
+ );
765
+ await runWrangler("publish");
766
+
767
+ expect(std.out).toMatchInlineSnapshot(`
768
+ "reading assets/file-1.txt...
769
+ uploading as assets/file-1.2ca234f380.txt...
770
+ Uploaded
771
+ test-name
772
+ (TIMINGS)
773
+ Deployed
774
+ test-name
775
+ (TIMINGS)
776
+
777
+ test-name.test-sub-domain.workers.dev"
778
+ `);
779
+ expect(std.err).toMatchInlineSnapshot(`""`);
780
+ });
781
+
782
+ it("should use `site-include` arg over `site.include` config", async () => {
783
+ const assets = [
784
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
785
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
786
+ ];
787
+ const kvNamespace = {
788
+ title: "__test-name-workers_sites_assets",
789
+ id: "__test-name-workers_sites_assets-id",
790
+ };
791
+ writeWranglerToml({
792
+ build: { upload: { main: "./index.js" } },
793
+ site: {
794
+ bucket: "assets",
795
+ include: ["file-2.txt"],
796
+ },
797
+ });
798
+ writeWorkerSource();
799
+ writeAssets(assets);
800
+ mockUploadWorkerRequest();
801
+ mockSubDomainRequest();
802
+ mockListKVNamespacesRequest(kvNamespace);
803
+ mockKeyListRequest(kvNamespace.id, []);
804
+ // Check we only upload file-1
805
+ mockUploadAssetsToKVRequest(
806
+ kvNamespace.id,
807
+ assets.filter((a) => a.filePath === "assets/file-1.txt")
808
+ );
809
+ await runWrangler("publish --site-include file-1.txt");
810
+
811
+ expect(std.out).toMatchInlineSnapshot(`
812
+ "reading assets/file-1.txt...
813
+ uploading as assets/file-1.2ca234f380.txt...
814
+ Uploaded
815
+ test-name
816
+ (TIMINGS)
817
+ Deployed
818
+ test-name
819
+ (TIMINGS)
820
+
821
+ test-name.test-sub-domain.workers.dev"
822
+ `);
823
+ expect(std.err).toMatchInlineSnapshot(`""`);
824
+ });
825
+
826
+ it("should use `site-exclude` arg over `site.exclude` config", async () => {
827
+ const assets = [
828
+ { filePath: "assets/file-1.txt", content: "Content of file-1" },
829
+ { filePath: "assets/file-2.txt", content: "Content of file-2" },
830
+ ];
831
+ const kvNamespace = {
832
+ title: "__test-name-workers_sites_assets",
833
+ id: "__test-name-workers_sites_assets-id",
834
+ };
835
+ writeWranglerToml({
836
+ build: { upload: { main: "./index.js" } },
837
+ site: {
838
+ bucket: "assets",
839
+ exclude: ["assets/file-1.txt"],
840
+ },
841
+ });
842
+ writeWorkerSource();
843
+ writeAssets(assets);
844
+ mockUploadWorkerRequest();
845
+ mockSubDomainRequest();
846
+ mockListKVNamespacesRequest(kvNamespace);
847
+ mockKeyListRequest(kvNamespace.id, []);
848
+ // Check we only upload file-1
849
+ mockUploadAssetsToKVRequest(
850
+ kvNamespace.id,
851
+ assets.filter((a) => a.filePath.endsWith("assets/file-1.txt"))
852
+ );
853
+ await runWrangler("publish --site-exclude file-2.txt");
854
+
855
+ expect(std.out).toMatchInlineSnapshot(`
856
+ "reading assets/file-1.txt...
857
+ uploading as assets/file-1.2ca234f380.txt...
858
+ Uploaded
859
+ test-name
860
+ (TIMINGS)
861
+ Deployed
862
+ test-name
863
+ (TIMINGS)
864
+
865
+ test-name.test-sub-domain.workers.dev"
866
+ `);
867
+ expect(std.err).toMatchInlineSnapshot(`""`);
868
+ });
869
+
870
+ it("should walk directories except node_modules", async () => {
871
+ const assets = [
872
+ {
873
+ filePath: "assets/directory-1/file-1.txt",
874
+ content: "Content of file-1",
875
+ },
876
+ {
877
+ filePath: "assets/node_modules/file-2.txt",
878
+ content: "Content of file-2",
879
+ },
880
+ ];
881
+ const kvNamespace = {
882
+ title: "__test-name-workers_sites_assets",
883
+ id: "__test-name-workers_sites_assets-id",
884
+ };
885
+ writeWranglerToml({
886
+ build: { upload: { main: "./index.js" } },
887
+ site: {
888
+ bucket: "assets",
889
+ },
890
+ });
891
+ writeWorkerSource();
892
+ writeAssets(assets);
893
+ mockUploadWorkerRequest();
894
+ mockSubDomainRequest();
895
+ mockListKVNamespacesRequest(kvNamespace);
896
+ mockKeyListRequest(kvNamespace.id, []);
897
+ // Only expect file-1 to be uploaded
898
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets.slice(0, 1));
899
+ await runWrangler("publish");
900
+
901
+ expect(std.out).toMatchInlineSnapshot(`
902
+ "reading assets/directory-1/file-1.txt...
903
+ uploading as assets/directory-1/file-1.2ca234f380.txt...
904
+ Uploaded
905
+ test-name
906
+ (TIMINGS)
907
+ Deployed
908
+ test-name
909
+ (TIMINGS)
910
+
911
+ test-name.test-sub-domain.workers.dev"
912
+ `);
913
+ expect(std.err).toMatchInlineSnapshot(`""`);
914
+ });
915
+
916
+ it("should skip hidden files and directories except `.well-known`", async () => {
917
+ const assets = [
918
+ {
919
+ filePath: "assets/.hidden-file.txt",
920
+ content: "Content of hidden-file",
921
+ },
922
+ {
923
+ filePath: "assets/.hidden/file-1.txt",
924
+ content: "Content of file-1",
925
+ },
926
+ {
927
+ filePath: "assets/.well-known/file-2.txt",
928
+ content: "Content of file-2",
929
+ },
930
+ ];
931
+ const kvNamespace = {
932
+ title: "__test-name-workers_sites_assets",
933
+ id: "__test-name-workers_sites_assets-id",
934
+ };
935
+ writeWranglerToml({
936
+ build: { upload: { main: "./index.js" } },
937
+ site: {
938
+ bucket: "assets",
939
+ },
940
+ });
941
+ writeWorkerSource();
942
+ writeAssets(assets);
943
+ mockUploadWorkerRequest();
944
+ mockSubDomainRequest();
945
+ mockListKVNamespacesRequest(kvNamespace);
946
+ mockKeyListRequest(kvNamespace.id, []);
947
+ // Only expect file-2 to be uploaded
948
+ mockUploadAssetsToKVRequest(kvNamespace.id, assets.slice(2));
949
+ await runWrangler("publish");
950
+
951
+ expect(std.out).toMatchInlineSnapshot(`
952
+ "reading assets/.well-known/file-2.txt...
953
+ uploading as assets/.well-known/file-2.5938485188.txt...
954
+ Uploaded
955
+ test-name
956
+ (TIMINGS)
957
+ Deployed
958
+ test-name
959
+ (TIMINGS)
960
+
961
+ test-name.test-sub-domain.workers.dev"
962
+ `);
963
+ expect(std.err).toMatchInlineSnapshot(`""`);
964
+ });
965
+
966
+ it("should error if the asset is over 25Mb", async () => {
967
+ const assets = [
968
+ {
969
+ filePath: "assets/large-file.txt",
970
+ // This file is greater than 25MiB when base64 encoded but small enough to be uploaded.
971
+ content: "X".repeat(25 * 1024 * 1024 * 0.8 + 1),
972
+ },
973
+ {
974
+ filePath: "assets/too-large-file.txt",
975
+ content: "X".repeat(25 * 1024 * 1024 + 1),
976
+ },
977
+ ];
978
+ const kvNamespace = {
979
+ title: "__test-name-workers_sites_assets",
980
+ id: "__test-name-workers_sites_assets-id",
981
+ };
982
+ writeWranglerToml({
983
+ build: { upload: { main: "./index.js" } },
984
+ site: {
985
+ bucket: "assets",
986
+ exclude: ["assets/file-1.txt"],
987
+ },
988
+ });
989
+ writeWorkerSource();
990
+ writeAssets(assets);
991
+ mockUploadWorkerRequest();
992
+ mockSubDomainRequest();
993
+ mockListKVNamespacesRequest(kvNamespace);
994
+ mockKeyListRequest(kvNamespace.id, []);
995
+
996
+ await expect(
997
+ runWrangler("publish")
998
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
999
+ `"File assets/too-large-file.txt is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits"`
1000
+ );
1001
+
1002
+ expect(std.out).toMatchInlineSnapshot(`
1003
+ "reading assets/large-file.txt...
1004
+ uploading as assets/large-file.0ea0637a45.txt..."
1005
+ `);
1006
+ expect(std.err).toMatchInlineSnapshot(`
1007
+ "File assets/too-large-file.txt is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits
1008
+
1009
+ %s
1010
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
1011
+ `);
1012
+ });
1013
+
1014
+ it("should error if the asset key is over 512 characters", async () => {
1015
+ const longFilePathAsset = {
1016
+ filePath: "assets/" + "folder/".repeat(100) + "file.txt",
1017
+ content: "content of file",
1018
+ };
1019
+ const kvNamespace = {
1020
+ title: "__test-name-workers_sites_assets",
1021
+ id: "__test-name-workers_sites_assets-id",
1022
+ };
1023
+ writeWranglerToml({
1024
+ build: { upload: { main: "./index.js" } },
1025
+ site: {
1026
+ bucket: "assets",
1027
+ },
1028
+ });
1029
+ writeWorkerSource();
1030
+ writeAssets([longFilePathAsset]);
1031
+ mockUploadWorkerRequest();
1032
+ mockSubDomainRequest();
1033
+ mockListKVNamespacesRequest(kvNamespace);
1034
+ mockKeyListRequest(kvNamespace.id, []);
1035
+
1036
+ await expect(
1037
+ runWrangler("publish")
1038
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1039
+ `"The asset path key \\"assets/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.3da0d0cd12.txt\\" exceeds the maximum key size limit of 512. See https://developers.cloudflare.com/workers/platform/limits#kv-limits\\","`
1040
+ );
1041
+
1042
+ expect(std.out).toMatchInlineSnapshot(
1043
+ `"reading assets/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.txt..."`
1044
+ );
1045
+ expect(std.err).toMatchInlineSnapshot(`
1046
+ "The asset path key \\"assets/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.3da0d0cd12.txt\\" exceeds the maximum key size limit of 512. See https://developers.cloudflare.com/workers/platform/limits#kv-limits\\",
1047
+
1048
+ %s
1049
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
1050
+ `);
1051
+ });
1052
+ });
1053
+
1054
+ describe("custom builds", () => {
1055
+ it("should run a custom build before publishing", async () => {
1056
+ writeWranglerToml({
1057
+ build: {
1058
+ command: `node -e "console.log('custom build'); require('fs').writeFileSync('index.js', 'export default { fetch(){ return new Response(123) } }')"`,
1059
+ },
1060
+ });
1061
+
1062
+ mockUploadWorkerRequest({
1063
+ expectedEntry: "return new Response(123)",
1064
+ });
1065
+ mockSubDomainRequest();
1066
+
1067
+ await runWrangler("publish index.js");
1068
+ expect(std.out).toMatchInlineSnapshot(`
1069
+ "running:
1070
+ node -e \\"console.log('custom build'); require('fs').writeFileSync('index.js', 'export default { fetch(){ return new Response(123) } }')\\"
1071
+ Uploaded
1072
+ test-name
1073
+ (TIMINGS)
1074
+ Deployed
1075
+ test-name
1076
+ (TIMINGS)
1077
+
1078
+ test-name.test-sub-domain.workers.dev"
1079
+ `);
1080
+ expect(std.err).toMatchInlineSnapshot(`""`);
1081
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1082
+ });
1083
+
1084
+ if (process.platform !== "win32") {
1085
+ it("should run a custom build of multiple steps combined by && before publishing", async () => {
1086
+ writeWranglerToml({
1087
+ build: {
1088
+ command: `echo "custom build" && echo "export default { fetch(){ return new Response(123) } }" > index.js`,
1089
+ },
1090
+ });
1091
+
1092
+ mockUploadWorkerRequest({
1093
+ expectedEntry: "return new Response(123)",
1094
+ });
1095
+ mockSubDomainRequest();
1096
+
1097
+ await runWrangler("publish index.js");
1098
+ expect(std.out).toMatchInlineSnapshot(`
1099
+ "running:
1100
+ echo \\"custom build\\" && echo \\"export default { fetch(){ return new Response(123) } }\\" > index.js
1101
+ Uploaded
1102
+ test-name
1103
+ (TIMINGS)
1104
+ Deployed
1105
+ test-name
1106
+ (TIMINGS)
1107
+
1108
+ test-name.test-sub-domain.workers.dev"
1109
+ `);
1110
+ expect(std.err).toMatchInlineSnapshot(`""`);
1111
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1112
+ });
1113
+ }
1114
+ });
1115
+
1116
+ describe("[wasm_modules]", () => {
1117
+ it("should be able to define wasm modules for service-worker format workers", async () => {
1118
+ writeWranglerToml({
1119
+ wasm_modules: {
1120
+ TESTWASMNAME: "./path/to/test.wasm",
1121
+ },
1122
+ });
1123
+ writeWorkerSource({ type: "sw" });
1124
+ fs.mkdirSync("./path/to", { recursive: true });
1125
+ fs.writeFileSync("./path/to/test.wasm", "SOME WASM CONTENT");
1126
+ mockUploadWorkerRequest({
1127
+ expectedType: "sw",
1128
+ expectedModules: { TESTWASMNAME: "SOME WASM CONTENT" },
1129
+ expectedBindings: [
1130
+ { name: "TESTWASMNAME", part: "TESTWASMNAME", type: "wasm_module" },
1131
+ ],
1132
+ });
1133
+ mockSubDomainRequest();
1134
+ await runWrangler("publish index.js");
1135
+ expect(std.out).toMatchInlineSnapshot(`
1136
+ "Uploaded
1137
+ test-name
1138
+ (TIMINGS)
1139
+ Deployed
1140
+ test-name
1141
+ (TIMINGS)
1142
+
1143
+ test-name.test-sub-domain.workers.dev"
1144
+ `);
1145
+ expect(std.err).toMatchInlineSnapshot(`""`);
1146
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1147
+ });
1148
+
1149
+ it("should error when defining wasm modules for modules format workers", async () => {
1150
+ writeWranglerToml({
1151
+ wasm_modules: {
1152
+ TESTWASMNAME: "./path/to/test.wasm",
1153
+ },
1154
+ });
1155
+ writeWorkerSource({ type: "esm" });
1156
+ fs.mkdirSync("./path/to", { recursive: true });
1157
+ fs.writeFileSync("./path/to/test.wasm", "SOME WASM CONTENT");
1158
+
1159
+ await expect(
1160
+ runWrangler("publish index.js")
1161
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1162
+ `"You cannot configure [wasm_modules] with an ES Module worker. Instead, import the .wasm module directly in your code"`
1163
+ );
1164
+ expect(std.out).toMatchInlineSnapshot(`""`);
1165
+ expect(std.err).toMatchInlineSnapshot(`
1166
+ "You cannot configure [wasm_modules] with an ES Module worker. Instead, import the .wasm module directly in your code
1167
+
1168
+ %s
1169
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
1170
+ `);
1171
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1172
+ });
1173
+
1174
+ it("should resolve wasm modules relative to the wrangler.toml file", async () => {
1175
+ fs.mkdirSync("./path/to/and/the/path/to/", { recursive: true });
1176
+ fs.writeFileSync(
1177
+ "./path/to/wrangler.toml",
1178
+ TOML.stringify({
1179
+ compatibility_date: "2022-01-12",
1180
+ name: "test-name",
1181
+ wasm_modules: {
1182
+ TESTWASMNAME: "./and/the/path/to/test.wasm",
1183
+ },
1184
+ }),
1185
+
1186
+ "utf-8"
1187
+ );
1188
+
1189
+ writeWorkerSource({ type: "sw" });
1190
+ fs.writeFileSync(
1191
+ "./path/to/and/the/path/to/test.wasm",
1192
+ "SOME WASM CONTENT"
1193
+ );
1194
+ mockUploadWorkerRequest({
1195
+ expectedType: "sw",
1196
+ expectedModules: { TESTWASMNAME: "SOME WASM CONTENT" },
1197
+ expectedBindings: [
1198
+ { name: "TESTWASMNAME", part: "TESTWASMNAME", type: "wasm_module" },
1199
+ ],
1200
+ });
1201
+ mockSubDomainRequest();
1202
+ await runWrangler("publish index.js --config ./path/to/wrangler.toml");
1203
+ expect(std.out).toMatchInlineSnapshot(`
1204
+ "Uploaded
1205
+ test-name
1206
+ (TIMINGS)
1207
+ Deployed
1208
+ test-name
1209
+ (TIMINGS)
1210
+
1211
+ test-name.test-sub-domain.workers.dev"
1212
+ `);
1213
+ expect(std.err).toMatchInlineSnapshot(`""`);
1214
+ expect(std.warn).toMatchInlineSnapshot(`""`);
1215
+ });
1216
+ });
1217
+ });
1218
+
1219
+ /** Write a mock wrangler.toml file to disk. */
1220
+ function writeWranglerToml(config: Omit<Config, "env"> = {}) {
1221
+ // We Omit `env` from config because TOML.stringify() appears to
1222
+ // have a weird type signature that appears to fail. We'll revisit this
1223
+ // when we write tests for publishing environments
1224
+ fs.writeFileSync(
1225
+ "./wrangler.toml",
1226
+ TOML.stringify({
1227
+ compatibility_date: "2022-01-12",
1228
+ name: "test-name",
1229
+ ...config,
1230
+ }),
1231
+
1232
+ "utf-8"
1233
+ );
1234
+ }
1235
+
1236
+ /** Write a mock Worker script to disk. */
1237
+ function writeWorkerSource({
1238
+ basePath = ".",
1239
+ format = "js",
1240
+ type = "esm",
1241
+ }: {
1242
+ basePath?: string;
1243
+ format?: "js" | "ts" | "jsx" | "tsx" | "mjs";
1244
+ type?: "esm" | "sw";
1245
+ } = {}) {
1246
+ if (basePath !== ".") {
1247
+ fs.mkdirSync(basePath, { recursive: true });
1248
+ }
1249
+ fs.writeFileSync(
1250
+ `${basePath}/index.${format}`,
1251
+ type === "esm"
1252
+ ? `import { foo } from "./another";
1253
+ export default {
1254
+ async fetch(request) {
1255
+ return new Response('Hello' + foo);
1256
+ },
1257
+ };`
1258
+ : `import { foo } from "./another";
1259
+ addEventListener('fetch', event => {
1260
+ event.respondWith(new Response('Hello' + foo));
1261
+ })`
1262
+ );
1263
+ fs.writeFileSync(`${basePath}/another.${format}`, `export const foo = 100;`);
1264
+ }
1265
+
1266
+ /** Write mock assets to the file system so they can be uploaded. */
1267
+ function writeAssets(assets: { filePath: string; content: string }[]) {
1268
+ for (const asset of assets) {
1269
+ fs.mkdirSync(path.dirname(asset.filePath), { recursive: true });
1270
+ fs.writeFileSync(asset.filePath, asset.content);
1271
+ }
1272
+ }
1273
+
1274
+ /** Create a mock handler for the request to upload a worker script. */
1275
+ function mockUploadWorkerRequest({
1276
+ available_on_subdomain = true,
1277
+ expectedEntry,
1278
+ expectedType = "esm",
1279
+ expectedBindings,
1280
+ expectedModules = {},
1281
+ env = undefined,
1282
+ }: {
1283
+ available_on_subdomain?: boolean;
1284
+ expectedEntry?: string;
1285
+ expectedType?: "esm" | "sw";
1286
+ expectedBindings?: unknown;
1287
+ expectedModules?: Record<string, string>;
1288
+ env?: string | undefined;
1289
+ } = {}) {
1290
+ setMockResponse(
1291
+ env
1292
+ ? `/accounts/:accountId/workers/services/:scriptName/environments/:envName`
1293
+ : "/accounts/:accountId/workers/scripts/:scriptName",
1294
+ "PUT",
1295
+ async ([_url, accountId, scriptName], { body }, queryParams) => {
1296
+ expect(accountId).toEqual("some-account-id");
1297
+ expect(scriptName).toEqual("test-name");
1298
+ expect(queryParams.get("available_on_subdomains")).toEqual("true");
1299
+ const formBody = body as FormData;
1300
+ if (expectedEntry !== undefined) {
1301
+ expect(await (formBody.get("index.js") as File).text()).toMatch(
1302
+ expectedEntry
1303
+ );
1304
+ }
1305
+
1306
+ const metadata = JSON.parse(
1307
+ formBody.get("metadata") as string
1308
+ ) as WorkerMetadata;
1309
+ if (expectedType === "esm") {
1310
+ expect(metadata.main_module).toEqual("index.js");
1311
+ } else {
1312
+ expect(metadata.body_part).toEqual("index.js");
1313
+ }
1314
+ if (expectedBindings !== undefined) {
1315
+ expect(metadata.bindings).toEqual(expectedBindings);
1316
+ }
1317
+ for (const [name, content] of Object.entries(expectedModules)) {
1318
+ expect(await (formBody.get(name) as File).text()).toMatch(content);
1319
+ }
1320
+
1321
+ return { available_on_subdomain };
1322
+ }
1323
+ );
1324
+ }
1325
+
1326
+ /** Create a mock handler for the request to get the account's subdomain. */
1327
+ function mockSubDomainRequest(subdomain = "test-sub-domain") {
1328
+ setMockResponse("/accounts/:accountId/workers/subdomain", () => {
1329
+ return { subdomain };
1330
+ });
1331
+ }
1332
+
1333
+ /** Create a mock handler for the request to get a list of all KV namespaces. */
1334
+ function mockListKVNamespacesRequest(...namespaces: KVNamespaceInfo[]) {
1335
+ setMockResponse(
1336
+ "/accounts/:accountId/storage/kv/namespaces",
1337
+ "GET",
1338
+ ([_url, accountId]) => {
1339
+ expect(accountId).toEqual("some-account-id");
1340
+ return namespaces;
1341
+ }
1342
+ );
1343
+ }
1344
+
1345
+ /** Create a mock handler for the request that tries to do a bulk upload of assets to a KV namespace. */
1346
+ function mockUploadAssetsToKVRequest(
1347
+ expectedNamespaceId: string,
1348
+ assets: { filePath: string; content: string }[]
1349
+ ) {
1350
+ setMockResponse(
1351
+ "/accounts/:accountId/storage/kv/namespaces/:namespaceId/bulk",
1352
+ "PUT",
1353
+ ([_url, accountId, namespaceId], { body }) => {
1354
+ expect(accountId).toEqual("some-account-id");
1355
+ expect(namespaceId).toEqual(expectedNamespaceId);
1356
+ const uploads = JSON.parse(body as string);
1357
+ expect(assets.length).toEqual(uploads.length);
1358
+ for (let i = 0; i < uploads.length; i++) {
1359
+ const asset = assets[i];
1360
+ const upload = uploads[i];
1361
+ // The asset key consists of:
1362
+ // - the basename of the filepath
1363
+ // - some hash value
1364
+ // - the extension
1365
+ const keyMatcher = new RegExp(
1366
+ "^" +
1367
+ asset.filePath
1368
+ .replace(/(\.[^.]+)$/, ".[a-z0-9]+$1")
1369
+ .replace(/\./g, "\\.")
1370
+ );
1371
+ expect(upload.key).toMatch(keyMatcher);
1372
+ // The asset value is base64 encoded.
1373
+ expect(upload.base64).toBe(true);
1374
+ expect(Buffer.from(upload.value, "base64").toString()).toEqual(
1375
+ asset.content
1376
+ );
1377
+ }
1378
+ return null;
1379
+ }
1380
+ );
1381
+ }