wrangler 0.0.0-e64812e → 0.0.0-e8e684c7

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 (214) hide show
  1. package/README.md +27 -3
  2. package/bin/wrangler.js +150 -62
  3. package/kv-asset-handler.js +1 -0
  4. package/miniflare-dist/index.mjs +663 -7
  5. package/package.json +173 -152
  6. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  7. package/src/__tests__/config-cache.test.ts +30 -24
  8. package/src/__tests__/configuration.test.ts +4015 -3363
  9. package/src/__tests__/dev.test.tsx +1290 -952
  10. package/src/__tests__/generate.test.ts +91 -0
  11. package/src/__tests__/guess-worker-format.test.ts +68 -68
  12. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  13. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  14. package/src/__tests__/helpers/mock-bin.ts +20 -20
  15. package/src/__tests__/helpers/mock-cfetch.ts +179 -94
  16. package/src/__tests__/helpers/mock-console.ts +49 -39
  17. package/src/__tests__/helpers/mock-dialogs.ts +94 -71
  18. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +30 -30
  20. package/src/__tests__/helpers/mock-istty.ts +65 -18
  21. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  22. package/src/__tests__/helpers/mock-kv.ts +26 -26
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
  24. package/src/__tests__/helpers/mock-process.ts +39 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  26. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  27. package/src/__tests__/helpers/run-in-tmp.ts +27 -28
  28. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  29. package/src/__tests__/helpers/write-worker-source.ts +16 -16
  30. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  31. package/src/__tests__/https-options.test.ts +142 -109
  32. package/src/__tests__/index.test.ts +222 -233
  33. package/src/__tests__/init.test.ts +2139 -1603
  34. package/src/__tests__/jest.setup.ts +114 -34
  35. package/src/__tests__/kv.test.ts +1222 -1032
  36. package/src/__tests__/logger.test.ts +100 -74
  37. package/src/__tests__/metrics.test.ts +450 -0
  38. package/src/__tests__/package-manager.test.ts +303 -303
  39. package/src/__tests__/pages.test.ts +1173 -652
  40. package/src/__tests__/parse.test.ts +252 -252
  41. package/src/__tests__/publish.test.ts +6788 -5301
  42. package/src/__tests__/pubsub.test.ts +367 -0
  43. package/src/__tests__/r2.test.ts +328 -225
  44. package/src/__tests__/route.test.ts +18 -18
  45. package/src/__tests__/secret.test.ts +470 -459
  46. package/src/__tests__/tail.test.ts +676 -564
  47. package/src/__tests__/test-old-node-version.js +31 -0
  48. package/src/__tests__/user.test.ts +136 -112
  49. package/src/__tests__/validate-dev-props.test.ts +56 -0
  50. package/src/__tests__/version.test.ts +35 -0
  51. package/src/__tests__/whoami.test.tsx +267 -83
  52. package/src/__tests__/worker-namespace.test.ts +329 -0
  53. package/src/abort.d.ts +1 -1
  54. package/src/api/dev.ts +93 -0
  55. package/src/api/index.ts +1 -0
  56. package/src/bundle-reporter.tsx +29 -0
  57. package/src/bundle.ts +430 -157
  58. package/src/cfetch/index.ts +80 -80
  59. package/src/cfetch/internal.ts +244 -91
  60. package/src/cli.ts +21 -7
  61. package/src/config/config.ts +224 -195
  62. package/src/config/diagnostics.ts +61 -61
  63. package/src/config/environment.ts +422 -357
  64. package/src/config/index.ts +219 -193
  65. package/src/config/validation-helpers.ts +379 -357
  66. package/src/config/validation.ts +1690 -1367
  67. package/src/config-cache.ts +81 -41
  68. package/src/create-worker-preview.ts +208 -136
  69. package/src/create-worker-upload-form.ts +275 -238
  70. package/src/dev/dev-vars.ts +13 -13
  71. package/src/dev/dev.tsx +477 -351
  72. package/src/dev/local.tsx +426 -295
  73. package/src/dev/remote.tsx +406 -211
  74. package/src/dev/use-esbuild.ts +154 -94
  75. package/src/dev/validate-dev-props.ts +31 -0
  76. package/src/dev-registry.tsx +160 -0
  77. package/src/dev.tsx +696 -0
  78. package/src/dialogs.tsx +97 -97
  79. package/src/dispatch-namespace.ts +206 -0
  80. package/src/durable.ts +87 -87
  81. package/src/entry.ts +234 -228
  82. package/src/environment-variables.ts +23 -23
  83. package/src/errors.ts +6 -6
  84. package/src/faye-websocket.d.ts +6 -0
  85. package/src/generate.ts +131 -0
  86. package/src/git-client.ts +42 -0
  87. package/src/global-wrangler-config-path.ts +26 -0
  88. package/src/https-options.ts +84 -80
  89. package/src/index.tsx +2164 -2768
  90. package/src/init.ts +622 -0
  91. package/src/inspect.ts +678 -593
  92. package/src/intl-polyfill.d.ts +123 -123
  93. package/src/is-ci.ts +14 -0
  94. package/src/is-interactive.ts +12 -0
  95. package/src/kv.ts +299 -277
  96. package/src/logger.ts +46 -39
  97. package/src/metrics/index.ts +5 -0
  98. package/src/metrics/metrics-config.ts +239 -0
  99. package/src/metrics/metrics-dispatcher.ts +96 -0
  100. package/src/metrics/metrics-usage-headers.ts +24 -0
  101. package/src/metrics/send-event.ts +92 -0
  102. package/src/miniflare-cli/assets.ts +554 -0
  103. package/src/miniflare-cli/enum-keys.ts +8 -8
  104. package/src/miniflare-cli/index.ts +198 -32
  105. package/src/miniflare-cli/request-context.ts +18 -18
  106. package/src/module-collection.ts +212 -212
  107. package/src/open-in-browser.ts +4 -6
  108. package/src/package-manager.ts +173 -128
  109. package/src/pages/build.tsx +238 -0
  110. package/src/pages/constants.ts +12 -0
  111. package/src/pages/deployments.tsx +106 -0
  112. package/src/pages/dev.tsx +528 -0
  113. package/src/pages/errors.ts +22 -0
  114. package/src/pages/functions/buildPlugin.ts +109 -0
  115. package/src/pages/functions/buildWorker.ts +155 -0
  116. package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
  117. package/src/pages/functions/filepath-routing.ts +189 -0
  118. package/src/pages/functions/identifiers.ts +78 -0
  119. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  120. package/src/pages/functions/routes-consolidation.ts +73 -0
  121. package/src/pages/functions/routes-transformation.test.ts +271 -0
  122. package/src/pages/functions/routes-transformation.ts +122 -0
  123. package/src/pages/functions/routes.ts +151 -0
  124. package/src/pages/functions.tsx +96 -0
  125. package/src/pages/index.tsx +94 -0
  126. package/src/pages/projects.tsx +169 -0
  127. package/src/pages/publish.tsx +391 -0
  128. package/src/pages/types.ts +49 -0
  129. package/src/pages/upload.tsx +413 -0
  130. package/src/pages/utils.ts +12 -0
  131. package/src/parse.ts +209 -125
  132. package/src/paths.ts +6 -6
  133. package/src/preview.ts +31 -0
  134. package/src/proxy.ts +410 -389
  135. package/src/publish.ts +773 -534
  136. package/src/pubsub/index.ts +286 -0
  137. package/src/pubsub/pubsub-commands.tsx +623 -0
  138. package/src/r2.ts +100 -19
  139. package/src/selfsigned.d.ts +23 -23
  140. package/src/sites.tsx +278 -201
  141. package/src/tail/filters.ts +111 -109
  142. package/src/tail/index.ts +230 -217
  143. package/src/tail/printing.ts +86 -46
  144. package/src/update-check.ts +11 -11
  145. package/src/user/choose-account.tsx +69 -0
  146. package/src/user/env-vars.ts +46 -0
  147. package/src/user/generate-auth-url.ts +33 -0
  148. package/src/user/generate-random-state.ts +16 -0
  149. package/src/user/index.ts +3 -0
  150. package/src/user/user.tsx +1191 -0
  151. package/src/whoami.tsx +141 -42
  152. package/src/worker.ts +123 -99
  153. package/src/zones.ts +167 -0
  154. package/templates/checked-fetch.js +17 -0
  155. package/templates/first-party-worker-module-facade.ts +18 -0
  156. package/templates/format-dev-errors.ts +32 -0
  157. package/templates/new-worker-scheduled.js +3 -3
  158. package/templates/new-worker-scheduled.ts +15 -15
  159. package/templates/new-worker.js +3 -3
  160. package/templates/new-worker.ts +15 -15
  161. package/templates/no-op-worker.js +10 -0
  162. package/templates/pages-shim.ts +9 -0
  163. package/templates/pages-template-plugin.ts +167 -0
  164. package/templates/pages-template-worker.ts +172 -0
  165. package/templates/serve-static-assets.ts +63 -0
  166. package/templates/service-bindings-module-facade.js +54 -0
  167. package/templates/service-bindings-sw-facade.js +42 -0
  168. package/templates/tsconfig.json +95 -95
  169. package/wrangler-dist/cli.d.ts +338 -0
  170. package/wrangler-dist/cli.js +95449 -67524
  171. package/pages/functions/buildPlugin.ts +0 -105
  172. package/pages/functions/buildWorker.ts +0 -151
  173. package/pages/functions/filepath-routing.ts +0 -189
  174. package/pages/functions/identifiers.ts +0 -78
  175. package/pages/functions/routes.ts +0 -156
  176. package/pages/functions/template-plugin.ts +0 -147
  177. package/pages/functions/template-worker.ts +0 -143
  178. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  179. package/src/pages.tsx +0 -2038
  180. package/src/user.tsx +0 -1203
  181. package/templates/static-asset-facade.js +0 -47
  182. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  183. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  184. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  185. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  186. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  187. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  188. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  189. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  190. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  191. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  192. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  193. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  194. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  195. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  196. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  197. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  198. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  199. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  200. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  201. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  202. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  203. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  204. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  205. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  206. package/vendor/wrangler-mime/LICENSE +0 -21
  207. package/vendor/wrangler-mime/Mime.js +0 -97
  208. package/vendor/wrangler-mime/README.md +0 -187
  209. package/vendor/wrangler-mime/cli.js +0 -46
  210. package/vendor/wrangler-mime/index.js +0 -4
  211. package/vendor/wrangler-mime/lite.js +0 -4
  212. package/vendor/wrangler-mime/package.json +0 -52
  213. package/vendor/wrangler-mime/types/other.js +0 -1
  214. package/vendor/wrangler-mime/types/standard.js +0 -1
