wrangler 2.0.28 → 2.0.29

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.
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { chdir } from "node:process";
3
+ import { ROUTES_SPEC_VERSION } from "../pages/constants";
3
4
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
4
5
  import {
5
6
  createFetchResult,
@@ -57,23 +58,23 @@ describe("pages", () => {
57
58
  await endEventLoop();
58
59
 
59
60
  expect(std.out).toMatchInlineSnapshot(`
60
- "wrangler pages
61
+ "wrangler pages
61
62
 
62
- ⚡️ Configure Cloudflare Pages
63
+ ⚡️ Configure Cloudflare Pages
63
64
 
64
- Commands:
65
- wrangler pages dev [directory] [-- command..] 🧑‍💻 Develop your full-stack Pages application locally
66
- wrangler pages project ⚡️ Interact with your Pages projects
67
- wrangler pages deployment 🚀 Interact with the deployments of a project
68
- wrangler pages publish [directory] 🆙 Publish a directory of static assets as a Pages deployment
65
+ Commands:
66
+ wrangler pages dev [directory] [-- command..] 🧑‍💻 Develop your full-stack Pages application locally
67
+ wrangler pages project ⚡️ Interact with your Pages projects
68
+ wrangler pages deployment 🚀 Interact with the deployments of a project
69
+ wrangler pages publish [directory] 🆙 Publish a directory of static assets as a Pages deployment
69
70
 
70
- Flags:
71
- -c, --config Path to .toml configuration file [string]
72
- -h, --help Show help [boolean]
73
- -v, --version Show version number [boolean]
71
+ Flags:
72
+ -c, --config Path to .toml configuration file [string]
73
+ -h, --help Show help [boolean]
74
+ -v, --version Show version number [boolean]
74
75
 
75
- 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
76
- `);
76
+ 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
77
+ `);
77
78
  });
78
79
 
79
80
  describe("beta message for subcommands", () => {
@@ -85,20 +86,20 @@ describe("pages", () => {
85
86
  );
86
87
 
87
88
  expect(std.out).toMatchInlineSnapshot(`
88
- "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
89
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
89
90
 
90
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
91
- `);
91
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
92
+ `);
92
93
  });
93
94
 
94
95
  it("should display for pages:functions:build", async () => {
95
96
  await expect(runWrangler("pages functions build")).rejects.toThrowError();
96
97
 
97
98
  expect(std.out).toMatchInlineSnapshot(`
98
- "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
99
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
99
100
 
100
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
101
- `);
101
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
102
+ `);
102
103
  });
103
104
 
104
105
  it("should display for pages:functions:optimize-routes", async () => {
@@ -109,10 +110,10 @@ describe("pages", () => {
109
110
  ).rejects.toThrowError();
110
111
 
111
112
  expect(std.out).toMatchInlineSnapshot(`
112
- "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
113
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
113
114
 
114
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
115
- `);
115
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
116
+ `);
116
117
  });
117
118
  });
118
119
 
@@ -234,9 +235,9 @@ describe("pages", () => {
234
235
  "pages project create a-new-project --production-branch=main"
235
236
  );
236
237
  expect(std.out).toMatchInlineSnapshot(`
237
- "✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
238
- To deploy a folder of assets, run 'wrangler pages publish [directory]'."
239
- `);
238
+ "✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
239
+ To deploy a folder of assets, run 'wrangler pages publish [directory]'."
240
+ `);
240
241
  });
241
242
  });
242
243
 
@@ -312,26 +313,26 @@ describe("pages", () => {
312
313
  await endEventLoop();
313
314
 
314
315
  expect(std.out).toMatchInlineSnapshot(`
315
- "wrangler pages publish [directory]
316
+ "wrangler pages publish [directory]
316
317
 
317
- 🆙 Publish a directory of static assets as a Pages deployment
318
+ 🆙 Publish a directory of static assets as a Pages deployment
318
319
 
319
- Positionals:
320
- directory The directory of static files to upload [string]
320
+ Positionals:
321
+ directory The directory of static files to upload [string]
321
322
 
322
- Flags:
323
- -h, --help Show help [boolean]
324
- -v, --version Show version number [boolean]
323
+ Flags:
324
+ -h, --help Show help [boolean]
325
+ -v, --version Show version number [boolean]
325
326
 
326
- Options:
327
- --project-name The name of the project you want to deploy to [string]
328
- --branch The name of the branch you want to deploy to [string]
329
- --commit-hash The SHA to attach to this deployment [string]
330
- --commit-message The commit message to attach to this deployment [string]
331
- --commit-dirty Whether or not the workspace should be considered dirty for this deployment [boolean]
327
+ Options:
328
+ --project-name The name of the project you want to deploy to [string]
329
+ --branch The name of the branch you want to deploy to [string]
330
+ --commit-hash The SHA to attach to this deployment [string]
331
+ --commit-message The commit message to attach to this deployment [string]
332
+ --commit-dirty Whether or not the workspace should be considered dirty for this deployment [boolean]
332
333
 
333
- 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
334
- `);
334
+ 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
335
+ `);
335
336
  });
336
337
 
337
338
  it("should upload a directory of files", async () => {
@@ -384,10 +385,10 @@ describe("pages", () => {
384
385
  const body = init.body as FormData;
385
386
  const manifest = JSON.parse(body.get("manifest") as string);
386
387
  expect(manifest).toMatchInlineSnapshot(`
387
- Object {
388
- "/logo.png": "2082190357cfd3617ccfe04f340c6247",
389
- }
390
- `);
388
+ Object {
389
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
390
+ }
391
+ `);
391
392
  });
392
393
 
393
394
  return {
@@ -398,13 +399,11 @@ describe("pages", () => {
398
399
 
399
400
  await runWrangler("pages publish . --project-name=foo");
400
401
 
401
- // TODO: Unmounting somehow loses this output
402
-
403
- // expect(std.out).toMatchInlineSnapshot(`
404
- // "✨ Success! Uploaded 1 files (TIMINGS)
402
+ expect(std.out).toMatchInlineSnapshot(`
403
+ "✨ Success! Uploaded 1 files (TIMINGS)
405
404
 
406
- // ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
407
- // `);
405
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
406
+ `);
408
407
  });
409
408
 
410
409
  it("should retry uploads", async () => {
@@ -455,10 +454,10 @@ describe("pages", () => {
455
454
  const body = init.body as FormData;
456
455
  const manifest = JSON.parse(body.get("manifest") as string);
457
456
  expect(manifest).toMatchInlineSnapshot(`
458
- Object {
459
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
460
- }
461
- `);
457
+ Object {
458
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
459
+ }
460
+ `);
462
461
  });
463
462
 
464
463
  return {
@@ -492,10 +491,10 @@ describe("pages", () => {
492
491
  }
493
492
 
494
493
  expect(std.out).toMatchInlineSnapshot(`
495
- "✨ Success! Uploaded 1 files (TIMINGS)
494
+ "✨ Success! Uploaded 1 files (TIMINGS)
496
495
 
497
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
498
- `);
496
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
497
+ `);
499
498
  });
500
499
 
501
500
  it("should refetch a JWT if it expires while uploading", async () => {
@@ -549,10 +548,10 @@ describe("pages", () => {
549
548
  const body = init.body as FormData;
550
549
  const manifest = JSON.parse(body.get("manifest") as string);
551
550
  expect(manifest).toMatchInlineSnapshot(`
552
- Object {
553
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
554
- }
555
- `);
551
+ Object {
552
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
553
+ }
554
+ `);
556
555
  });
557
556
 
558
557
  return {
@@ -589,10 +588,10 @@ describe("pages", () => {
589
588
  }
590
589
 
591
590
  expect(std.out).toMatchInlineSnapshot(`
592
- "✨ Success! Uploaded 1 files (TIMINGS)
591
+ "✨ Success! Uploaded 1 files (TIMINGS)
593
592
 
594
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
595
- `);
593
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
594
+ `);
596
595
  });
597
596
 
598
597
  it("should try to use multiple buckets (up to the max concurrency)", async () => {
@@ -640,13 +639,13 @@ describe("pages", () => {
640
639
  const body = init.body as FormData;
641
640
  const manifest = JSON.parse(body.get("manifest") as string);
642
641
  expect(manifest).toMatchInlineSnapshot(`
643
- Object {
644
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
645
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
646
- "/logo.png": "2082190357cfd3617ccfe04f340c6247",
647
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
648
- }
649
- `);
642
+ Object {
643
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
644
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
645
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
646
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
647
+ }
648
+ `);
650
649
  });
651
650
 