@@ -1,54 +1,62 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { chdir } from "node:process";
2
3
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
3
4
  import {
4
- createFetchResult,
5
- setMockRawResponse,
6
- setMockResponse,
7
- unsetAllMocks,
5
+ createFetchResult,
6
+ setMockRawResponse,
7
+ setMockResponse,
8
+ unsetAllMocks,
8
9
  } from "./helpers/mock-cfetch";
9
10
  import { mockConsoleMethods } from "./helpers/mock-console";
10
11
  import { runInTempDir } from "./helpers/run-in-tmp";
11
12
  import { runWrangler } from "./helpers/run-wrangler";
12
- import type { Deployment, Project, UploadPayloadFile } from "../pages";
13
+ import type { Deployment, Project, UploadPayloadFile } from "../pages/types";
13
14
  import type { FormData, RequestInit } from "undici";
14
15
 
15
16
  // Asserting within mock responses get swallowed, so run them out-of-band
16
17
  const outOfBandTests: (() => void)[] = [];
17
18
  function assertLater(fn: () => void) {
18
- outOfBandTests.push(fn);
19
+ outOfBandTests.push(fn);
19
20
  }
20
21
 
21
22
  function mockGetToken(jwt: string) {
22
- return setMockResponse(
23
- "/accounts/:accountId/pages/projects/foo/upload-token",
24
- async ([_url, accountId]) => {
25
- assertLater(() => {
26
- expect(accountId).toEqual("some-account-id");
27
- });
28
-
29
- return { jwt };
30
- }
31
- );
23
+ return setMockResponse(
24
+ "/accounts/:accountId/pages/projects/foo/upload-token",
25
+ async ([_url, accountId]) => {
26
+ assertLater(() => {
27
+ expect(accountId).toEqual("some-account-id");
28
+ });
29
+
30
+ return { jwt };
31
+ }
32
+ );
32
33
  }
33
34
 