652
651
  return {
@@ -699,10 +698,10 @@ describe("pages", () => {
699
698
  );
700
699
 
701
700
  expect(std.out).toMatchInlineSnapshot(`
702
- "✨ Success! Uploaded 4 files (TIMINGS)
701
+ "✨ Success! Uploaded 4 files (TIMINGS)
703
702
 
704
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
705
- `);
703
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
704
+ `);
706
705
  });
707
706
 
708
707
  it("should resolve child directories correctly", async () => {
@@ -754,13 +753,13 @@ describe("pages", () => {
754
753
  const body = init.body as FormData;
755
754
  const manifest = JSON.parse(body.get("manifest") as string);
756
755
  expect(manifest).toMatchInlineSnapshot(`
757
- Object {
758
- "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
759
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
760
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
761
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
762
- }
763
- `);
756
+ Object {
757
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
758
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
759
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
760
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
761
+ }
762
+ `);
764
763
  });
765
764
 
766
765
  return {
@@ -813,10 +812,10 @@ describe("pages", () => {
813
812
  );
814
813
 
815
814
  expect(std.out).toMatchInlineSnapshot(`
816
- "✨ Success! Uploaded 4 files (TIMINGS)
815
+ "✨ Success! Uploaded 4 files (TIMINGS)
817
816
 
818
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
819
- `);
817
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
818
+ `);
820
819
  });
821
820
 
822
821
  it("should resolve the current directory correctly", async () => {
@@ -868,13 +867,13 @@ describe("pages", () => {
868
867
  const body = init.body as FormData;
869
868
  const manifest = JSON.parse(body.get("manifest") as string);
870
869
  expect(manifest).toMatchInlineSnapshot(`
871
- Object {
872
- "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
873
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
874
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
875
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
876
- }
877
- `);
870
+ Object {
871
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
872
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
873
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
874
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
875
+ }
876
+ `);
878
877
  });
879
878
 
880
879
  return {
@@ -928,10 +927,10 @@ describe("pages", () => {
928
927
  );
929
928
 
930
929
  expect(std.out).toMatchInlineSnapshot(`
931
- "✨ Success! Uploaded 4 files (TIMINGS)
930
+ "✨ Success! Uploaded 4 files (TIMINGS)
932
931
 
933
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
934
- `);
932
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
933
+ `);
935
934
  });
936
935
 
937
936
  it("should not error when directory names contain periods and houses a extensionless file", async () => {
@@ -1002,6 +1001,733 @@ describe("pages", () => {
1002
1001
  `"Pages does not support wrangler.toml"`
1003
1002
  );
1004
1003
  });
1004
+
1005
+ it("should upload a Functions project", async () => {
1006
+ // set up the directory of static files to upload.
1007
+ mkdirSync("public");
1008
+ writeFileSync("public/README.md", "This is a readme");
1009
+
1010
+ // set up /functions
1011
+ mkdirSync("functions");
1012
+ writeFileSync(
1013
+ "functions/hello.js",
1014
+ `
1015
+ export async function onRequest() {
1016
+ return new Response("Hello, world!");
1017
+ }
1018
+ `
1019
+ );
1020
+
1021
+ mockGetToken("<<funfetti-auth-jwt>>");
1022
+
1023
+ setMockResponse(
1024
+ "/pages/assets/check-missing",
1025
+ "POST",
1026
+ async (_, init) => {
1027
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1028
+ assertLater(() => {
1029
+ expect(init.headers).toMatchObject({
1030
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1031
+ });
1032
+ expect(body).toMatchObject({
1033
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1034
+ });
1035
+ });
1036
+ return body.hashes;
1037
+ }
1038
+ );
1039
+
1040
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1041
+ assertLater(() => {
1042
+ expect(init.headers).toMatchObject({
1043
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1044
+ });
1045
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1046
+ expect(body).toMatchObject([
1047
+ {
1048
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1049
+ value: Buffer.from("This is a readme").toString("base64"),
1050
+ metadata: {
1051
+ contentType: "text/markdown",
1052
+ },
1053
+ base64: true,
1054
+ },
1055
+ ]);
1056
+ });
1057
+ });
1058
+
1059
+ setMockResponse(
1060
+ `/pages/assets/upsert-hashes`,
1061
+ "POST",
1062
+ async (_, init) => {
1063
+ assertLater(() => {
1064
+ expect(init.headers).toMatchObject({
1065
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1066
+ });
1067
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1068
+ expect(body).toMatchObject({
1069
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1070
+ });
1071
+ });
1072
+
1073
+ return Promise.resolve(true);
1074
+ }
1075
+ );
1076
+
1077
+ setMockResponse(
1078
+ "/accounts/:accountId/pages/projects/foo/deployments",
1079
+ async ([_url, accountId], init) => {
1080
+ assertLater(async () => {
1081
+ expect(accountId).toEqual("some-account-id");
1082
+ expect(init.method).toEqual("POST");
1083
+ const body = init.body as FormData;
1084
+ const manifest = JSON.parse(body.get("manifest") as string);
1085
+
1086
+ // for Functions projects, we auto-generate a `_worker.js` and `_routes.json`
1087
+ // file, based on the contents of `/functions`
1088
+ const generatedWorkerJS = body.get("_worker.js") as Blob;
1089
+ const generatedRoutesJSON = await (
1090
+ body.get("_routes.json") as Blob
1091
+ ).text();
1092
+
1093
+ // make sure this is all we uploaded
1094
+ expect([...body.keys()]).toEqual([
1095
+ "manifest",
1096
+ "_worker.js",
1097
+ "_routes.json",
1098
+ ]);
1099
+
1100
+ expect(manifest).toMatchInlineSnapshot(`
1101
+ Object {
1102
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1103
+ }
1104
+ `);
1105
+
1106
+ // the contents of the generated `_worker.js` file is pretty massive, so I don't
1107
+ // think snapshot testing makes much sense here. Plus, calling
1108
+ // `.toMatchInlineSnapshot()` without any arguments, in order to generate that
1109
+ // snapshot value, doesn't generate anything in this case (probably because the
1110
+ // file contents is too big). So for now, let's test that _worker.js was indeed
1111
+ // generated and that the file size is greater than zero
1112
+ expect(generatedWorkerJS).not.toBeNull();
1113
+ expect(generatedWorkerJS.size).toBeGreaterThan(0);
1114
+
1115
+ expect(generatedRoutesJSON).toMatchInlineSnapshot(`
1116
+ "{
1117
+ \\"version\\": 1,
1118
+ \\"description\\": \\"Generated by wrangler@2.0.28\\",
1119
+ \\"include\\": [
1120
+ \\"/hello\\"
1121
+ ],
1122
+ \\"exclude\\": []
1123
+ }"
1124
+ `);
1125
+ });
1126
+
1127
+ return {
1128
+ url: "https://abcxyz.foo.pages.dev/",
1129
+ };
1130
+ }
1131
+ );
1132
+
1133
+ await runWrangler("pages publish public --project-name=foo");
1134
+
1135
+ expect(std.out).toMatchInlineSnapshot(`
1136
+ "Compiled Worker successfully.
1137
+ ✨ Success! Uploaded 1 files (TIMINGS)
1138
+
1139
+ ✨ Uploading Functions
1140
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1141
+ `);
1142
+
1143
+ expect(std.err).toMatchInlineSnapshot('""');
1144
+ });
1145
+
1146
+ it("should upload an Advanced Mode project", async () => {
1147
+ // set up the directory of static files to upload.
1148
+ mkdirSync("public");
1149
+ writeFileSync("public/README.md", "This is a readme");
1150
+
1151
+ // set up _worker.js
1152
+ writeFileSync(
1153
+ "public/_worker.js",
1154
+ `
1155
+ export default {
1156
+ async fetch(request, env) {
1157
+ const url = new URL(request.url);
1158
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1159
+ };
1160
+ `
1161
+ );
1162
+
1163
+ mockGetToken("<<funfetti-auth-jwt>>");
1164
+
1165
+ setMockResponse(
1166
+ "/pages/assets/check-missing",
1167
+ "POST",
1168
+ async (_, init) => {
1169
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1170
+ assertLater(() => {
1171
+ expect(init.headers).toMatchObject({
1172
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1173
+ });
1174
+ expect(body).toMatchObject({
1175
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1176
+ });
1177
+ });
1178
+ return body.hashes;
1179
+ }
1180
+ );
1181
+
1182
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1183
+ assertLater(() => {
1184
+ expect(init.headers).toMatchObject({
1185
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1186
+ });
1187
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1188
+ expect(body).toMatchObject([
1189
+ {
1190
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1191
+ value: Buffer.from("This is a readme").toString("base64"),
1192
+ metadata: {
1193
+ contentType: "text/markdown",
1194
+ },
1195
+ base64: true,
1196
+ },
1197
+ ]);
1198
+ });
1199
+ });
1200
+
1201
+ setMockResponse(
1202
+ "/accounts/:accountId/pages/projects/foo/deployments",
1203
+ async ([_url, accountId], init) => {
1204
+ assertLater(async () => {
1205
+ expect(accountId).toEqual("some-account-id");
1206
+ expect(init.method).toEqual("POST");
1207
+ const body = init.body as FormData;
1208
+ const manifest = JSON.parse(body.get("manifest") as string);
1209
+ const customWorkerJS = await (
1210
+ body.get("_worker.js") as Blob
1211
+ ).text();
1212
+
1213
+ // make sure this is all we uploaded
1214
+ expect([...body.keys()]).toEqual(["manifest", "_worker.js"]);
1215
+
1216
+ expect(manifest).toMatchInlineSnapshot(`
1217
+ Object {
1218
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1219
+ }
1220
+ `);
1221
+
1222
+ expect(customWorkerJS).toMatchInlineSnapshot(`
1223
+ "
1224
+ export default {
1225
+ async fetch(request, env) {
1226
+ const url = new URL(request.url);
1227
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1228
+ };
1229
+ "
1230
+ `);
1231
+ });
1232
+
1233
+ return {
1234
+ url: "https://abcxyz.foo.pages.dev/",
1235
+ };
1236
+ }
1237
+ );
1238
+
1239
+ await runWrangler("pages publish public --project-name=foo");
1240
+
1241
+ expect(std.out).toMatchInlineSnapshot(`
1242
+ "✨ Success! Uploaded 1 files (TIMINGS)
1243
+
1244
+ ✨ Uploading _worker.js
1245
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1246
+ `);
1247
+
1248
+ expect(std.err).toMatchInlineSnapshot('""');
1249
+ });
1250
+
1251
+ it("should upload _routes.json for Functions projects, if provided", async () => {
1252
+ // set up the directory of static files to upload.
1253
+ mkdirSync("public");
1254
+ writeFileSync("public/README.md", "This is a readme");
1255
+
1256
+ // set up /functions
1257
+ mkdirSync("functions");
1258
+ writeFileSync(
1259
+ "functions/hello.js",
1260
+ `
1261
+ export async function onRequest() {
1262
+ return new Response("Hello, world!");
1263
+ }
1264
+ `
1265
+ );
1266
+
1267
+ writeFileSync(
1268
+ "functions/goodbye.ts",
1269
+ `
1270
+ export async function onRequest() {
1271
+ return new Response("Bye bye!");
1272
+ }
1273
+ `
1274
+ );
1275
+
1276
+ // set up _routes.json
1277
+ writeFileSync(
1278
+ "public/_routes.json",
1279
+ `
1280
+ {
1281
+ "version": ${ROUTES_SPEC_VERSION},
1282
+ "description": "Custom _routes.json file",
1283
+ "include": ["/hello"],
1284
+ "exclude": []
1285
+ }
1286
+ `
1287
+ );
1288
+
1289
+ mockGetToken("<<funfetti-auth-jwt>>");
1290
+
1291
+ setMockResponse(
1292
+ "/pages/assets/check-missing",
1293
+ "POST",
1294
+ async (_, init) => {
1295
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1296
+ assertLater(() => {
1297
+ expect(init.headers).toMatchObject({
1298
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1299
+ });
1300
+ expect(body).toMatchObject({
1301
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1302
+ });
1303
+ });
1304
+ return body.hashes;
1305
+ }
1306
+ );
1307
+
1308
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1309
+ assertLater(() => {
1310
+ expect(init.headers).toMatchObject({
1311
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1312
+ });
1313
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1314
+ expect(body).toMatchObject([
1315
+ {
1316
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1317
+ value: Buffer.from("This is a readme").toString("base64"),
1318
+ metadata: {
1319
+ contentType: "text/markdown",
1320
+ },
1321
+ base64: true,
1322
+ },
1323
+ ]);
1324
+ });
1325
+ });
1326
+
1327
+ setMockResponse(
1328
+ `/pages/assets/upsert-hashes`,
1329
+ "POST",
1330
+ async (_, init) => {
1331
+ assertLater(() => {
1332
+ expect(init.headers).toMatchObject({
1333
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1334
+ });
1335
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1336
+ expect(body).toMatchObject({
1337
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1338
+ });
1339
+ });
1340
+
1341
+ return Promise.resolve(true);
1342
+ }
1343
+ );
1344
+
1345
+ setMockResponse(
1346
+ "/accounts/:accountId/pages/projects/foo/deployments",
1347
+ async ([_url, accountId], init) => {
1348
+ assertLater(async () => {
1349
+ expect(accountId).toEqual("some-account-id");
1350
+ expect(init.method).toEqual("POST");
1351
+ const body = init.body as FormData;
1352
+ const manifest = JSON.parse(body.get("manifest") as string);
1353
+ const generatedWorkerJS = body.get("_worker.js") as Blob;
1354
+ const customRoutesJSON = await (
1355
+ body.get("_routes.json") as Blob
1356
+ ).text();
1357
+
1358
+ // make sure this is all we uploaded
1359
+ expect([...body.keys()]).toEqual([
1360
+ "manifest",
1361
+ "_worker.js",
1362
+ "_routes.json",
1363
+ ]);
1364
+
1365
+ expect(manifest).toMatchInlineSnapshot(`
1366
+ Object {
1367
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1368
+ }
1369
+ `);
1370
+
1371
+ // file content of generated `_worker.js` is too massive to snapshot test
1372
+ expect(generatedWorkerJS).not.toBeNull();
1373
+ expect(generatedWorkerJS.size).toBeGreaterThan(0);
1374
+
1375
+ expect(customRoutesJSON).toMatchInlineSnapshot(`
1376
+ "
1377
+ {
1378
+ \\"version\\": 1,
1379
+ \\"description\\": \\"Custom _routes.json file\\",
1380
+ \\"include\\": [\\"/hello\\"],
1381
+ \\"exclude\\": []
1382
+ }
1383
+ "
1384
+ `);
1385
+ });
1386
+
1387
+ return {
1388
+ url: "https://abcxyz.foo.pages.dev/",
1389
+ };
1390
+ }
1391
+ );
1392
+
1393
+ await runWrangler("pages publish public --project-name=foo");
1394
+
1395
+ expect(std.out).toMatchInlineSnapshot(`
1396
+ "Compiled Worker successfully.
1397
+ ✨ Success! Uploaded 1 files (TIMINGS)
1398
+
1399
+ ✨ Uploading Functions
1400
+ ✨ Uploading _routes.json
1401
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1402
+ `);
1403
+
1404
+ expect(std.warn).toMatchInlineSnapshot(`
1405
+ "▲ [WARNING] _routes.json is an experimental feature and is subject to change. Please use with care.
1406
+
1407
+ "
1408
+ `);
1409
+
1410
+ expect(std.err).toMatchInlineSnapshot('""');
1411
+ });
1412
+
1413
+ it("should not deploy Functions projects that provide an invalid custom _routes.json file", async () => {
1414
+ // set up the directory of static files to upload.
1415
+ mkdirSync("public");
1416
+ writeFileSync("public/README.md", "This is a readme");
1417
+
1418
+ // set up _routes.json
1419
+ writeFileSync(
1420
+ "public/_routes.json",
1421
+ `
1422
+ {
1423
+ "description": "Custom _routes.json file",
1424
+ "include": [],
1425
+ "exclude": []
1426
+ }
1427
+ `
1428
+ );
1429
+
1430
+ // set up /functions
1431
+ mkdirSync("functions");
1432
+ writeFileSync(
1433
+ "functions/hello.js",
1434
+ `
1435
+ export async function onRequest() {
1436
+ return new Response("Hello, world!");
1437
+ }
1438
+ `
1439
+ );
1440
+
1441
+ mockGetToken("<<funfetti-auth-jwt>>");
1442
+
1443
+ setMockResponse(
1444
+ "/pages/assets/check-missing",
1445
+ "POST",
1446
+ async (_, init) => {
1447
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1448
+ assertLater(() => {
1449
+ expect(init.headers).toMatchObject({
1450
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1451
+ });
1452
+ expect(body).toMatchObject({
1453
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1454
+ });
1455
+ });
1456
+ return body.hashes;
1457
+ }
1458
+ );
1459
+
1460
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1461
+ assertLater(() => {
1462
+ expect(init.headers).toMatchObject({
1463
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1464
+ });
1465
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1466
+ expect(body).toMatchObject([
1467
+ {
1468
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1469
+ value: Buffer.from("This is a readme").toString("base64"),
1470
+ metadata: {
1471
+ contentType: "text/markdown",
1472
+ },
1473
+ base64: true,
1474
+ },
1475
+ ]);
1476
+ });
1477
+ });
1478
+
1479
+ await expect(runWrangler("pages publish public --project-name=foo"))
1480
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
1481
+ "Invalid _routes.json file found at: public/_routes.json. Please make sure the JSON object has the following format:
1482
+ {
1483
+ version: ${ROUTES_SPEC_VERSION};
1484
+ include: string[];
1485
+ exclude: string[];
1486
+ }
1487
+ and that at least one include rule is provided.
1488
+ "
1489
+ `);
1490
+ });
1491
+
1492
+ it("should upload _routes.json for Advanced Mode projects, if provided", async () => {
1493
+ // set up the directory of static files to upload.
1494
+ mkdirSync("public");
1495
+ writeFileSync("public/README.md", "This is a readme");
1496
+
1497
+ // set up _routes.json
1498
+ writeFileSync(
1499
+ "public/_routes.json",
1500
+ `
1501
+ {
1502
+ "version": ${ROUTES_SPEC_VERSION},
1503
+ "description": "Custom _routes.json file",
1504
+ "include": ["/api/*"],
1505
+ "exclude": []
1506
+ }
1507
+ `
1508
+ );
1509
+
1510
+ // set up _worker.js
1511
+ writeFileSync(
1512
+ "public/_worker.js",
1513
+ `
1514
+ export default {
1515
+ async fetch(request, env) {
1516
+ const url = new URL(request.url);
1517
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1518
+ };
1519
+ `
1520
+ );
1521
+
1522
+ mockGetToken("<<funfetti-auth-jwt>>");
1523
+
1524
+ setMockResponse(
1525
+ "/pages/assets/check-missing",
1526
+ "POST",
1527
+ async (_, init) => {
1528
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1529
+ assertLater(() => {
1530
+ expect(init.headers).toMatchObject({
1531
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1532
+ });
1533
+ expect(body).toMatchObject({
1534
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1535
+ });
1536
+ });
1537
+ return body.hashes;
1538
+ }
1539
+ );
1540
+
1541
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1542
+ assertLater(() => {
1543
+ expect(init.headers).toMatchObject({
1544
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1545
+ });
1546
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1547
+ expect(body).toMatchObject([
1548
+ {
1549
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1550
+ value: Buffer.from("This is a readme").toString("base64"),
1551
+ metadata: {
1552
+ contentType: "text/markdown",
1553
+ },
1554
+ base64: true,
1555
+ },
1556
+ ]);
1557
+ });
1558
+ });
1559
+
1560
+ setMockResponse(
1561
+ `/pages/assets/upsert-hashes`,
1562
+ "POST",
1563
+ async (_, init) => {
1564
+ assertLater(() => {
1565
+ expect(init.headers).toMatchObject({
1566
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1567
+ });
1568
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1569
+ expect(body).toMatchObject({
1570
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1571
+ });
1572
+ });
1573
+
1574
+ return Promise.resolve(true);
1575
+ }
1576
+ );
1577
+
1578
+ setMockResponse(
1579
+ "/accounts/:accountId/pages/projects/foo/deployments",
1580
+ async ([_url, accountId], init) => {
1581
+ assertLater(async () => {
1582
+ expect(accountId).toEqual("some-account-id");
1583
+ expect(init.method).toEqual("POST");
1584
+ const body = init.body as FormData;
1585
+ const manifest = JSON.parse(body.get("manifest") as string);
1586
+ const customWorkerJS = await (
1587
+ body.get("_worker.js") as Blob
1588
+ ).text();
1589
+ const customRoutesJSON = await (
1590
+ body.get("_routes.json") as Blob
1591
+ ).text();
1592
+
1593
+ // make sure this is all we uploaded
1594
+ expect([...body.keys()]).toEqual([
1595
+ "manifest",
1596
+ "_worker.js",
1597
+ "_routes.json",
1598
+ ]);
1599
+
1600
+ expect(manifest).toMatchInlineSnapshot(`
1601
+ Object {
1602
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1603
+ }
1604
+ `);
1605
+
1606
+ expect(customWorkerJS).toMatchInlineSnapshot(`
1607
+ "
1608
+ export default {
1609
+ async fetch(request, env) {
1610
+ const url = new URL(request.url);
1611
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1612
+ };
1613
+ "
1614
+ `);
1615
+
1616
+ expect(customRoutesJSON).toMatchInlineSnapshot(`
1617
+ "
1618
+ {
1619
+ \\"version\\": 1,
1620
+ \\"description\\": \\"Custom _routes.json file\\",
1621
+ \\"include\\": [\\"/api/*\\"],
1622
+ \\"exclude\\": []
1623
+ }
1624
+ "
1625
+ `);
1626
+ });
1627
+
1628
+ return {
1629
+ url: "https://abcxyz.foo.pages.dev/",
1630
+ };
1631
+ }
1632
+ );
1633
+
1634
+ await runWrangler("pages publish public --project-name=foo");
1635
+
1636
+ expect(std.out).toMatchInlineSnapshot(`
1637
+ "✨ Success! Uploaded 1 files (TIMINGS)
1638
+
1639
+ ✨ Uploading _worker.js
1640
+ ✨ Uploading _routes.json
1641
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1642
+ `);
1643
+
1644
+ expect(std.warn).toMatchInlineSnapshot(`
1645
+ "▲ [WARNING] _routes.json is an experimental feature and is subject to change. Please use with care.
1646
+
1647
+ "
1648
+ `);
1649
+ expect(std.err).toMatchInlineSnapshot(`""`);
1650
+ });
1651
+
1652
+ it("should not deploy Advanced Mode projects that provide an invalid _routes.json file", async () => {
1653
+ // set up the directory of static files to upload.
1654
+ mkdirSync("public");
1655
+ writeFileSync("public/README.md", "This is a readme");
1656
+
1657
+ // set up _routes.json
1658
+ writeFileSync(
1659
+ "public/_routes.json",
1660
+ `
1661
+ {
1662
+ "description": "Custom _routes.json file",
1663
+ "include": [],
1664
+ "exclude": []
1665
+ }
1666
+ `
1667
+ );
1668
+
1669
+ // set up _worker.js
1670
+ writeFileSync(
1671
+ "public/_worker.js",
1672
+ `
1673
+ export default {
1674
+ async fetch(request, env) {
1675
+ const url = new URL(request.url);
1676
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1677
+ };
1678
+ `
1679
+ );
1680
+
1681
+ mockGetToken("<<funfetti-auth-jwt>>");
1682
+
1683
+ setMockResponse(
1684
+ "/pages/assets/check-missing",
1685
+ "POST",
1686
+ async (_, init) => {
1687
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1688
+ assertLater(() => {
1689
+ expect(init.headers).toMatchObject({
1690
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1691
+ });
1692
+ expect(body).toMatchObject({
1693
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1694
+ });
1695
+ });
1696
+ return body.hashes;
1697
+ }
1698
+ );
1699
+
1700
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1701
+ assertLater(() => {
1702
+ expect(init.headers).toMatchObject({
1703
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1704
+ });
1705
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1706
+ expect(body).toMatchObject([
1707
+ {
1708
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1709
+ value: Buffer.from("This is a readme").toString("base64"),
1710
+ metadata: {
1711
+ contentType: "text/markdown",
1712
+ },
1713
+ base64: true,
1714
+ },
1715
+ ]);
1716
+ });
1717
+ });
1718
+
1719
+ await expect(runWrangler("pages publish public --project-name=foo"))
1720
+ .rejects.toThrowErrorMatchingInlineSnapshot(`
1721
+ "Invalid _routes.json file found at: public/_routes.json. Please make sure the JSON object has the following format:
1722
+ {
1723
+ version: ${ROUTES_SPEC_VERSION};
1724
+ include: string[];
1725
+ exclude: string[];
1726
+ }
1727
+ and that at least one include rule is provided.
1728
+ "
1729
+ `);
1730
+ });
1005
1731
  });