34
35
  describe("pages", () => {
35
- runInTempDir();
36
- const std = mockConsoleMethods();
37
- function endEventLoop() {
38
- return new Promise((resolve) => setImmediate(resolve));
39
- }
40
- beforeEach(() => {
41
- outOfBandTests.length = 0;
42
- });
43
- afterEach(() => {
44
- outOfBandTests.forEach((fn) => fn());
45
- });
46
-
47
- it("should should display a list of available subcommands, for pages with no subcommand", async () => {
48
- await runWrangler("pages");
49
- await endEventLoop();
50
-
51
- expect(std.out).toMatchInlineSnapshot(`
36
+ runInTempDir();
37
+ const std = mockConsoleMethods();
38
+ function endEventLoop() {
39
+ return new Promise((resolve) => setImmediate(resolve));
40
+ }
41
+ beforeEach(() => {
42
+ outOfBandTests.length = 0;
43
+ });
44
+ afterEach(() => {
45
+ outOfBandTests.forEach((fn) => fn());
46
+ });
47
+
48
+ beforeEach(() => {
49
+ // @ts-expect-error we're using a very simple setTimeout mock here
50
+ jest.spyOn(global, "setTimeout").mockImplementation((fn, _period) => {
51
+ setImmediate(fn);
52
+ });
53
+ });
54
+
55
+ it("should should display a list of available subcommands, for pages with no subcommand", async () => {
56
+ await runWrangler("pages");
57
+ await endEventLoop();
58
+
59
+ expect(std.out).toMatchInlineSnapshot(`
52
60
  "wrangler pages
53
61
 
54
62
  ⚡️ Configure Cloudflare Pages
@@ -66,230 +74,244 @@ describe("pages", () => {
66
74
 
67
75
  🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
68
76
  `);
69
- });
77
+ });
70
78
 
71
- describe("beta message for subcommands", () => {
72
- it("should display for pages:dev", async () => {
73
- await expect(
74
- runWrangler("pages dev")
75
- ).rejects.toThrowErrorMatchingInlineSnapshot(
76
- `"Must specify a directory of static assets to serve or a command to run."`
77
- );
79
+ describe("beta message for subcommands", () => {
80
+ it("should display for pages:dev", async () => {
81
+ await expect(
82
+ runWrangler("pages dev")
83
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
84
+ `"Must specify a directory of static assets to serve or a command to run."`
85
+ );
78
86
 
79
- expect(std.out).toMatchInlineSnapshot(`
87
+ expect(std.out).toMatchInlineSnapshot(`
80
88
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
81
89
 
82
90
  If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
83
91
  `);
84
- });
92
+ });
85
93
 
86
- it("should display for pages:functions:build", async () => {
87
- await expect(runWrangler("pages functions build")).rejects.toThrowError();
94
+ it("should display for pages:functions:build", async () => {
95
+ await expect(runWrangler("pages functions build")).rejects.toThrowError();
88
96
 
89
- expect(std.out).toMatchInlineSnapshot(`
97
+ expect(std.out).toMatchInlineSnapshot(`
90
98
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
91
99
 
92
100
  If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
93
101
  `);
94
- });
95
- });
96
-
97
- describe("project list", () => {
98
- mockAccountId();
99
- mockApiToken();
100
-
101
- afterEach(() => {
102
- unsetAllMocks();
103
- });
104
-
105
- function mockListRequest(projects: unknown[]) {
106
- const requests = { count: 0 };
107
- setMockResponse(
108
- "/accounts/:accountId/pages/projects",
109
- ([_url, accountId], init, query) => {
110
- requests.count++;
111
- const pageSize = Number(query.get("per_page"));
112
- const page = Number(query.get("page"));
113
- const expectedPageSize = 10;
114
- const expectedPage = requests.count;
115
- assertLater(() => {
116
- expect(accountId).toEqual("some-account-id");
117
- expect(pageSize).toEqual(expectedPageSize);
118
- expect(page).toEqual(expectedPage);
119
- expect(init).toEqual({});
120
- });
121
- return projects.slice((page - 1) * pageSize, page * pageSize);
122
- }
123
- );
124
- return requests;
125
- }
126
-
127
- it("should make request to list projects", async () => {
128
- const projects: Project[] = [
129
- {
130
- name: "dogs",
131
- subdomain: "docs.pages.dev",
132
- domains: ["dogs.pages.dev"],
133
- source: {
134
- type: "github",
135
- },
136
- latest_deployment: {
137
- modified_on: "2021-11-17T14:52:26.133835Z",
138
- },
139
- created_on: "2021-11-17T14:52:26.133835Z",
140
- production_branch: "main",
141
- },
142
- {
143
- name: "cats",
144
- subdomain: "cats.pages.dev",
145
- domains: ["cats.pages.dev", "kitten.com"],
146
- latest_deployment: {
147
- modified_on: "2021-11-17T14:52:26.133835Z",
148
- },
149
- created_on: "2021-11-17T14:52:26.133835Z",
150
- production_branch: "main",
151
- },
152
- ];
153
-
154
- const requests = mockListRequest(projects);
155
- await runWrangler("pages project list");
156
-
157
- expect(requests.count).toBe(1);
158
- });
159
-
160
- it("should make multiple requests for paginated results", async () => {
161
- const projects: Project[] = [];
162
- for (let i = 0; i < 15; i++) {
163
- projects.push({
164
- name: "dogs" + i,
165
- subdomain: i + "dogs.pages.dev",
166
- domains: [i + "dogs.pages.dev"],
167
- source: {
168
- type: "github",
169
- },
170
- latest_deployment: {
171
- modified_on: "2021-11-17T14:52:26.133835Z",
172
- },
173
- created_on: "2021-11-17T14:52:26.133835Z",
174
- production_branch: "main",
175
- });
176
- }
177
- const requests = mockListRequest(projects);
178
- await runWrangler("pages project list");
179
- expect(requests.count).toEqual(2);
180
- });
181
- });
182
-
183
- describe("project create", () => {
184
- mockAccountId();
185
- mockApiToken();
186
-
187
- afterEach(() => {
188
- unsetAllMocks();
189
- });
190
-
191
- it("should create a project with a production branch", async () => {
192
- setMockResponse(
193
- "/accounts/:accountId/pages/projects",
194
- ([_url, accountId], init) => {
195
- const body = JSON.parse(init.body as string);
196
- assertLater(() => {
197
- expect(accountId).toEqual("some-account-id");
198
- expect(init.method).toEqual("POST");
199
- expect(body).toEqual({
200
- name: "a-new-project",
201
- production_branch: "main",
202
- });
203
- });
204
- return {
205
- name: "a-new-project",
206
- subdomain: "a-new-project.pages.dev",
207
- production_branch: "main",
208
- };
209
- }
210
- );
211
- await runWrangler(
212
- "pages project create a-new-project --production-branch=main"
213
- );
214
- expect(std.out).toMatchInlineSnapshot(`
102
+ });
103
+
104
+ it("should display for pages:functions:optimize-routes", async () => {
105
+ await expect(
106
+ runWrangler(
107
+ 'pages functions optimize-routes --routes-path="/build/_routes.json" --output-routes-path="/build/_optimized-routes.json"'
108
+ )
109
+ ).rejects.toThrowError();
110
+
111
+ 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
+
114
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
115
+ `);
116
+ });
117
+ });
118
+
119
+ describe("project list", () => {
120
+ mockAccountId();
121
+ mockApiToken();
122
+
123
+ afterEach(() => {
124
+ unsetAllMocks();
125
+ });
126
+
127
+ function mockListRequest(projects: unknown[]) {
128
+ const requests = { count: 0 };
129
+ setMockResponse(
130
+ "/accounts/:accountId/pages/projects",
131
+ ([_url, accountId], init, query) => {
132
+ requests.count++;
133
+ const pageSize = Number(query.get("per_page"));
134
+ const page = Number(query.get("page"));
135
+ const expectedPageSize = 10;
136
+ const expectedPage = requests.count;
137
+ assertLater(() => {
138
+ expect(accountId).toEqual("some-account-id");
139
+ expect(pageSize).toEqual(expectedPageSize);
140
+ expect(page).toEqual(expectedPage);
141
+ expect(init).toEqual({});
142
+ });
143
+ return projects.slice((page - 1) * pageSize, page * pageSize);
144
+ }
145
+ );
146
+ return requests;
147
+ }
148
+
149
+ it("should make request to list projects", async () => {
150
+ const projects: Project[] = [
151
+ {
152
+ name: "dogs",
153
+ subdomain: "docs.pages.dev",
154
+ domains: ["dogs.pages.dev"],
155
+ source: {
156
+ type: "github",
157
+ },
158
+ latest_deployment: {
159
+ modified_on: "2021-11-17T14:52:26.133835Z",
160
+ },
161
+ created_on: "2021-11-17T14:52:26.133835Z",
162
+ production_branch: "main",
163
+ },
164
+ {
165
+ name: "cats",
166
+ subdomain: "cats.pages.dev",
167
+ domains: ["cats.pages.dev", "kitten.com"],
168
+ latest_deployment: {
169
+ modified_on: "2021-11-17T14:52:26.133835Z",
170
+ },
171
+ created_on: "2021-11-17T14:52:26.133835Z",
172
+ production_branch: "main",
173
+ },
174
+ ];
175
+
176
+ const requests = mockListRequest(projects);
177
+ await runWrangler("pages project list");
178
+
179
+ expect(requests.count).toBe(1);
180
+ });
181
+
182
+ it("should make multiple requests for paginated results", async () => {
183
+ const projects: Project[] = [];
184
+ for (let i = 0; i < 15; i++) {
185
+ projects.push({
186
+ name: "dogs" + i,
187
+ subdomain: i + "dogs.pages.dev",
188
+ domains: [i + "dogs.pages.dev"],
189
+ source: {
190
+ type: "github",
191
+ },
192
+ latest_deployment: {
193
+ modified_on: "2021-11-17T14:52:26.133835Z",
194
+ },
195
+ created_on: "2021-11-17T14:52:26.133835Z",
196
+ production_branch: "main",
197
+ });
198
+ }
199
+ const requests = mockListRequest(projects);
200
+ await runWrangler("pages project list");
201
+ expect(requests.count).toEqual(2);
202
+ });
203
+ });
204
+
205
+ describe("project create", () => {
206
+ mockAccountId();
207
+ mockApiToken();
208
+
209
+ afterEach(() => {
210
+ unsetAllMocks();
211
+ });
212
+
213
+ it("should create a project with a production branch", async () => {
214
+ setMockResponse(
215
+ "/accounts/:accountId/pages/projects",
216
+ ([_url, accountId], init) => {
217
+ const body = JSON.parse(init.body as string);
218
+ assertLater(() => {
219
+ expect(accountId).toEqual("some-account-id");
220
+ expect(init.method).toEqual("POST");
221
+ expect(body).toEqual({
222
+ name: "a-new-project",
223
+ production_branch: "main",
224
+ });
225
+ });
226
+ return {
227
+ name: "a-new-project",
228
+ subdomain: "a-new-project.pages.dev",
229
+ production_branch: "main",
230
+ };
231
+ }
232
+ );
233
+ await runWrangler(
234
+ "pages project create a-new-project --production-branch=main"
235
+ );
236
+ expect(std.out).toMatchInlineSnapshot(`
215
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.
216
238
  To deploy a folder of assets, run 'wrangler pages publish [directory]'."
217
239
  `);
218
- });
219
- });
220
-
221
- describe("deployment list", () => {
222
- mockAccountId();
223
- mockApiToken();
224
-
225
- afterEach(() => {
226
- unsetAllMocks();
227
- });
228
- function mockListRequest(deployments: unknown[]) {
229
- const requests = { count: 0 };
230
- setMockResponse(
231
- "/accounts/:accountId/pages/projects/:project/deployments",
232
- ([_url, accountId, project]) => {
233
- requests.count++;
234
- assertLater(() => {
235
- expect(project).toEqual("images");
236
- expect(accountId).toEqual("some-account-id");
237
- });
238
- return deployments;
239
- }
240
- );
241
- return requests;
242
- }
243
-
244
- it("should make request to list deployments", async () => {
245
- const deployments: Deployment[] = [
246
- {
247
- id: "87bbc8fe-16be-45cd-81e0-63d722e82cdf",
248
- url: "https://87bbc8fe.images.pages.dev",
249
- environment: "preview",
250
- latest_stage: {
251
- ended_on: "2021-11-17T14:52:26.133835Z",
252
- status: "success",
253
- },
254
- deployment_trigger: {
255
- metadata: {
256
- branch: "main",
257
- commit_hash: "c7649364c4cb32ad4f65b530b9424e8be5bec9d6",
258
- },
259
- },
260
- project_name: "images",
261
- },
262
- ];
263
-
264
- const requests = mockListRequest(deployments);
265
- await runWrangler("pages deployment list --project-name=images");
266
-
267
- expect(requests.count).toBe(1);
268
- });
269
- });
270
-
271
- describe("deployment create", () => {
272
- let actualProcessEnvCI: string | undefined;
273
-
274
- mockAccountId();
275
- mockApiToken();
276
- runInTempDir();
277
-
278
- beforeEach(() => {
279
- actualProcessEnvCI = process.env.CI;
280
- process.env.CI = "true";
281
- });
282
-
283
- afterEach(() => {
284
- unsetAllMocks();
285
- process.env.CI = actualProcessEnvCI;
286
- });
287
-
288
- it("should be aliased with 'wrangler pages publish'", async () => {
289
- await runWrangler("pages publish --help");
290
- await endEventLoop();
291
-
292
- expect(std.out).toMatchInlineSnapshot(`
240
+ });
241
+ });
242
+
243
+ describe("deployment list", () => {
244
+ mockAccountId();
245
+ mockApiToken();
246
+
247
+ afterEach(() => {
248
+ unsetAllMocks();
249
+ });
250
+ function mockListRequest(deployments: unknown[]) {
251
+ const requests = { count: 0 };
252
+ setMockResponse(
253
+ "/accounts/:accountId/pages/projects/:project/deployments",
254
+ ([_url, accountId, project]) => {
255
+ requests.count++;
256
+ assertLater(() => {
257
+ expect(project).toEqual("images");
258
+ expect(accountId).toEqual("some-account-id");
259
+ });
260
+ return deployments;
261
+ }
262
+ );
263
+ return requests;
264
+ }
265
+
266
+ it("should make request to list deployments", async () => {
267
+ const deployments: Deployment[] = [
268
+ {
269
+ id: "87bbc8fe-16be-45cd-81e0-63d722e82cdf",
270
+ url: "https://87bbc8fe.images.pages.dev",
271
+ environment: "preview",
272
+ latest_stage: {
273
+ ended_on: "2021-11-17T14:52:26.133835Z",
274
+ status: "success",
275
+ },
276
+ deployment_trigger: {
277
+ metadata: {
278
+ branch: "main",
279
+ commit_hash: "c7649364c4cb32ad4f65b530b9424e8be5bec9d6",
280
+ },
281
+ },
282
+ project_name: "images",
283
+ },
284
+ ];
285
+
286
+ const requests = mockListRequest(deployments);
287
+ await runWrangler("pages deployment list --project-name=images");
288
+
289
+ expect(requests.count).toBe(1);
290
+ });
291
+ });
292
+
293
+ describe("deployment create", () => {
294
+ let actualProcessEnvCI: string | undefined;
295
+
296
+ mockAccountId();
297
+ mockApiToken();
298
+ runInTempDir();
299
+
300
+ beforeEach(() => {
301
+ actualProcessEnvCI = process.env.CI;
302
+ process.env.CI = "true";
303
+ });
304
+
305
+ afterEach(() => {
306
+ unsetAllMocks();
307
+ process.env.CI = actualProcessEnvCI;
308
+ });
309
+
310
+ it("should be aliased with 'wrangler pages publish'", async () => {
311
+ await runWrangler("pages publish --help");
312
+ await endEventLoop();
313
+
314
+ expect(std.out).toMatchInlineSnapshot(`
293
315
  "wrangler pages publish [directory]
294
316
 
295
317
  🆙 Publish a directory of static assets as a Pages deployment
@@ -298,7 +320,6 @@ describe("pages", () => {
298
320
  directory The directory of static files to upload [string]
299
321
 
300
322
  Flags:
301
- -c, --config Path to .toml configuration file [string]
302
323
  -h, --help Show help [boolean]
303
324
  -v, --version Show version number [boolean]
304
325
 
@@ -311,325 +332,314 @@ describe("pages", () => {
311
332
 
312
333
  🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
313
334
  `);
314
- });
315
-
316
- it("should upload a directory of files", async () => {
317
- writeFileSync("logo.png", "foobar");
318
-
319
- setMockResponse(
320
- "/accounts/:accountId/pages/projects/foo/upload-token",
321
- async ([_url, accountId]) => {
322
- assertLater(() => {
323
- expect(accountId).toEqual("some-account-id");
324
- });
325
-
326
- return {
327
- jwt: "<<funfetti-auth-jwt>>",
328
- };
329
- }
330
- );
331
-
332
- setMockResponse(
333
- "/pages/assets/check-missing",
334
- "POST",
335
- async (_, init) => {
336
- const body = JSON.parse(init.body as string) as { hashes: string[] };
337
- assertLater(() => {
338
- expect(init.headers).toMatchObject({
339
- Authorization: "Bearer <<funfetti-auth-jwt>>",
340
- });
341
- expect(body).toMatchObject({
342
- hashes: ["2082190357cfd3617ccfe04f340c6247"],
343
- });
344
- });
345
- return body.hashes;
346
- }
347
- );
348
-
349
- setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
350
- assertLater(() => {
351
- expect(init.headers).toMatchObject({
352
- Authorization: "Bearer <<funfetti-auth-jwt>>",
353
- });
354
- const body = JSON.parse(init.body as string) as UploadPayloadFile[];
355
- expect(body).toMatchObject([
356
- {
357
- key: "2082190357cfd3617ccfe04f340c6247",
358
- value: Buffer.from("foobar").toString("base64"),
359
- metadata: {
360
- contentType: "image/png",
361
- },
362
- base64: true,
363
- },
364
- ]);
365
- });
366
- });
367
-
368
- setMockResponse(
369
- "/accounts/:accountId/pages/projects/foo/deployments",
370
- async ([_url, accountId], init) => {
371
- assertLater(() => {
372
- expect(accountId).toEqual("some-account-id");
373
- expect(init.method).toEqual("POST");
374
- const body = init.body as FormData;
375
- const manifest = JSON.parse(body.get("manifest") as string);
376
- expect(manifest).toMatchInlineSnapshot(`
335
+ });
336
+
337
+ it("should upload a directory of files", async () => {
338
+ writeFileSync("logo.png", "foobar");
339
+
340
+ mockGetToken("<<funfetti-auth-jwt>>");
341
+
342
+ setMockResponse(
343
+ "/pages/assets/check-missing",
344
+ "POST",
345
+ async (_, init) => {
346
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
347
+ assertLater(() => {
348
+ expect(init.headers).toMatchObject({
349
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
350
+ });
351
+ expect(body).toMatchObject({
352
+ hashes: ["2082190357cfd3617ccfe04f340c6247"],
353
+ });
354
+ });
355
+ return body.hashes;
356
+ }
357
+ );
358
+
359
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
360
+ assertLater(() => {
361
+ expect(init.headers).toMatchObject({
362
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
363
+ });
364
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
365
+ expect(body).toMatchObject([
366
+ {
367
+ key: "2082190357cfd3617ccfe04f340c6247",
368
+ value: Buffer.from("foobar").toString("base64"),
369
+ metadata: {
370
+ contentType: "image/png",
371
+ },
372
+ base64: true,
373
+ },
374
+ ]);
375
+ });
376
+ });
377
+
378
+ setMockResponse(
379
+ "/accounts/:accountId/pages/projects/foo/deployments",
380
+ async ([_url, accountId], init) => {
381
+ assertLater(() => {
382
+ expect(accountId).toEqual("some-account-id");
383
+ expect(init.method).toEqual("POST");
384
+ const body = init.body as FormData;
385
+ const manifest = JSON.parse(body.get("manifest") as string);
386
+ expect(manifest).toMatchInlineSnapshot(`
377
387
  Object {
378
388
  "/logo.png": "2082190357cfd3617ccfe04f340c6247",
379
389
  }
380
390
  `);
381
- });
382
-
383
- return {
384
- url: "https://abcxyz.foo.pages.dev/",
385
- };
386
- }
387
- );
388
-
389
- await runWrangler("pages publish . --project-name=foo");
390
-
391
- // TODO: Unmounting somehow loses this output
392
-
393
- // expect(std.out).toMatchInlineSnapshot(`
394
- // "✨ Success! Uploaded 1 files (TIMINGS)
395
-
396
- // ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
397
- // `);
398
- });
399
-
400
- it("should retry uploads", async () => {
401
- writeFileSync("logo.txt", "foobar");
402
-
403
- mockGetToken("<<funfetti-auth-jwt>>");
404
-
405
- setMockResponse(
406
- "/pages/assets/check-missing",
407
- "POST",
408
- async (_, init) => {
409
- const body = JSON.parse(init.body as string) as { hashes: string[] };
410
- assertLater(() => {
411
- expect(init.headers).toMatchObject({
412
- Authorization: "Bearer <<funfetti-auth-jwt>>",
413
- });
414
- expect(body).toMatchObject({
415
- hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
416
- });
417
- });
418
- return body.hashes;
419
- }
420
- );
421
-
422
- // Accumulate multiple requests then assert afterwards
423
- const requests: RequestInit[] = [];
424
- setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
425
- requests.push(init);
426
-
427
- if (requests.length < 2) {
428
- return createFetchResult(null, false, [
429
- {
430
- code: 800000,
431
- message: "Something exploded, please retry",
432
- },
433
- ]);
434
- } else {
435
- return createFetchResult(null, true);
436
- }
437
- });
438
-
439
- setMockResponse(
440
- "/accounts/:accountId/pages/projects/foo/deployments",
441
- async ([_url, accountId], init) => {
442
- assertLater(() => {
443
- expect(accountId).toEqual("some-account-id");
444
- expect(init.method).toEqual("POST");
445
- const body = init.body as FormData;
446
- const manifest = JSON.parse(body.get("manifest") as string);
447
- expect(manifest).toMatchInlineSnapshot(`
391
+ });
392
+
393
+ return {
394
+ url: "https://abcxyz.foo.pages.dev/",
395
+ };
396
+ }
397
+ );
398
+
399
+ await runWrangler("pages publish . --project-name=foo");
400
+
401
+ // TODO: Unmounting somehow loses this output
402
+
403
+ // expect(std.out).toMatchInlineSnapshot(`
404
+ // "✨ Success! Uploaded 1 files (TIMINGS)
405
+
406
+ // ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
407
+ // `);
408
+ });
409
+
410
+ it("should retry uploads", async () => {
411
+ writeFileSync("logo.txt", "foobar");
412
+
413
+ mockGetToken("<<funfetti-auth-jwt>>");
414
+
415
+ setMockResponse(
416
+ "/pages/assets/check-missing",
417
+ "POST",
418
+ async (_, init) => {
419
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
420
+ assertLater(() => {
421
+ expect(init.headers).toMatchObject({
422
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
423
+ });
424
+ expect(body).toMatchObject({
425
+ hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
426
+ });
427
+ });
428
+ return body.hashes;
429
+ }
430
+ );
431
+
432
+ // Accumulate multiple requests then assert afterwards
433
+ const requests: RequestInit[] = [];
434
+ setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
435
+ requests.push(init);
436
+
437
+ if (requests.length < 2) {
438
+ return createFetchResult(null, false, [
439
+ {
440
+ code: 800000,
441
+ message: "Something exploded, please retry",
442
+ },
443
+ ]);
444
+ } else {
445
+ return createFetchResult(null, true);
446
+ }
447
+ });
448
+
449
+ setMockResponse(
450
+ "/accounts/:accountId/pages/projects/foo/deployments",
451
+ async ([_url, accountId], init) => {
452
+ assertLater(() => {
453
+ expect(accountId).toEqual("some-account-id");
454
+ expect(init.method).toEqual("POST");
455
+ const body = init.body as FormData;
456
+ const manifest = JSON.parse(body.get("manifest") as string);
457
+ expect(manifest).toMatchInlineSnapshot(`
448
458
  Object {
449
459
  "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
450
460
  }
451
461
  `);
452
- });
453
-
454
- return {
455
- url: "https://abcxyz.foo.pages.dev/",
456
- };
457
- }
458
- );
459
-
460
- await runWrangler("pages publish . --project-name=foo");
461
-
462
- // Assert two identical requests
463
- expect(requests.length).toBe(2);
464
- for (const init of requests) {
465
- assertLater(() => {
466
- expect(init.headers).toMatchObject({
467
- Authorization: "Bearer <<funfetti-auth-jwt>>",
468
- });
469
-
470
- const body = JSON.parse(init.body as string) as UploadPayloadFile[];
471
- expect(body).toMatchObject([
472
- {
473
- key: "1a98fb08af91aca4a7df1764a2c4ddb0",
474
- value: Buffer.from("foobar").toString("base64"),
475
- metadata: {
476
- contentType: "text/plain",
477
- },
478
- base64: true,
479
- },
480
- ]);
481
- });
482
- }
483
-
484
- expect(std.out).toMatchInlineSnapshot(`
462
+ });
463
+
464
+ return {
465
+ url: "https://abcxyz.foo.pages.dev/",
466
+ };
467
+ }
468
+ );
469
+
470
+ await runWrangler("pages publish . --project-name=foo");
471
+
472
+ // Assert two identical requests
473
+ expect(requests.length).toBe(2);
474
+ for (const init of requests) {
475
+ assertLater(() => {
476
+ expect(init.headers).toMatchObject({
477
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
478
+ });
479
+
480
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
481
+ expect(body).toMatchObject([
482
+ {
483
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
484
+ value: Buffer.from("foobar").toString("base64"),
485
+ metadata: {
486
+ contentType: "text/plain",
487
+ },
488
+ base64: true,
489
+ },
490
+ ]);
491
+ });
492
+ }
493
+
494
+ expect(std.out).toMatchInlineSnapshot(`
485
495
  "✨ Success! Uploaded 1 files (TIMINGS)
486
496
 
487
497
  ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
488
498
  `);
489
- });
490
-
491
- it("should refetch a JWT if it expires while uploading", async () => {
492
- writeFileSync("logo.txt", "foobar");
493
-
494
- const cancelMockGetToken = mockGetToken("<<funfetti-auth-jwt>>");
495
-
496
- setMockResponse(
497
- "/pages/assets/check-missing",
498
- "POST",
499
- async (_, init) => {
500
- const body = JSON.parse(init.body as string) as { hashes: string[] };
501
- assertLater(() => {
502
- expect(init.headers).toMatchObject({
503
- Authorization: "Bearer <<funfetti-auth-jwt>>",
504
- });
505
- expect(body).toMatchObject({
506
- hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
507
- });
508
- });
509
- return body.hashes;
510
- }
511
- );
512
-
513
- // Accumulate multiple requests then assert afterwards
514
- const requests: RequestInit[] = [];
515
- setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
516
- requests.push(init);
517
-
518
- // Fail just the first request
519
- if (requests.length < 2) {
520
- cancelMockGetToken();
521
- mockGetToken("<<funfetti-auth-jwt2>>");
522
- return createFetchResult(null, false, [
523
- {
524
- code: 8000013,
525
- message: "Authorization failed",
526
- },
527
- ]);
528
- } else {
529
- return createFetchResult(null, true);
530
- }
531
- });
532
-
533
- setMockResponse(
534
- "/accounts/:accountId/pages/projects/foo/deployments",
535
- async ([_url, accountId], init) => {
536
- assertLater(() => {
537
- expect(accountId).toEqual("some-account-id");
538
- expect(init.method).toEqual("POST");
539
- const body = init.body as FormData;
540
- const manifest = JSON.parse(body.get("manifest") as string);
541
- expect(manifest).toMatchInlineSnapshot(`
499
+ });
500
+
501
+ it("should refetch a JWT if it expires while uploading", async () => {
502
+ writeFileSync("logo.txt", "foobar");
503
+
504
+ const cancelMockGetToken = mockGetToken("<<funfetti-auth-jwt>>");
505
+
506
+ setMockResponse(
507
+ "/pages/assets/check-missing",
508
+ "POST",
509
+ async (_, init) => {
510
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
511
+ assertLater(() => {
512
+ expect(init.headers).toMatchObject({
513
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
514
+ });
515
+ expect(body).toMatchObject({
516
+ hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
517
+ });
518
+ });
519
+ return body.hashes;
520
+ }
521
+ );
522
+
523
+ // Accumulate multiple requests then assert afterwards
524
+ const requests: RequestInit[] = [];
525
+ setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
526
+ requests.push(init);
527
+
528
+ // Fail just the first request
529
+ if (requests.length < 2) {
530
+ cancelMockGetToken();
531
+ mockGetToken("<<funfetti-auth-jwt2>>");
532
+ return createFetchResult(null, false, [
533
+ {
534
+ code: 8000013,
535
+ message: "Authorization failed",
536
+ },
537
+ ]);
538
+ } else {
539
+ return createFetchResult(null, true);
540
+ }
541
+ });
542
+
543
+ setMockResponse(
544
+ "/accounts/:accountId/pages/projects/foo/deployments",
545
+ async ([_url, accountId], init) => {
546
+ assertLater(() => {
547
+ expect(accountId).toEqual("some-account-id");
548
+ expect(init.method).toEqual("POST");
549
+ const body = init.body as FormData;
550
+ const manifest = JSON.parse(body.get("manifest") as string);
551
+ expect(manifest).toMatchInlineSnapshot(`
542
552
  Object {
543
553
  "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
544
554
  }
545
555
  `);
546
- });
547
-
548
- return {
549
- url: "https://abcxyz.foo.pages.dev/",
550
- };
551
- }
552
- );
553
-
554
- await runWrangler("pages publish . --project-name=foo");
555
-
556
- // Assert two requests
557
- expect(requests.length).toBe(2);
558
-
559
- expect(requests[0].headers).toMatchObject({
560
- Authorization: "Bearer <<funfetti-auth-jwt>>",
561
- });
562
-
563
- expect(requests[1].headers).toMatchObject({
564
- Authorization: "Bearer <<funfetti-auth-jwt2>>",
565
- });
566
-
567
- for (const init of requests) {
568
- const body = JSON.parse(init.body as string) as UploadPayloadFile[];
569
- expect(body).toMatchObject([
570
- {
571
- key: "1a98fb08af91aca4a7df1764a2c4ddb0",
572
- value: Buffer.from("foobar").toString("base64"),
573
- metadata: {
574
- contentType: "text/plain",
575
- },
576
- base64: true,
577
- },
578
- ]);
579
- }
580
-
581
- expect(std.out).toMatchInlineSnapshot(`
556
+ });
557
+
558
+ return {
559
+ url: "https://abcxyz.foo.pages.dev/",
560
+ };
561
+ }
562
+ );
563
+
564
+ await runWrangler("pages publish . --project-name=foo");
565
+
566
+ // Assert two requests
567
+ expect(requests.length).toBe(2);
568
+
569
+ expect(requests[0].headers).toMatchObject({
570
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
571
+ });
572
+
573
+ expect(requests[1].headers).toMatchObject({
574
+ Authorization: "Bearer <<funfetti-auth-jwt2>>",
575
+ });
576
+
577
+ for (const init of requests) {
578
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
579
+ expect(body).toMatchObject([
580
+ {
581
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
582
+ value: Buffer.from("foobar").toString("base64"),
583
+ metadata: {
584
+ contentType: "text/plain",
585
+ },
586
+ base64: true,
587
+ },
588
+ ]);
589
+ }
590
+
591
+ expect(std.out).toMatchInlineSnapshot(`
582
592
  "✨ Success! Uploaded 1 files (TIMINGS)
583
593
 
584
594
  ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
585
595
  `);
586
- });
587
-
588
- it("should try to use multiple buckets (up to the max concurrency)", async () => {
589
- writeFileSync("logo.txt", "foobar");
590
- writeFileSync("logo.png", "foobar");
591
- writeFileSync("logo.html", "foobar");
592
- writeFileSync("logo.js", "foobar");
593
-
594
- mockGetToken("<<funfetti-auth-jwt>>");
595
-
596
- setMockResponse(
597
- "/pages/assets/check-missing",
598
- "POST",
599
- async (_, init) => {
600
- const body = JSON.parse(init.body as string) as { hashes: string[] };
601
- assertLater(() => {
602
- expect(init.headers).toMatchObject({
603
- Authorization: "Bearer <<funfetti-auth-jwt>>",
604
- });
605
- expect(body).toMatchObject({
606
- hashes: expect.arrayContaining([
607
- "d96fef225537c9f5e44a3cb27fd0b492",
608
- "2082190357cfd3617ccfe04f340c6247",
609
- "6be321bef99e758250dac034474ddbb8",
610
- "1a98fb08af91aca4a7df1764a2c4ddb0",
611
- ]),
612
- });
613
- });
614
- return body.hashes;
615
- }
616
- );
617
-
618
- // Accumulate multiple requests then assert afterwards
619
- const requests: RequestInit[] = [];
620
- setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
621
- requests.push(init);
622
- });
623
-
624
- setMockResponse(
625
- "/accounts/:accountId/pages/projects/foo/deployments",
626
- async ([_url, accountId], init) => {
627
- assertLater(() => {
628
- expect(accountId).toEqual("some-account-id");
629
- expect(init.method).toEqual("POST");
630
- const body = init.body as FormData;
631
- const manifest = JSON.parse(body.get("manifest") as string);
632
- expect(manifest).toMatchInlineSnapshot(`
596
+ });
597
+
598
+ it("should try to use multiple buckets (up to the max concurrency)", async () => {
599
+ writeFileSync("logo.txt", "foobar");
600
+ writeFileSync("logo.png", "foobar");
601
+ writeFileSync("logo.html", "foobar");
602
+ writeFileSync("logo.js", "foobar");
603
+
604
+ mockGetToken("<<funfetti-auth-jwt>>");
605
+
606
+ setMockResponse(
607
+ "/pages/assets/check-missing",
608
+ "POST",
609
+ async (_, init) => {
610
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
611
+ assertLater(() => {
612
+ expect(init.headers).toMatchObject({
613
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
614
+ });
615
+ expect(body).toMatchObject({
616
+ hashes: expect.arrayContaining([
617
+ "d96fef225537c9f5e44a3cb27fd0b492",
618
+ "2082190357cfd3617ccfe04f340c6247",
619
+ "6be321bef99e758250dac034474ddbb8",
620
+ "1a98fb08af91aca4a7df1764a2c4ddb0",
621
+ ]),
622
+ });
623
+ });
624
+ return body.hashes;
625
+ }
626
+ );
627
+
628
+ // Accumulate multiple requests then assert afterwards
629
+ const requests: RequestInit[] = [];
630
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
631
+ requests.push(init);
632
+ });
633
+
634
+ setMockResponse(
635
+ "/accounts/:accountId/pages/projects/foo/deployments",
636
+ async ([_url, accountId], init) => {
637
+ assertLater(() => {
638
+ expect(accountId).toEqual("some-account-id");
639
+ expect(init.method).toEqual("POST");
640
+ const body = init.body as FormData;
641
+ const manifest = JSON.parse(body.get("manifest") as string);
642
+ expect(manifest).toMatchInlineSnapshot(`
633
643
  Object {
634
644
  "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
635
645
  "/logo.js": "6be321bef99e758250dac034474ddbb8",
@@ -637,118 +647,629 @@ describe("pages", () => {
637
647
  "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
638
648
  }
639
649
  `);
640
- });
641
-
642
- return {
643
- url: "https://abcxyz.foo.pages.dev/",
644
- };
645
- }
646
- );
647
-
648
- await runWrangler("pages publish . --project-name=foo");
649
-
650
- // We have 3 buckets, so expect 3 uploads
651
- expect(requests.length).toBe(3);
652
- const bodies: UploadPayloadFile[][] = [];
653
- for (const init of requests) {
654
- expect(init.headers).toMatchObject({
655
- Authorization: "Bearer <<funfetti-auth-jwt>>",
656
- });
657
- bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
658
- }
659
- // First bucket should end up with 2 files
660
- expect(bodies.map((b) => b.length)).toEqual([2, 1, 1]);
661
- // But we don't know the order, so flatten and test without ordering
662
- expect(bodies.flatMap((b) => b)).toEqual(
663
- expect.arrayContaining([
664
- {
665
- base64: true,
666
- key: "d96fef225537c9f5e44a3cb27fd0b492",
667
- metadata: { contentType: "text/html" },
668
- value: "Zm9vYmFy",
669
- },
670
- {
671
- base64: true,
672
- key: "1a98fb08af91aca4a7df1764a2c4ddb0",
673
- metadata: { contentType: "text/plain" },
674
- value: "Zm9vYmFy",
675
- },
676
- {
677
- base64: true,
678
- key: "6be321bef99e758250dac034474ddbb8",
679
- metadata: { contentType: "application/javascript" },
680
- value: "Zm9vYmFy",
681
- },
682
- {
683
- base64: true,
684
- key: "2082190357cfd3617ccfe04f340c6247",
685
- metadata: { contentType: "image/png" },
686
- value: "Zm9vYmFy",
687
- },
688
- ])
689
- );
690
-
691
- expect(std.out).toMatchInlineSnapshot(`
650
+ });
651
+
652
+ return {
653
+ url: "https://abcxyz.foo.pages.dev/",
654
+ };
655
+ }
656
+ );
657
+
658
+ await runWrangler("pages publish . --project-name=foo");
659
+
660
+ // We have 3 buckets, so expect 3 uploads
661
+ expect(requests.length).toBe(3);
662
+ const bodies: UploadPayloadFile[][] = [];
663
+ for (const init of requests) {
664
+ expect(init.headers).toMatchObject({
665
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
666
+ });
667
+ bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
668
+ }
669
+ // One bucket should end up with 2 files
670
+ expect(bodies.map((b) => b.length).sort()).toEqual([1, 1, 2]);
671
+ // But we don't know the order, so flatten and test without ordering
672
+ expect(bodies.flatMap((b) => b)).toEqual(
673
+ expect.arrayContaining([
674
+ {
675
+ base64: true,
676
+ key: "d96fef225537c9f5e44a3cb27fd0b492",
677
+ metadata: { contentType: "text/html" },
678
+ value: "Zm9vYmFy",
679
+ },
680
+ {
681
+ base64: true,
682
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
683
+ metadata: { contentType: "text/plain" },
684
+ value: "Zm9vYmFy",
685
+ },
686
+ {
687
+ base64: true,
688
+ key: "6be321bef99e758250dac034474ddbb8",
689
+ metadata: { contentType: "application/javascript" },
690
+ value: "Zm9vYmFy",
691
+ },
692
+ {
693
+ base64: true,
694
+ key: "2082190357cfd3617ccfe04f340c6247",
695
+ metadata: { contentType: "image/png" },
696
+ value: "Zm9vYmFy",
697
+ },
698
+ ])
699
+ );
700
+
701
+ expect(std.out).toMatchInlineSnapshot(`
692
702
  "✨ Success! Uploaded 4 files (TIMINGS)
693
703
 
694
704
  ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
695
705
  `);
696
- });
697
-
698
- it("should not error when directory names contain periods and houses a extensionless file", async () => {
699
- mkdirSync(".well-known");
700
- // Note: same content as previous test, but since it's a different extension,
701
- // it hashes to a different value
702
- writeFileSync(".well-known/foobar", "foobar");
703
-
704
- mockGetToken("<<funfetti-auth-jwt>>");
705
-
706
- setMockResponse(
707
- "/pages/assets/check-missing",
708
- "POST",
709
- async (_, init) => {
710
- const body = JSON.parse(init.body as string) as { hashes: string[] };
711
- assertLater(() => {
712
- expect(init.headers).toMatchObject({
713
- Authorization: "Bearer <<funfetti-auth-jwt>>",
714
- });
715
- expect(body).toMatchObject({
716
- hashes: ["7b764dacfd211bebd8077828a7ddefd7"],
717
- });
718
- });
719
- return body.hashes;
720
- }
721
- );
722
-
723
- setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
724
- assertLater(() => {
725
- expect(init.headers).toMatchObject({
726
- Authorization: "Bearer <<funfetti-auth-jwt>>",
727
- });
728
- const body = JSON.parse(init.body as string) as UploadPayloadFile[];
729
- expect(body).toMatchObject([
730
- {
731
- key: "7b764dacfd211bebd8077828a7ddefd7",
732
- value: Buffer.from("foobar").toString("base64"),
733
- metadata: {
734
- contentType: "application/octet-stream",
735
- },
736
- base64: true,
737
- },
738
- ]);
739
- });
740
- });
741
-
742
- setMockResponse(
743
- "/accounts/:accountId/pages/projects/foo/deployments",
744
- async () => ({
745
- url: "https://abcxyz.foo.pages.dev/",
746
- })
747
- );
748
-
749
- await runWrangler("pages publish . --project-name=foo");
750
-
751
- expect(std.err).toMatchInlineSnapshot(`""`);
752
- });
753
- });
706
+ });
707
+
708
+ it("should resolve child directories correctly", async () => {
709
+ mkdirSync("public");
710
+ mkdirSync("public/imgs");
711
+ writeFileSync("public/logo.txt", "foobar");
712
+ writeFileSync("public/imgs/logo.png", "foobar");
713
+ writeFileSync("public/logo.html", "foobar");
714
+ writeFileSync("public/logo.js", "foobar");
715
+
716
+ mockGetToken("<<funfetti-auth-jwt>>");
717
+
718
+ setMockResponse(
719
+ "/pages/assets/check-missing",
720
+ "POST",
721
+ async (_, init) => {
722
+ const body = JSON.parse(init.body as string) as {
723
+ hashes: string[];
724
+ };
725
+ assertLater(() => {
726
+ expect(init.headers).toMatchObject({
727
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
728
+ });
729
+ expect(body).toMatchObject({
730
+ hashes: expect.arrayContaining([
731
+ "d96fef225537c9f5e44a3cb27fd0b492",
732
+ "2082190357cfd3617ccfe04f340c6247",
733
+ "6be321bef99e758250dac034474ddbb8",
734
+ "1a98fb08af91aca4a7df1764a2c4ddb0",
735
+ ]),
736
+ });
737
+ });
738
+ return body.hashes;
739
+ }
740
+ );
741
+
742
+ // Accumulate multiple requests then assert afterwards
743
+ const requests: RequestInit[] = [];
744
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
745
+ requests.push(init);
746
+ });
747
+
748
+ setMockResponse(
749
+ "/accounts/:accountId/pages/projects/foo/deployments",
750
+ async ([_url, accountId], init) => {
751
+ assertLater(() => {
752
+ expect(accountId).toEqual("some-account-id");
753
+ expect(init.method).toEqual("POST");
754
+ const body = init.body as FormData;
755
+ const manifest = JSON.parse(body.get("manifest") as string);
756
+ expect(manifest).toMatchInlineSnapshot(`
757
+ Object {
758
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
759
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
760
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
761
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
762
+ }
763
+ `);
764
+ });
765
+
766
+ return {
767
+ url: "https://abcxyz.foo.pages.dev/",
768
+ };
769
+ }
770
+ );
771
+
772
+ await runWrangler(`pages publish public --project-name=foo`);
773
+
774
+ // We have 3 buckets, so expect 3 uploads
775
+ expect(requests.length).toBe(3);
776
+ const bodies: UploadPayloadFile[][] = [];
777
+ for (const init of requests) {
778
+ expect(init.headers).toMatchObject({
779
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
780
+ });
781
+ bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
782
+ }
783
+ // One bucket should end up with 2 files
784
+ expect(bodies.map((b) => b.length).sort()).toEqual([1, 1, 2]);
785
+ // But we don't know the order, so flatten and test without ordering
786
+ expect(bodies.flatMap((b) => b)).toEqual(
787
+ expect.arrayContaining([
788
+ {
789
+ base64: true,
790
+ key: "d96fef225537c9f5e44a3cb27fd0b492",
791
+ metadata: { contentType: "text/html" },
792
+ value: "Zm9vYmFy",
793
+ },
794
+ {
795
+ base64: true,
796
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
797
+ metadata: { contentType: "text/plain" },
798
+ value: "Zm9vYmFy",
799
+ },
800
+ {
801
+ base64: true,
802
+ key: "6be321bef99e758250dac034474ddbb8",
803
+ metadata: { contentType: "application/javascript" },
804
+ value: "Zm9vYmFy",
805
+ },
806
+ {
807
+ base64: true,
808
+ key: "2082190357cfd3617ccfe04f340c6247",
809
+ metadata: { contentType: "image/png" },
810
+ value: "Zm9vYmFy",
811
+ },
812
+ ])
813
+ );
814
+
815
+ expect(std.out).toMatchInlineSnapshot(`
816
+ "✨ Success! Uploaded 4 files (TIMINGS)
817
+
818
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
819
+ `);
820
+ });
821
+
822
+ it("should resolve the current directory correctly", async () => {
823
+ mkdirSync("public");
824
+ mkdirSync("public/imgs");
825
+ writeFileSync("public/logo.txt", "foobar");
826
+ writeFileSync("public/imgs/logo.png", "foobar");
827
+ writeFileSync("public/logo.html", "foobar");
828
+ writeFileSync("public/logo.js", "foobar");
829
+
830
+ mockGetToken("<<funfetti-auth-jwt>>");
831
+
832
+ setMockResponse(
833
+ "/pages/assets/check-missing",
834
+ "POST",
835
+ async (_, init) => {
836
+ const body = JSON.parse(init.body as string) as {
837
+ hashes: string[];
838
+ };
839
+ assertLater(() => {
840
+ expect(init.headers).toMatchObject({
841
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
842
+ });
843
+ expect(body).toMatchObject({
844
+ hashes: expect.arrayContaining([
845
+ "d96fef225537c9f5e44a3cb27fd0b492",
846
+ "2082190357cfd3617ccfe04f340c6247",
847
+ "6be321bef99e758250dac034474ddbb8",
848
+ "1a98fb08af91aca4a7df1764a2c4ddb0",
849
+ ]),
850
+ });
851
+ });
852
+ return body.hashes;
853
+ }
854
+ );
855
+
856
+ // Accumulate multiple requests then assert afterwards
857
+ const requests: RequestInit[] = [];
858
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
859
+ requests.push(init);
860
+ });
861
+
862
+ setMockResponse(
863
+ "/accounts/:accountId/pages/projects/foo/deployments",
864
+ async ([_url, accountId], init) => {
865
+ assertLater(() => {
866
+ expect(accountId).toEqual("some-account-id");
867
+ expect(init.method).toEqual("POST");
868
+ const body = init.body as FormData;
869
+ const manifest = JSON.parse(body.get("manifest") as string);
870
+ expect(manifest).toMatchInlineSnapshot(`
871
+ Object {
872
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
873
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
874
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
875
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
876
+ }
877
+ `);
878
+ });
879
+
880
+ return {
881
+ url: "https://abcxyz.foo.pages.dev/",
882
+ };
883
+ }
884
+ );
885
+
886
+ chdir("public");
887
+ await runWrangler(`pages publish . --project-name=foo`);
888
+
889
+ // We have 3 buckets, so expect 3 uploads
890
+ expect(requests.length).toBe(3);
891
+ const bodies: UploadPayloadFile[][] = [];
892
+ for (const init of requests) {
893
+ expect(init.headers).toMatchObject({
894
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
895
+ });
896
+ bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
897
+ }
898
+ // One bucket should end up with 2 files
899
+ expect(bodies.map((b) => b.length).sort()).toEqual([1, 1, 2]);
900
+ // But we don't know the order, so flatten and test without ordering
901
+ expect(bodies.flatMap((b) => b)).toEqual(
902
+ expect.arrayContaining([
903
+ {
904
+ base64: true,
905
+ key: "d96fef225537c9f5e44a3cb27fd0b492",
906
+ metadata: { contentType: "text/html" },
907
+ value: "Zm9vYmFy",
908
+ },
909
+ {
910
+ base64: true,
911
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
912
+ metadata: { contentType: "text/plain" },
913
+ value: "Zm9vYmFy",
914
+ },
915
+ {
916
+ base64: true,
917
+ key: "6be321bef99e758250dac034474ddbb8",
918
+ metadata: { contentType: "application/javascript" },
919
+ value: "Zm9vYmFy",
920
+ },
921
+ {
922
+ base64: true,
923
+ key: "2082190357cfd3617ccfe04f340c6247",
924
+ metadata: { contentType: "image/png" },
925
+ value: "Zm9vYmFy",
926
+ },
927
+ ])
928
+ );
929
+
930
+ expect(std.out).toMatchInlineSnapshot(`
931
+ "✨ Success! Uploaded 4 files (TIMINGS)
932
+
933
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
934
+ `);
935
+ });
936
+
937
+ it("should not error when directory names contain periods and houses a extensionless file", async () => {
938
+ mkdirSync(".well-known");
939
+ // Note: same content as previous test, but since it's a different extension,
940
+ // it hashes to a different value
941
+ writeFileSync(".well-known/foobar", "foobar");
942
+
943
+ mockGetToken("<<funfetti-auth-jwt>>");
944
+
945
+ setMockResponse(
946
+ "/pages/assets/check-missing",
947
+ "POST",
948
+ async (_, init) => {
949
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
950
+ assertLater(() => {
951
+ expect(init.headers).toMatchObject({
952
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
953
+ });
954
+ expect(body).toMatchObject({
955
+ hashes: ["7b764dacfd211bebd8077828a7ddefd7"],
956
+ });
957
+ });
958
+ return body.hashes;
959
+ }
960
+ );
961
+
962
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
963
+ assertLater(() => {
964
+ expect(init.headers).toMatchObject({
965
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
966
+ });
967
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
968
+ expect(body).toMatchObject([
969
+ {
970
+ key: "7b764dacfd211bebd8077828a7ddefd7",
971
+ value: Buffer.from("foobar").toString("base64"),
972
+ metadata: {
973
+ contentType: "application/octet-stream",
974
+ },
975
+ base64: true,
976
+ },
977
+ ]);
978
+ });
979
+ });
980
+
981
+ setMockResponse(
982
+ "/accounts/:accountId/pages/projects/foo/deployments",
983
+ async () => ({
984
+ url: "https://abcxyz.foo.pages.dev/",
985
+ })
986
+ );
987
+
988
+ await runWrangler("pages publish . --project-name=foo");
989
+
990
+ expect(std.err).toMatchInlineSnapshot(`""`);
991
+ });
992
+
993
+ it("should throw an error if user attempts to use config with pages", async () => {
994
+ await expect(
995
+ runWrangler("pages dev --config foo.toml")
996
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
997
+ `"Pages does not support wrangler.toml"`
998
+ );
999
+ await expect(
1000
+ runWrangler("pages publish --config foo.toml")
1001
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1002
+ `"Pages does not support wrangler.toml"`
1003
+ );
1004
+ });
1005
+ });
1006
+
1007
+ describe("project upload", () => {
1008
+ const ENV_COPY = process.env;
1009
+
1010
+ mockAccountId();
1011
+ mockApiToken();
1012
+ runInTempDir();
1013
+
1014
+ beforeEach(() => {
1015
+ process.env.CI = "true";
1016
+ process.env.CF_PAGES_UPLOAD_JWT = "<<funfetti-auth-jwt>>";
1017
+ });
1018
+
1019
+ afterEach(() => {
1020
+ unsetAllMocks();
1021
+ process.env = ENV_COPY;
1022
+ });
1023
+
1024
+ it("should upload a directory of files with a provided JWT", async () => {
1025
+ writeFileSync("logo.png", "foobar");
1026
+
1027
+ setMockResponse(
1028
+ "/pages/assets/check-missing",
1029
+ "POST",
1030
+ async (_, init) => {
1031
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1032
+ assertLater(() => {
1033
+ expect(init.headers).toMatchObject({
1034
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1035
+ });
1036
+ expect(body).toMatchObject({
1037
+ hashes: ["2082190357cfd3617ccfe04f340c6247"],
1038
+ });
1039
+ });
1040
+ return body.hashes;
1041
+ }
1042
+ );
1043
+
1044
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1045
+ assertLater(() => {
1046
+ expect(init.headers).toMatchObject({
1047
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1048
+ });
1049
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1050
+ expect(body).toMatchObject([
1051
+ {
1052
+ key: "2082190357cfd3617ccfe04f340c6247",
1053
+ value: Buffer.from("foobar").toString("base64"),
1054
+ metadata: {
1055
+ contentType: "image/png",
1056
+ },
1057
+ base64: true,
1058
+ },
1059
+ ]);
1060
+ });
1061
+ });
1062
+
1063
+ await runWrangler("pages project upload .");
1064
+
1065
+ expect(std.out).toMatchInlineSnapshot(`
1066
+ "✨ Success! Uploaded 1 files (TIMINGS)
1067
+
1068
+ ✨ Upload complete!"
1069
+ `);
1070
+ });
1071
+
1072
+ it("should retry uploads", async () => {
1073
+ writeFileSync("logo.txt", "foobar");
1074
+
1075
+ setMockResponse(
1076
+ "/pages/assets/check-missing",
1077
+ "POST",
1078
+ async (_, init) => {
1079
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1080
+ assertLater(() => {
1081
+ expect(init.headers).toMatchObject({
1082
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1083
+ });
1084
+ expect(body).toMatchObject({
1085
+ hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
1086
+ });
1087
+ });
1088
+ return body.hashes;
1089
+ }
1090
+ );
1091
+
1092
+ // Accumulate multiple requests then assert afterwards
1093
+ const requests: RequestInit[] = [];
1094
+ setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
1095
+ requests.push(init);
1096
+
1097
+ if (requests.length < 2) {
1098
+ return createFetchResult(null, false, [
1099
+ {
1100
+ code: 800000,
1101
+ message: "Something exploded, please retry",
1102
+ },
1103
+ ]);
1104
+ } else {
1105
+ return createFetchResult(null, true);
1106
+ }
1107
+ });
1108
+
1109
+ await runWrangler("pages project upload .");
1110
+
1111
+ // Assert two identical requests
1112
+ expect(requests.length).toBe(2);
1113
+ for (const init of requests) {
1114
+ assertLater(() => {
1115
+ expect(init.headers).toMatchObject({
1116
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1117
+ });
1118
+
1119
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1120
+ expect(body).toMatchObject([
1121
+ {
1122
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
1123
+ value: Buffer.from("foobar").toString("base64"),
1124
+ metadata: {
1125
+ contentType: "text/plain",
1126
+ },
1127
+ base64: true,
1128
+ },
1129
+ ]);
1130
+ });
1131
+ }
1132
+
1133
+ expect(std.out).toMatchInlineSnapshot(`
1134
+ "✨ Success! Uploaded 1 files (TIMINGS)
1135
+
1136
+ ✨ Upload complete!"
1137
+ `);
1138
+ });
1139
+
1140
+ it("should try to use multiple buckets (up to the max concurrency)", async () => {
1141
+ writeFileSync("logo.txt", "foobar");
1142
+ writeFileSync("logo.png", "foobar");
1143
+ writeFileSync("logo.html", "foobar");
1144
+ writeFileSync("logo.js", "foobar");
1145
+
1146
+ mockGetToken("<<funfetti-auth-jwt>>");
1147
+
1148
+ setMockResponse(
1149
+ "/pages/assets/check-missing",
1150
+ "POST",
1151
+ async (_, init) => {
1152
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1153
+ assertLater(() => {
1154
+ expect(init.headers).toMatchObject({
1155
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1156
+ });
1157
+ expect(body).toMatchObject({
1158
+ hashes: expect.arrayContaining([
1159
+ "d96fef225537c9f5e44a3cb27fd0b492",
1160
+ "2082190357cfd3617ccfe04f340c6247",
1161
+ "6be321bef99e758250dac034474ddbb8",
1162
+ "1a98fb08af91aca4a7df1764a2c4ddb0",
1163
+ ]),
1164
+ });
1165
+ });
1166
+ return body.hashes;
1167
+ }
1168
+ );
1169
+
1170
+ // Accumulate multiple requests then assert afterwards
1171
+ const requests: RequestInit[] = [];
1172
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1173
+ requests.push(init);
1174
+ });
1175
+
1176
+ await runWrangler("pages project upload .");
1177
+
1178
+ // We have 3 buckets, so expect 3 uploads
1179
+ expect(requests.length).toBe(3);
1180
+ const bodies: UploadPayloadFile[][] = [];
1181
+ for (const init of requests) {
1182
+ expect(init.headers).toMatchObject({
1183
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1184
+ });
1185
+ bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
1186
+ }
1187
+ // One bucket should end up with 2 files
1188
+ expect(bodies.map((b) => b.length).sort()).toEqual([1, 1, 2]);
1189
+ // But we don't know the order, so flatten and test without ordering
1190
+ expect(bodies.flatMap((b) => b)).toEqual(
1191
+ expect.arrayContaining([
1192
+ {
1193
+ base64: true,
1194
+ key: "d96fef225537c9f5e44a3cb27fd0b492",
1195
+ metadata: { contentType: "text/html" },
1196
+ value: "Zm9vYmFy",
1197
+ },
1198
+ {
1199
+ base64: true,
1200
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
1201
+ metadata: { contentType: "text/plain" },
1202
+ value: "Zm9vYmFy",
1203
+ },
1204
+ {
1205
+ base64: true,
1206
+ key: "6be321bef99e758250dac034474ddbb8",
1207
+ metadata: { contentType: "application/javascript" },
1208
+ value: "Zm9vYmFy",
1209
+ },
1210
+ {
1211
+ base64: true,
1212
+ key: "2082190357cfd3617ccfe04f340c6247",
1213
+ metadata: { contentType: "image/png" },
1214
+ value: "Zm9vYmFy",
1215
+ },
1216
+ ])
1217
+ );
1218
+
1219
+ expect(std.out).toMatchInlineSnapshot(`
1220
+ "✨ Success! Uploaded 4 files (TIMINGS)
1221
+
1222
+ ✨ Upload complete!"
1223
+ `);
1224
+ });
1225
+
1226
+ it("should not error when directory names contain periods and houses a extensionless file", async () => {
1227
+ mkdirSync(".well-known");
1228
+ // Note: same content as previous test, but since it's a different extension,
1229
+ // it hashes to a different value
1230
+ writeFileSync(".well-known/foobar", "foobar");
1231
+
1232
+ mockGetToken("<<funfetti-auth-jwt>>");
1233
+
1234
+ setMockResponse(
1235
+ "/pages/assets/check-missing",
1236
+ "POST",
1237
+ async (_, init) => {
1238
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1239
+ assertLater(() => {
1240
+ expect(init.headers).toMatchObject({
1241
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1242
+ });
1243
+ expect(body).toMatchObject({
1244
+ hashes: ["7b764dacfd211bebd8077828a7ddefd7"],
1245
+ });
1246
+ });
1247
+ return body.hashes;
1248
+ }
1249
+ );
1250
+
1251
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1252
+ assertLater(() => {
1253
+ expect(init.headers).toMatchObject({
1254
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1255
+ });
1256
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1257
+ expect(body).toMatchObject([
1258
+ {
1259
+ key: "7b764dacfd211bebd8077828a7ddefd7",
1260
+ value: Buffer.from("foobar").toString("base64"),
1261
+ metadata: {
1262
+ contentType: "application/octet-stream",
1263
+ },
1264
+ base64: true,
1265
+ },
1266
+ ]);
1267
+ });
1268
+ });
1269
+
1270
+ await runWrangler("pages project upload .");
1271
+
1272
+ expect(std.err).toMatchInlineSnapshot(`""`);
1273
+ });
1274
+ });
754
1275
  });