1006
1732
 
1007
1733
  describe("project upload", () => {
@@ -1063,10 +1789,10 @@ describe("pages", () => {
1063
1789
  await runWrangler("pages project upload .");
1064
1790
 
1065
1791
  expect(std.out).toMatchInlineSnapshot(`
1066
- "✨ Success! Uploaded 1 files (TIMINGS)
1792
+ "✨ Success! Uploaded 1 files (TIMINGS)
1067
1793
 
1068
- ✨ Upload complete!"
1069
- `);
1794
+ ✨ Upload complete!"
1795
+ `);
1070
1796
  });
1071
1797
 
1072
1798
  it("should retry uploads", async () => {
@@ -1131,10 +1857,10 @@ describe("pages", () => {
1131
1857
  }
1132
1858
 
1133
1859
  expect(std.out).toMatchInlineSnapshot(`
1134
- "✨ Success! Uploaded 1 files (TIMINGS)
1860
+ "✨ Success! Uploaded 1 files (TIMINGS)
1135
1861
 
1136
- ✨ Upload complete!"
1137
- `);
1862
+ ✨ Upload complete!"
1863
+ `);
1138
1864
  });
1139
1865
 
1140
1866
  it("should try to use multiple buckets (up to the max concurrency)", async () => {
@@ -1217,10 +1943,10 @@ describe("pages", () => {
1217
1943
  );
1218
1944
 
1219
1945
  expect(std.out).toMatchInlineSnapshot(`
1220
- "✨ Success! Uploaded 4 files (TIMINGS)
1946
+ "✨ Success! Uploaded 4 files (TIMINGS)
1221
1947
 
1222
- ✨ Upload complete!"
1223
- `);
1948
+ ✨ Upload complete!"
1949
+ `);
1224
1950
  });
1225
1951
 
1226
1952
  it("should not error when directory names contain periods and houses a extensionless file", async () => {