wrangler 0.0.0-e178d6fb → 0.0.0-e1c2f5b9

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 (239) hide show
  1. package/README.md +16 -2
  2. package/bin/wrangler.js +31 -11
  3. package/miniflare-dist/index.mjs +1340 -330
  4. package/package.json +40 -13
  5. package/src/__tests__/access.test.ts +25 -0
  6. package/src/__tests__/api-dev.test.ts +238 -0
  7. package/src/__tests__/api-devregistry.test.js +64 -0
  8. package/src/__tests__/api.test.ts +80 -0
  9. package/src/__tests__/configuration.test.ts +683 -470
  10. package/src/__tests__/d1.test.ts +123 -0
  11. package/src/__tests__/delete.test.ts +263 -0
  12. package/src/__tests__/deployments.test.ts +105 -0
  13. package/src/__tests__/dev.test.tsx +453 -70
  14. package/src/__tests__/dialogs.test.tsx +40 -0
  15. package/src/__tests__/generate.test.ts +235 -0
  16. package/src/__tests__/get-host-from-url.test.ts +16 -0
  17. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  18. package/src/__tests__/helpers/mock-cfetch.ts +92 -26
  19. package/src/__tests__/helpers/mock-console.ts +11 -2
  20. package/src/__tests__/helpers/mock-dialogs.ts +1 -1
  21. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +1 -1
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
  25. package/src/__tests__/helpers/mock-process.ts +8 -13
  26. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  27. package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
  28. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  29. package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
  30. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  31. package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
  32. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  33. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  34. package/src/__tests__/helpers/msw/index.ts +21 -0
  35. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  36. package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
  37. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  38. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  39. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  40. package/src/__tests__/index.test.ts +103 -97
  41. package/src/__tests__/init.test.ts +1484 -382
  42. package/src/__tests__/jest.setup.ts +88 -5
  43. package/src/__tests__/kv.test.ts +655 -540
  44. package/src/__tests__/logout.test.ts +47 -0
  45. package/src/__tests__/metrics.test.ts +95 -45
  46. package/src/__tests__/middleware.scheduled.test.ts +145 -0
  47. package/src/__tests__/middleware.test.ts +750 -0
  48. package/src/__tests__/pages-deployment-tail.test.ts +884 -0
  49. package/src/__tests__/pages.test.ts +1301 -119
  50. package/src/__tests__/paths.test.ts +39 -0
  51. package/src/__tests__/publish.test.ts +2266 -1180
  52. package/src/__tests__/pubsub.test.ts +211 -88
  53. package/src/__tests__/queues.test.ts +522 -0
  54. package/src/__tests__/r2.test.ts +363 -203
  55. package/src/__tests__/secret.test.ts +261 -59
  56. package/src/__tests__/tail.test.ts +352 -151
  57. package/src/__tests__/test-old-node-version.js +3 -3
  58. package/src/__tests__/tsconfig-sanity.ts +12 -0
  59. package/src/__tests__/tsconfig.json +8 -0
  60. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  61. package/src/__tests__/type-generation.test.ts +233 -0
  62. package/src/__tests__/user.test.ts +59 -73
  63. package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
  64. package/src/__tests__/validate-dev-props.test.ts +56 -0
  65. package/src/__tests__/whoami.test.tsx +114 -66
  66. package/src/__tests__/worker-namespace.test.ts +154 -155
  67. package/src/api/dev.ts +170 -38
  68. package/src/api/index.ts +1 -0
  69. package/src/bundle-reporter.tsx +41 -2
  70. package/src/bundle.ts +668 -81
  71. package/src/cfetch/index.ts +40 -3
  72. package/src/cfetch/internal.ts +144 -32
  73. package/src/cli.ts +4 -3
  74. package/src/config/config.ts +27 -3
  75. package/src/config/environment.ts +131 -7
  76. package/src/config/index.ts +128 -8
  77. package/src/config/validation-helpers.ts +19 -6
  78. package/src/config/validation.ts +411 -14
  79. package/src/config-cache.ts +3 -2
  80. package/src/create-worker-preview.ts +31 -6
  81. package/src/create-worker-upload-form.ts +84 -22
  82. package/src/d1/backups.tsx +223 -0
  83. package/src/d1/constants.ts +2 -0
  84. package/src/d1/create.tsx +63 -0
  85. package/src/d1/delete.tsx +56 -0
  86. package/src/d1/execute.tsx +352 -0
  87. package/src/d1/formatTimeAgo.ts +14 -0
  88. package/src/d1/index.ts +101 -0
  89. package/src/d1/list.tsx +48 -0
  90. package/src/d1/migrations/apply.tsx +196 -0
  91. package/src/d1/migrations/create.tsx +77 -0
  92. package/src/d1/migrations/helpers.ts +147 -0
  93. package/src/d1/migrations/index.tsx +3 -0
  94. package/src/d1/migrations/list.tsx +79 -0
  95. package/src/d1/migrations/options.ts +18 -0
  96. package/src/d1/options.ts +22 -0
  97. package/src/d1/types.tsx +24 -0
  98. package/src/d1/utils.ts +48 -0
  99. package/src/delete.ts +108 -0
  100. package/src/deployments.ts +83 -0
  101. package/src/deprecated/index.ts +145 -0
  102. package/src/dev/dev-vars.ts +11 -8
  103. package/src/dev/dev.tsx +220 -58
  104. package/src/dev/get-local-persistence-path.tsx +31 -0
  105. package/src/dev/local.tsx +740 -181
  106. package/src/dev/remote.tsx +340 -123
  107. package/src/dev/start-server.ts +543 -0
  108. package/src/dev/use-esbuild.ts +61 -7
  109. package/src/dev/validate-dev-props.ts +31 -0
  110. package/src/dev-registry.tsx +201 -0
  111. package/src/dev.tsx +614 -258
  112. package/src/dialogs.tsx +41 -8
  113. package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
  114. package/src/docs/index.ts +97 -0
  115. package/src/entry.ts +3 -2
  116. package/src/environment-variables/factory.ts +88 -0
  117. package/src/environment-variables/misc-variables.ts +30 -0
  118. package/src/generate/index.ts +300 -0
  119. package/src/git-client.ts +100 -7
  120. package/src/index.tsx +249 -1560
  121. package/src/init.ts +626 -150
  122. package/src/inspect.ts +137 -32
  123. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  124. package/src/is-interactive.ts +4 -0
  125. package/src/jest.d.ts +4 -0
  126. package/src/{kv.ts → kv/helpers.ts} +3 -3
  127. package/src/kv/index.ts +596 -0
  128. package/src/logger.ts +27 -8
  129. package/src/metrics/index.ts +1 -0
  130. package/src/metrics/metrics-config.ts +4 -4
  131. package/src/metrics/metrics-dispatcher.ts +1 -0
  132. package/src/metrics/metrics-usage-headers.ts +24 -0
  133. package/src/metrics/send-event.ts +13 -8
  134. package/src/miniflare-cli/assets.ts +172 -465
  135. package/src/miniflare-cli/index.ts +149 -24
  136. package/src/miniflare-cli/tsconfig.json +9 -0
  137. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  138. package/src/miniflare-cli/types.ts +11 -0
  139. package/src/module-collection.ts +12 -3
  140. package/src/pages/build.tsx +108 -42
  141. package/src/pages/constants.ts +11 -0
  142. package/src/pages/deployment-tails.tsx +284 -0
  143. package/src/pages/deployments.tsx +9 -31
  144. package/src/pages/dev.tsx +417 -108
  145. package/src/pages/errors.ts +66 -0
  146. package/src/pages/functions/buildPlugin.ts +86 -84
  147. package/src/pages/functions/buildWorker.ts +121 -106
  148. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  149. package/src/pages/functions/routes-consolidation.ts +73 -0
  150. package/src/pages/functions/routes-transformation.test.ts +282 -0
  151. package/src/pages/functions/routes-transformation.ts +115 -0
  152. package/src/pages/functions/routes-validation.test.ts +403 -0
  153. package/src/pages/functions/routes-validation.ts +202 -0
  154. package/src/pages/functions/tsconfig.json +8 -0
  155. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  156. package/src/pages/functions.tsx +82 -0
  157. package/src/pages/hash.tsx +13 -0
  158. package/src/pages/index.tsx +73 -55
  159. package/src/pages/projects.tsx +9 -3
  160. package/src/pages/prompt-select-project.tsx +31 -0
  161. package/src/pages/publish.tsx +183 -60
  162. package/src/pages/types.ts +1 -0
  163. package/src/pages/upload.tsx +107 -56
  164. package/src/pages/utils.ts +13 -2
  165. package/src/paths.ts +49 -4
  166. package/src/proxy.ts +375 -157
  167. package/src/publish/index.ts +270 -0
  168. package/src/{publish.ts → publish/publish.ts} +431 -188
  169. package/src/pubsub/pubsub-commands.tsx +4 -3
  170. package/src/queues/cli/commands/consumer/add.ts +71 -0
  171. package/src/queues/cli/commands/consumer/index.ts +22 -0
  172. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  173. package/src/queues/cli/commands/create.ts +25 -0
  174. package/src/queues/cli/commands/delete.ts +26 -0
  175. package/src/queues/cli/commands/index.ts +36 -0
  176. package/src/queues/cli/commands/list.ts +25 -0
  177. package/src/queues/client.ts +135 -0
  178. package/src/queues/utils.ts +18 -0
  179. package/src/r2/constants.ts +4 -0
  180. package/src/r2/helpers.ts +131 -0
  181. package/src/r2/index.ts +289 -0
  182. package/src/routes.ts +140 -0
  183. package/src/secret/index.ts +379 -0
  184. package/src/sites.tsx +3 -3
  185. package/src/tail/createTail.ts +379 -0
  186. package/src/tail/filters.ts +4 -6
  187. package/src/tail/index.ts +199 -284
  188. package/src/tail/printing.ts +43 -3
  189. package/src/tsconfig-sanity.ts +16 -0
  190. package/src/type-generation.ts +181 -0
  191. package/src/user/access.ts +68 -0
  192. package/src/user/auth-variables.ts +113 -0
  193. package/src/user/choose-account.tsx +1 -1
  194. package/src/user/index.ts +0 -1
  195. package/src/user/user.tsx +116 -49
  196. package/src/utils/collectKeyValues.ts +14 -0
  197. package/src/whoami.tsx +80 -2
  198. package/src/worker.ts +69 -9
  199. package/src/yargs-types.ts +24 -0
  200. package/src/zones.ts +124 -9
  201. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  202. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  203. package/templates/__tests__/tsconfig.json +8 -0
  204. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  205. package/templates/d1-beta-facade.js +205 -0
  206. package/templates/facade.d.ts +14 -0
  207. package/templates/first-party-worker-module-facade.ts +19 -0
  208. package/templates/format-dev-errors.ts +33 -0
  209. package/templates/init-tests/test-jest-new-worker.js +23 -0
  210. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  211. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  212. package/templates/middleware/common.ts +62 -0
  213. package/templates/middleware/loader-modules.ts +82 -0
  214. package/templates/middleware/loader-sw.ts +233 -0
  215. package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
  216. package/templates/middleware/middleware-pretty-error.ts +40 -0
  217. package/templates/middleware/middleware-scheduled.ts +15 -0
  218. package/templates/new-worker.js +1 -1
  219. package/templates/pages-dev-pipeline.ts +31 -0
  220. package/templates/pages-dev-util.ts +52 -0
  221. package/templates/pages-shim.ts +4 -1
  222. package/templates/pages-template-plugin.ts +21 -5
  223. package/templates/pages-template-worker.ts +37 -9
  224. package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
  225. package/templates/service-bindings-module-facade.js +54 -0
  226. package/templates/service-bindings-sw-facade.js +42 -0
  227. package/templates/tsconfig-sanity.ts +11 -0
  228. package/templates/tsconfig.init.json +106 -0
  229. package/templates/tsconfig.json +5 -102
  230. package/templates/tsconfig.tsbuildinfo +1 -0
  231. package/wrangler-dist/cli.d.ts +621 -42
  232. package/wrangler-dist/cli.js +144441 -102606
  233. package/src/environment-variables.ts +0 -35
  234. package/src/faye-websocket.d.ts +0 -6
  235. package/src/generate.ts +0 -33
  236. package/src/miniflare-cli/enum-keys.ts +0 -17
  237. package/src/preview.ts +0 -31
  238. package/src/r2.ts +0 -50
  239. package/src/user/env-vars.ts +0 -46
@@ -1,5 +1,8 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { chdir } from "node:process";
3
+ import { ROUTES_SPEC_VERSION } from "../pages/constants";
4
+ import { isRoutesJSONSpec } from "../pages/functions/routes-validation";
5
+ import { version } from "./../../package.json";
3
6
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
4
7
  import {
5
8
  createFetchResult,
@@ -57,23 +60,24 @@ describe("pages", () => {
57
60
  await endEventLoop();
58
61
 
59
62
  expect(std.out).toMatchInlineSnapshot(`
60
- "wrangler pages
63
+ "wrangler pages
61
64
 
62
- ⚡️ Configure Cloudflare Pages
65
+ ⚡️ Configure Cloudflare Pages
63
66
 
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
67
+ Commands:
68
+ wrangler pages dev [directory] [-- command..] 🧑‍💻 Develop your full-stack Pages application locally
69
+ wrangler pages project ⚡️ Interact with your Pages projects
70
+ wrangler pages deployment 🚀 Interact with the deployments of a project
71
+ wrangler pages publish [directory] 🆙 Publish a directory of static assets as a Pages deployment
69
72
 
70
- Flags:
71
- -c, --config Path to .toml configuration file [string]
72
- -h, --help Show help [boolean]
73
- -v, --version Show version number [boolean]
73
+ Flags:
74
+ -c, --config Path to .toml configuration file [string]
75
+ -e, --env Environment to use for operations and .env files [string]
76
+ -h, --help Show help [boolean]
77
+ -v, --version Show version number [boolean]
74
78
 
75
- 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
76
- `);
79
+ 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
80
+ `);
77
81
  });
78
82
 
79
83
  describe("beta message for subcommands", () => {
@@ -81,24 +85,38 @@ describe("pages", () => {
81
85
  await expect(
82
86
  runWrangler("pages dev")
83
87
  ).rejects.toThrowErrorMatchingInlineSnapshot(
84
- `"Must specify a directory of static assets to serve or a command to run."`
88
+ `"Must specify a directory of static assets to serve or a command to run or a proxy port."`
85
89
  );
86
90
 
87
91
  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
92
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
89
93
 
90
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
91
- `);
94
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
95
+ `);
92
96
  });
93
97
 
94
98
  it("should display for pages:functions:build", async () => {
95
99
  await expect(runWrangler("pages functions build")).rejects.toThrowError();
96
100
 
97
101
  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
102
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
99
103
 
100
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
101
- `);
104
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
105
+ `);
106
+ });
107
+
108
+ it("should display for pages:functions:optimize-routes", async () => {
109
+ await expect(
110
+ runWrangler(
111
+ 'pages functions optimize-routes --routes-path="/build/_routes.json" --output-routes-path="/build/_optimized-routes.json"'
112
+ )
113
+ ).rejects.toThrowError();
114
+
115
+ expect(std.out).toMatchInlineSnapshot(`
116
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
117
+
118
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
119
+ `);
102
120
  });
103
121
  });
104
122
 
@@ -220,9 +238,9 @@ describe("pages", () => {
220
238
  "pages project create a-new-project --production-branch=main"
221
239
  );
222
240
  expect(std.out).toMatchInlineSnapshot(`
223
- "✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
224
- To deploy a folder of assets, run 'wrangler pages publish [directory]'."
225
- `);
241
+ "✨ Successfully created the 'a-new-project' project. It will be available at https://a-new-project.pages.dev/ once you create your first deployment.
242
+ To deploy a folder of assets, run 'wrangler pages publish [directory]'."
243
+ `);
226
244
  });
227
245
  });
228
246
 
@@ -255,6 +273,7 @@ describe("pages", () => {
255
273
  id: "87bbc8fe-16be-45cd-81e0-63d722e82cdf",
256
274
  url: "https://87bbc8fe.images.pages.dev",
257
275
  environment: "preview",
276
+ created_on: "2021-11-17T14:52:26.133835Z",
258
277
  latest_stage: {
259
278
  ended_on: "2021-11-17T14:52:26.133835Z",
260
279
  status: "success",
@@ -298,26 +317,28 @@ describe("pages", () => {
298
317
  await endEventLoop();
299
318
 
300
319
  expect(std.out).toMatchInlineSnapshot(`
301
- "wrangler pages publish [directory]
320
+ "wrangler pages publish [directory]
302
321
 
303
- 🆙 Publish a directory of static assets as a Pages deployment
322
+ 🆙 Publish a directory of static assets as a Pages deployment
304
323
 
305
- Positionals:
306
- directory The directory of static files to upload [string]
324
+ Positionals:
325
+ directory The directory of static files to upload [string]
307
326
 
308
- Flags:
309
- -h, --help Show help [boolean]
310
- -v, --version Show version number [boolean]
327
+ Flags:
328
+ -e, --env Environment to use for operations and .env files [string]
329
+ -h, --help Show help [boolean]
330
+ -v, --version Show version number [boolean]
311
331
 
312
- Options:
313
- --project-name The name of the project you want to deploy to [string]
314
- --branch The name of the branch you want to deploy to [string]
315
- --commit-hash The SHA to attach to this deployment [string]
316
- --commit-message The commit message to attach to this deployment [string]
317
- --commit-dirty Whether or not the workspace should be considered dirty for this deployment [boolean]
332
+ Options:
333
+ --project-name The name of the project you want to deploy to [string]
334
+ --branch The name of the branch you want to deploy to [string]
335
+ --commit-hash The SHA to attach to this deployment [string]
336
+ --commit-message The commit message to attach to this deployment [string]
337
+ --commit-dirty Whether or not the workspace should be considered dirty for this deployment [boolean]
338
+ --skip-caching Skip asset caching which speeds up builds [boolean]
318
339
 
319
- 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
320
- `);
340
+ 🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
341
+ `);
321
342
  });
322
343
 
323
344
  it("should upload a directory of files", async () => {
@@ -370,10 +391,10 @@ describe("pages", () => {
370
391
  const body = init.body as FormData;
371
392
  const manifest = JSON.parse(body.get("manifest") as string);
372
393
  expect(manifest).toMatchInlineSnapshot(`
373
- Object {
374
- "/logo.png": "2082190357cfd3617ccfe04f340c6247",
375
- }
376
- `);
394
+ Object {
395
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
396
+ }
397
+ `);
377
398
  });
378
399
 
379
400
  return {
@@ -382,15 +403,24 @@ describe("pages", () => {
382
403
  }
383
404
  );
384
405
 
385
- await runWrangler("pages publish . --project-name=foo");
406
+ setMockResponse(
407
+ "/accounts/:accountId/pages/projects/foo",
408
+ "GET",
409
+ async ([_url, accountId]) => {
410
+ assertLater(() => {
411
+ expect(accountId).toEqual("some-account-id");
412
+ });
413
+ return { deployment_configs: { production: {}, preview: {} } };
414
+ }
415
+ );
386
416
 
387
- // TODO: Unmounting somehow loses this output
417
+ await runWrangler("pages publish . --project-name=foo");
388
418
 
389
- // expect(std.out).toMatchInlineSnapshot(`
390
- // "✨ Success! Uploaded 1 files (TIMINGS)
419
+ expect(std.out).toMatchInlineSnapshot(`
420
+ "✨ Success! Uploaded 1 files (TIMINGS)
391
421
 
392
- // ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
393
- // `);
422
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
423
+ `);
394
424
  });
395
425
 
396
426
  it("should retry uploads", async () => {
@@ -441,10 +471,10 @@ describe("pages", () => {
441
471
  const body = init.body as FormData;
442
472
  const manifest = JSON.parse(body.get("manifest") as string);
443
473
  expect(manifest).toMatchInlineSnapshot(`
444
- Object {
445
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
446
- }
447
- `);
474
+ Object {
475
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
476
+ }
477
+ `);
448
478
  });
449
479
 
450
480
  return {
@@ -453,6 +483,17 @@ describe("pages", () => {
453
483
  }
454
484
  );
455
485
 
486
+ setMockResponse(
487
+ "/accounts/:accountId/pages/projects/foo",
488
+ "GET",
489
+ async ([_url, accountId]) => {
490
+ assertLater(() => {
491
+ expect(accountId).toEqual("some-account-id");
492
+ });
493
+ return { deployment_configs: { production: {}, preview: {} } };
494
+ }
495
+ );
496
+
456
497
  await runWrangler("pages publish . --project-name=foo");
457
498
 
458
499
  // Assert two identical requests
@@ -478,10 +519,10 @@ describe("pages", () => {
478
519
  }
479
520
 
480
521
  expect(std.out).toMatchInlineSnapshot(`
481
- "✨ Success! Uploaded 1 files (TIMINGS)
522
+ "✨ Success! Uploaded 1 files (TIMINGS)
482
523
 
483
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
484
- `);
524
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
525
+ `);
485
526
  });
486
527
 
487
528
  it("should refetch a JWT if it expires while uploading", async () => {
@@ -535,10 +576,10 @@ describe("pages", () => {
535
576
  const body = init.body as FormData;
536
577
  const manifest = JSON.parse(body.get("manifest") as string);
537
578
  expect(manifest).toMatchInlineSnapshot(`
538
- Object {
539
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
540
- }
541
- `);
579
+ Object {
580
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
581
+ }
582
+ `);
542
583
  });
543
584
 
544
585
  return {
@@ -547,6 +588,17 @@ describe("pages", () => {
547
588
  }
548
589
  );
549
590
 
591
+ setMockResponse(
592
+ "/accounts/:accountId/pages/projects/foo",
593
+ "GET",
594
+ async ([_url, accountId]) => {
595
+ assertLater(() => {
596
+ expect(accountId).toEqual("some-account-id");
597
+ });
598
+ return { deployment_configs: { production: {}, preview: {} } };
599
+ }
600
+ );
601
+
550
602
  await runWrangler("pages publish . --project-name=foo");
551
603
 
552
604
  // Assert two requests
@@ -575,10 +627,10 @@ describe("pages", () => {
575
627
  }
576
628
 
577
629
  expect(std.out).toMatchInlineSnapshot(`
578
- "✨ Success! Uploaded 1 files (TIMINGS)
630
+ "✨ Success! Uploaded 1 files (TIMINGS)
579
631
 
580
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
581
- `);
632
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
633
+ `);
582
634
  });
583
635
 
584
636
  it("should try to use multiple buckets (up to the max concurrency)", async () => {
@@ -626,13 +678,13 @@ describe("pages", () => {
626
678
  const body = init.body as FormData;
627
679
  const manifest = JSON.parse(body.get("manifest") as string);
628
680
  expect(manifest).toMatchInlineSnapshot(`
629
- Object {
630
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
631
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
632
- "/logo.png": "2082190357cfd3617ccfe04f340c6247",
633
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
634
- }
635
- `);
681
+ Object {
682
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
683
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
684
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
685
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
686
+ }
687
+ `);
636
688
  });
637
689
 
638
690
  return {
@@ -641,6 +693,17 @@ describe("pages", () => {
641
693
  }
642
694
  );
643
695
 
696
+ setMockResponse(
697
+ "/accounts/:accountId/pages/projects/foo",
698
+ "GET",
699
+ async ([_url, accountId]) => {
700
+ assertLater(() => {
701
+ expect(accountId).toEqual("some-account-id");
702
+ });
703
+ return { deployment_configs: { production: {}, preview: {} } };
704
+ }
705
+ );
706
+
644
707
  await runWrangler("pages publish . --project-name=foo");
645
708
 
646
709
  // We have 3 buckets, so expect 3 uploads
@@ -685,10 +748,10 @@ describe("pages", () => {
685
748
  );
686
749
 
687
750
  expect(std.out).toMatchInlineSnapshot(`
688
- "✨ Success! Uploaded 4 files (TIMINGS)
751
+ "✨ Success! Uploaded 4 files (TIMINGS)
689
752
 
690
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
691
- `);
753
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
754
+ `);
692
755
  });
693
756
 
694
757
  it("should resolve child directories correctly", async () => {
@@ -740,13 +803,13 @@ describe("pages", () => {
740
803
  const body = init.body as FormData;
741
804
  const manifest = JSON.parse(body.get("manifest") as string);
742
805
  expect(manifest).toMatchInlineSnapshot(`
743
- Object {
744
- "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
745
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
746
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
747
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
748
- }
749
- `);
806
+ Object {
807
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
808
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
809
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
810
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
811
+ }
812
+ `);
750
813
  });
751
814
 
752
815
  return {
@@ -755,6 +818,17 @@ describe("pages", () => {
755
818
  }
756
819
  );
757
820
 
821
+ setMockResponse(
822
+ "/accounts/:accountId/pages/projects/foo",
823
+ "GET",
824
+ async ([_url, accountId]) => {
825
+ assertLater(() => {
826
+ expect(accountId).toEqual("some-account-id");
827
+ });
828
+ return { deployment_configs: { production: {}, preview: {} } };
829
+ }
830
+ );
831
+
758
832
  await runWrangler(`pages publish public --project-name=foo`);
759
833
 
760
834
  // We have 3 buckets, so expect 3 uploads
@@ -799,10 +873,10 @@ describe("pages", () => {
799
873
  );
800
874
 
801
875
  expect(std.out).toMatchInlineSnapshot(`
802
- "✨ Success! Uploaded 4 files (TIMINGS)
876
+ "✨ Success! Uploaded 4 files (TIMINGS)
803
877
 
804
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
805
- `);
878
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
879
+ `);
806
880
  });
807
881
 
808
882
  it("should resolve the current directory correctly", async () => {
@@ -854,13 +928,13 @@ describe("pages", () => {
854
928
  const body = init.body as FormData;
855
929
  const manifest = JSON.parse(body.get("manifest") as string);
856
930
  expect(manifest).toMatchInlineSnapshot(`
857
- Object {
858
- "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
859
- "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
860
- "/logo.js": "6be321bef99e758250dac034474ddbb8",
861
- "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
862
- }
863
- `);
931
+ Object {
932
+ "/imgs/logo.png": "2082190357cfd3617ccfe04f340c6247",
933
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
934
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
935
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
936
+ }
937
+ `);
864
938
  });
865
939
 
866
940
  return {
@@ -869,6 +943,17 @@ describe("pages", () => {
869
943
  }
870
944
  );
871
945
 
946
+ setMockResponse(
947
+ "/accounts/:accountId/pages/projects/foo",
948
+ "GET",
949
+ async ([_url, accountId]) => {
950
+ assertLater(() => {
951
+ expect(accountId).toEqual("some-account-id");
952
+ });
953
+ return { deployment_configs: { production: {}, preview: {} } };
954
+ }
955
+ );
956
+
872
957
  chdir("public");
873
958
  await runWrangler(`pages publish . --project-name=foo`);
874
959
 
@@ -914,10 +999,10 @@ describe("pages", () => {
914
999
  );
915
1000
 
916
1001
  expect(std.out).toMatchInlineSnapshot(`
917
- "✨ Success! Uploaded 4 files (TIMINGS)
1002
+ "✨ Success! Uploaded 4 files (TIMINGS)
918
1003
 
919
- ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
920
- `);
1004
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1005
+ `);
921
1006
  });
922
1007
 
923
1008
  it("should not error when directory names contain periods and houses a extensionless file", async () => {
@@ -971,6 +1056,17 @@ describe("pages", () => {
971
1056
  })
972
1057
  );
973
1058
 
1059
+ setMockResponse(
1060
+ "/accounts/:accountId/pages/projects/foo",
1061
+ "GET",
1062
+ async ([_url, accountId]) => {
1063
+ assertLater(() => {
1064
+ expect(accountId).toEqual("some-account-id");
1065
+ });
1066
+ return { deployment_configs: { production: {}, preview: {} } };
1067
+ }
1068
+ );
1069
+
974
1070
  await runWrangler("pages publish . --project-name=foo");
975
1071
 
976
1072
  expect(std.err).toMatchInlineSnapshot(`""`);
@@ -988,27 +1084,336 @@ describe("pages", () => {
988
1084
  `"Pages does not support wrangler.toml"`
989
1085
  );
990
1086
  });
991
- });
992
1087
 
993
- describe("project upload", () => {
994
- const ENV_COPY = process.env;
1088
+ it("should upload a Functions project", async () => {
1089
+ // set up the directory of static files to upload.
1090
+ mkdirSync("public");
1091
+ writeFileSync("public/README.md", "This is a readme");
1092
+
1093
+ // set up /functions
1094
+ mkdirSync("functions");
1095
+ writeFileSync(
1096
+ "functions/hello.js",
1097
+ `
1098
+ export async function onRequest() {
1099
+ return new Response("Hello, world!");
1100
+ }
1101
+ `
1102
+ );
995
1103
 
996
- mockAccountId();
997
- mockApiToken();
998
- runInTempDir();
1104
+ mockGetToken("<<funfetti-auth-jwt>>");
999
1105
 
1000
- beforeEach(() => {
1001
- process.env.CI = "true";
1002
- process.env.CF_PAGES_UPLOAD_JWT = "<<funfetti-auth-jwt>>";
1106
+ setMockResponse(
1107
+ "/pages/assets/check-missing",
1108
+ "POST",
1109
+ async (_, init) => {
1110
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1111
+ assertLater(() => {
1112
+ expect(init.headers).toMatchObject({
1113
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1114
+ });
1115
+ expect(body).toMatchObject({
1116
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1117
+ });
1118
+ });
1119
+ return body.hashes;
1120
+ }
1121
+ );
1122
+
1123
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1124
+ assertLater(() => {
1125
+ expect(init.headers).toMatchObject({
1126
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1127
+ });
1128
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1129
+ expect(body).toMatchObject([
1130
+ {
1131
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1132
+ value: Buffer.from("This is a readme").toString("base64"),
1133
+ metadata: {
1134
+ contentType: "text/markdown",
1135
+ },
1136
+ base64: true,
1137
+ },
1138
+ ]);
1139
+ });
1140
+ });
1141
+
1142
+ setMockResponse(
1143
+ `/pages/assets/upsert-hashes`,
1144
+ "POST",
1145
+ async (_, init) => {
1146
+ assertLater(() => {
1147
+ expect(init.headers).toMatchObject({
1148
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1149
+ });
1150
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1151
+ expect(body).toMatchObject({
1152
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1153
+ });
1154
+ });
1155
+
1156
+ return Promise.resolve(true);
1157
+ }
1158
+ );
1159
+
1160
+ setMockResponse(
1161
+ "/accounts/:accountId/pages/projects/foo/deployments",
1162
+ async ([_url, accountId], init) => {
1163
+ assertLater(async () => {
1164
+ expect(accountId).toEqual("some-account-id");
1165
+ expect(init.method).toEqual("POST");
1166
+ const body = init.body as FormData;
1167
+ const manifest = JSON.parse(body.get("manifest") as string);
1168
+
1169
+ // for Functions projects, we auto-generate a `_worker.js`,
1170
+ // `functions-filepath-routing-config.json`, and `_routes.json`
1171
+ // file, based on the contents of `/functions`
1172
+ const generatedWorkerJS = body.get("_worker.js") as Blob;
1173
+ const generatedRoutesJSON = await (
1174
+ body.get("_routes.json") as Blob
1175
+ ).text();
1176
+ const generatedFilepathRoutingConfig = await (
1177
+ body.get("functions-filepath-routing-config.json") as Blob
1178
+ ).text();
1179
+
1180
+ // make sure this is all we uploaded
1181
+ expect([...body.keys()]).toEqual([
1182
+ "manifest",
1183
+ "functions-filepath-routing-config.json",
1184
+ "_worker.js",
1185
+ "_routes.json",
1186
+ ]);
1187
+
1188
+ expect(manifest).toMatchInlineSnapshot(`
1189
+ Object {
1190
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1191
+ }
1192
+ `);
1193
+
1194
+ // the contents of the generated `_worker.js` file is pretty massive, so I don't
1195
+ // think snapshot testing makes much sense here. Plus, calling
1196
+ // `.toMatchInlineSnapshot()` without any arguments, in order to generate that
1197
+ // snapshot value, doesn't generate anything in this case (probably because the
1198
+ // file contents is too big). So for now, let's test that _worker.js was indeed
1199
+ // generated and that the file size is greater than zero
1200
+ expect(generatedWorkerJS).not.toBeNull();
1201
+ expect(generatedWorkerJS.size).toBeGreaterThan(0);
1202
+
1203
+ const maybeRoutesJSONSpec = JSON.parse(generatedRoutesJSON);
1204
+ expect(isRoutesJSONSpec(maybeRoutesJSONSpec)).toBe(true);
1205
+ expect(maybeRoutesJSONSpec).toMatchObject({
1206
+ version: ROUTES_SPEC_VERSION,
1207
+ description: `Generated by wrangler@${version}`,
1208
+ include: ["/hello"],
1209
+ exclude: [],
1210
+ });
1211
+
1212
+ // Make sure the routing config is valid json
1213
+ const parsedFilepathRoutingConfig = JSON.parse(
1214
+ generatedFilepathRoutingConfig
1215
+ );
1216
+ // The actual shape doesn't matter that much since this
1217
+ // is only used for display in Dash, but it's still useful for
1218
+ // tracking unexpected changes to this config.
1219
+ expect(parsedFilepathRoutingConfig).toStrictEqual({
1220
+ routes: [
1221
+ {
1222
+ routePath: "/hello",
1223
+ mountPath: "/",
1224
+ method: "",
1225
+ module: ["hello.js:onRequest"],
1226
+ },
1227
+ ],
1228
+ baseURL: "/",
1229
+ });
1230
+ });
1231
+
1232
+ return {
1233
+ url: "https://abcxyz.foo.pages.dev/",
1234
+ };
1235
+ }
1236
+ );
1237
+
1238
+ setMockResponse(
1239
+ "/accounts/:accountId/pages/projects/foo",
1240
+ "GET",
1241
+ async ([_url, accountId]) => {
1242
+ assertLater(() => {
1243
+ expect(accountId).toEqual("some-account-id");
1244
+ });
1245
+ return { deployment_configs: { production: {}, preview: {} } };
1246
+ }
1247
+ );
1248
+
1249
+ await runWrangler("pages publish public --project-name=foo");
1250
+
1251
+ expect(std.out).toMatchInlineSnapshot(`
1252
+ "Compiled Worker successfully.
1253
+ ✨ Success! Uploaded 1 files (TIMINGS)
1254
+
1255
+ ✨ Uploading Functions
1256
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1257
+ `);
1258
+
1259
+ expect(std.err).toMatchInlineSnapshot('""');
1003
1260
  });
1004
1261
 
1005
- afterEach(() => {
1006
- unsetAllMocks();
1007
- process.env = ENV_COPY;
1262
+ it("should upload an Advanced Mode project", async () => {
1263
+ // set up the directory of static files to upload.
1264
+ mkdirSync("public");
1265
+ writeFileSync("public/README.md", "This is a readme");
1266
+
1267
+ // set up _worker.js
1268
+ writeFileSync(
1269
+ "public/_worker.js",
1270
+ `
1271
+ export default {
1272
+ async fetch(request, env) {
1273
+ const url = new URL(request.url);
1274
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1275
+ };
1276
+ `
1277
+ );
1278
+
1279
+ mockGetToken("<<funfetti-auth-jwt>>");
1280
+
1281
+ setMockResponse(
1282
+ "/pages/assets/check-missing",
1283
+ "POST",
1284
+ async (_, init) => {
1285
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1286
+ assertLater(() => {
1287
+ expect(init.headers).toMatchObject({
1288
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1289
+ });
1290
+ expect(body).toMatchObject({
1291
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1292
+ });
1293
+ });
1294
+ return body.hashes;
1295
+ }
1296
+ );
1297
+
1298
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1299
+ assertLater(() => {
1300
+ expect(init.headers).toMatchObject({
1301
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1302
+ });
1303
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1304
+ expect(body).toMatchObject([
1305
+ {
1306
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1307
+ value: Buffer.from("This is a readme").toString("base64"),
1308
+ metadata: {
1309
+ contentType: "text/markdown",
1310
+ },
1311
+ base64: true,
1312
+ },
1313
+ ]);
1314
+ });
1315
+ });
1316
+
1317
+ setMockResponse(
1318
+ "/accounts/:accountId/pages/projects/foo/deployments",
1319
+ async ([_url, accountId], init) => {
1320
+ assertLater(async () => {
1321
+ expect(accountId).toEqual("some-account-id");
1322
+ expect(init.method).toEqual("POST");
1323
+ const body = init.body as FormData;
1324
+ const manifest = JSON.parse(body.get("manifest") as string);
1325
+ const customWorkerJS = await (
1326
+ body.get("_worker.js") as Blob
1327
+ ).text();
1328
+
1329
+ // make sure this is all we uploaded
1330
+ expect([...body.keys()]).toEqual(["manifest", "_worker.js"]);
1331
+
1332
+ expect(manifest).toMatchInlineSnapshot(`
1333
+ Object {
1334
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1335
+ }
1336
+ `);
1337
+
1338
+ expect(customWorkerJS).toMatchInlineSnapshot(`
1339
+ "
1340
+ export default {
1341
+ async fetch(request, env) {
1342
+ const url = new URL(request.url);
1343
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1344
+ };
1345
+ "
1346
+ `);
1347
+ });
1348
+
1349
+ return {
1350
+ url: "https://abcxyz.foo.pages.dev/",
1351
+ };
1352
+ }
1353
+ );
1354
+
1355
+ setMockResponse(
1356
+ "/accounts/:accountId/pages/projects/foo",
1357
+ "GET",
1358
+ async ([_url, accountId]) => {
1359
+ assertLater(() => {
1360
+ expect(accountId).toEqual("some-account-id");
1361
+ });
1362
+ return { deployment_configs: { production: {}, preview: {} } };
1363
+ }
1364
+ );
1365
+
1366
+ await runWrangler("pages publish public --project-name=foo");
1367
+
1368
+ expect(std.out).toMatchInlineSnapshot(`
1369
+ "✨ Success! Uploaded 1 files (TIMINGS)
1370
+
1371
+ ✨ Uploading _worker.js
1372
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1373
+ `);
1374
+
1375
+ expect(std.err).toMatchInlineSnapshot('""');
1008
1376
  });
1009
1377
 
1010
- it("should upload a directory of files with a provided JWT", async () => {
1011
- writeFileSync("logo.png", "foobar");
1378
+ it("should upload _routes.json for Functions projects, if provided", async () => {
1379
+ // set up the directory of static files to upload.
1380
+ mkdirSync("public");
1381
+ writeFileSync("public/README.md", "This is a readme");
1382
+
1383
+ // set up /functions
1384
+ mkdirSync("functions");
1385
+ writeFileSync(
1386
+ "functions/hello.js",
1387
+ `
1388
+ export async function onRequest() {
1389
+ return new Response("Hello, world!");
1390
+ }
1391
+ `
1392
+ );
1393
+
1394
+ writeFileSync(
1395
+ "functions/goodbye.ts",
1396
+ `
1397
+ export async function onRequest() {
1398
+ return new Response("Bye bye!");
1399
+ }
1400
+ `
1401
+ );
1402
+
1403
+ // set up _routes.json
1404
+ writeFileSync(
1405
+ "public/_routes.json",
1406
+ `
1407
+ {
1408
+ "version": ${ROUTES_SPEC_VERSION},
1409
+ "description": "Custom _routes.json file",
1410
+ "include": ["/hello"],
1411
+ "exclude": []
1412
+ }
1413
+ `
1414
+ );
1415
+
1416
+ mockGetToken("<<funfetti-auth-jwt>>");
1012
1417
 
1013
1418
  setMockResponse(
1014
1419
  "/pages/assets/check-missing",
@@ -1020,7 +1425,7 @@ describe("pages", () => {
1020
1425
  Authorization: "Bearer <<funfetti-auth-jwt>>",
1021
1426
  });
1022
1427
  expect(body).toMatchObject({
1023
- hashes: ["2082190357cfd3617ccfe04f340c6247"],
1428
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1024
1429
  });
1025
1430
  });
1026
1431
  return body.hashes;
@@ -1035,10 +1440,10 @@ describe("pages", () => {
1035
1440
  const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1036
1441
  expect(body).toMatchObject([
1037
1442
  {
1038
- key: "2082190357cfd3617ccfe04f340c6247",
1039
- value: Buffer.from("foobar").toString("base64"),
1443
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1444
+ value: Buffer.from("This is a readme").toString("base64"),
1040
1445
  metadata: {
1041
- contentType: "image/png",
1446
+ contentType: "text/markdown",
1042
1447
  },
1043
1448
  base64: true,
1044
1449
  },
@@ -1046,13 +1451,790 @@ describe("pages", () => {
1046
1451
  });
1047
1452
  });
1048
1453
 
1454
+ setMockResponse(
1455
+ `/pages/assets/upsert-hashes`,
1456
+ "POST",
1457
+ async (_, init) => {
1458
+ assertLater(() => {
1459
+ expect(init.headers).toMatchObject({
1460
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1461
+ });
1462
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1463
+ expect(body).toMatchObject({
1464
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1465
+ });
1466
+ });
1467
+
1468
+ return Promise.resolve(true);
1469
+ }
1470
+ );
1471
+
1472
+ setMockResponse(
1473
+ "/accounts/:accountId/pages/projects/foo/deployments",
1474
+ async ([_url, accountId], init) => {
1475
+ assertLater(async () => {
1476
+ expect(accountId).toEqual("some-account-id");
1477
+ expect(init.method).toEqual("POST");
1478
+ const body = init.body as FormData;
1479
+ const manifest = JSON.parse(body.get("manifest") as string);
1480
+ const generatedWorkerJS = body.get("_worker.js") as Blob;
1481
+ const customRoutesJSON = await (
1482
+ body.get("_routes.json") as Blob
1483
+ ).text();
1484
+ const generatedFilepathRoutingConfig = await (
1485
+ body.get("functions-filepath-routing-config.json") as Blob
1486
+ ).text();
1487
+
1488
+ // make sure this is all we uploaded
1489
+ expect([...body.keys()]).toEqual([
1490
+ "manifest",
1491
+ "functions-filepath-routing-config.json",
1492
+ "_worker.js",
1493
+ "_routes.json",
1494
+ ]);
1495
+
1496
+ expect(manifest).toMatchInlineSnapshot(`
1497
+ Object {
1498
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1499
+ }
1500
+ `);
1501
+
1502
+ // file content of generated `_worker.js` is too massive to snapshot test
1503
+ expect(generatedWorkerJS).not.toBeNull();
1504
+ expect(generatedWorkerJS.size).toBeGreaterThan(0);
1505
+
1506
+ const customRoutes = JSON.parse(customRoutesJSON);
1507
+ expect(customRoutes).toMatchObject({
1508
+ version: ROUTES_SPEC_VERSION,
1509
+ description: "Custom _routes.json file",
1510
+ include: ["/hello"],
1511
+ exclude: [],
1512
+ });
1513
+
1514
+ // Make sure the routing config is valid json
1515
+ const parsedFilepathRoutingConfig = JSON.parse(
1516
+ generatedFilepathRoutingConfig
1517
+ );
1518
+ // The actual shape doesn't matter that much since this
1519
+ // is only used for display in Dash, but it's still useful for
1520
+ // tracking unexpected changes to this config.
1521
+ console.log(generatedFilepathRoutingConfig);
1522
+ expect(parsedFilepathRoutingConfig).toStrictEqual({
1523
+ routes: [
1524
+ {
1525
+ routePath: "/goodbye",
1526
+ mountPath: "/",
1527
+ method: "",
1528
+ module: ["goodbye.ts:onRequest"],
1529
+ },
1530
+ {
1531
+ routePath: "/hello",
1532
+ mountPath: "/",
1533
+ method: "",
1534
+ module: ["hello.js:onRequest"],
1535
+ },
1536
+ ],
1537
+ baseURL: "/",
1538
+ });
1539
+ });
1540
+
1541
+ return {
1542
+ url: "https://abcxyz.foo.pages.dev/",
1543
+ };
1544
+ }
1545
+ );
1546
+
1547
+ setMockResponse(
1548
+ "/accounts/:accountId/pages/projects/foo",
1549
+ "GET",
1550
+ async ([_url, accountId]) => {
1551
+ assertLater(() => {
1552
+ expect(accountId).toEqual("some-account-id");
1553
+ });
1554
+ return { deployment_configs: { production: {}, preview: {} } };
1555
+ }
1556
+ );
1557
+
1558
+ await runWrangler("pages publish public --project-name=foo");
1559
+
1560
+ expect(std.out).toMatchInlineSnapshot(`
1561
+ "Compiled Worker successfully.
1562
+ ✨ Success! Uploaded 1 files (TIMINGS)
1563
+
1564
+ ✨ Uploading Functions
1565
+ ✨ Uploading _routes.json
1566
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1567
+ `);
1568
+
1569
+ expect(std.warn).toMatchInlineSnapshot(`
1570
+ "▲ [WARNING] _routes.json is an experimental feature and is subject to change. Please use with care.
1571
+
1572
+ "
1573
+ `);
1574
+
1575
+ expect(std.err).toMatchInlineSnapshot('""');
1576
+ });
1577
+
1578
+ it("should not deploy Functions projects that provide an invalid custom _routes.json file", async () => {
1579
+ // set up the directory of static files to upload.
1580
+ mkdirSync("public");
1581
+ writeFileSync("public/README.md", "This is a readme");
1582
+
1583
+ // set up _routes.json
1584
+ writeFileSync(
1585
+ "public/_routes.json",
1586
+ `
1587
+ {
1588
+ "description": "Custom _routes.json file",
1589
+ "include": [],
1590
+ "exclude": []
1591
+ }
1592
+ `
1593
+ );
1594
+
1595
+ // set up /functions
1596
+ mkdirSync("functions");
1597
+ writeFileSync(
1598
+ "functions/hello.js",
1599
+ `
1600
+ export async function onRequest() {
1601
+ return new Response("Hello, world!");
1602
+ }
1603
+ `
1604
+ );
1605
+
1606
+ mockGetToken("<<funfetti-auth-jwt>>");
1607
+
1608
+ setMockResponse(
1609
+ "/pages/assets/check-missing",
1610
+ "POST",
1611
+ async (_, init) => {
1612
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1613
+ assertLater(() => {
1614
+ expect(init.headers).toMatchObject({
1615
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1616
+ });
1617
+ expect(body).toMatchObject({
1618
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1619
+ });
1620
+ });
1621
+ return body.hashes;
1622
+ }
1623
+ );
1624
+
1625
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1626
+ assertLater(() => {
1627
+ expect(init.headers).toMatchObject({
1628
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1629
+ });
1630
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1631
+ expect(body).toMatchObject([
1632
+ {
1633
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1634
+ value: Buffer.from("This is a readme").toString("base64"),
1635
+ metadata: {
1636
+ contentType: "text/markdown",
1637
+ },
1638
+ base64: true,
1639
+ },
1640
+ ]);
1641
+ });
1642
+ });
1643
+
1644
+ setMockResponse(
1645
+ "/accounts/:accountId/pages/projects/foo",
1646
+ "GET",
1647
+ async ([_url, accountId]) => {
1648
+ assertLater(() => {
1649
+ expect(accountId).toEqual("some-account-id");
1650
+ });
1651
+ return { deployment_configs: { production: {}, preview: {} } };
1652
+ }
1653
+ );
1654
+
1655
+ await expect(runWrangler("pages publish public --project-name=foo"))
1656
+ .rejects
1657
+ .toThrow(`Invalid _routes.json file found at: public/_routes.json
1658
+ Please make sure the JSON object has the following format:
1659
+ {
1660
+ version: ${ROUTES_SPEC_VERSION};
1661
+ include: string[];
1662
+ exclude: string[];
1663
+ }
1664
+ and that at least one include rule is provided.
1665
+ `);
1666
+ });
1667
+
1668
+ it("should upload _routes.json for Advanced Mode projects, if provided", async () => {
1669
+ // set up the directory of static files to upload.
1670
+ mkdirSync("public");
1671
+ writeFileSync("public/README.md", "This is a readme");
1672
+
1673
+ // set up _routes.json
1674
+ writeFileSync(
1675
+ "public/_routes.json",
1676
+ `
1677
+ {
1678
+ "version": ${ROUTES_SPEC_VERSION},
1679
+ "description": "Custom _routes.json file",
1680
+ "include": ["/api/*"],
1681
+ "exclude": []
1682
+ }
1683
+ `
1684
+ );
1685
+
1686
+ // set up _worker.js
1687
+ writeFileSync(
1688
+ "public/_worker.js",
1689
+ `
1690
+ export default {
1691
+ async fetch(request, env) {
1692
+ const url = new URL(request.url);
1693
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1694
+ };
1695
+ `
1696
+ );
1697
+
1698
+ mockGetToken("<<funfetti-auth-jwt>>");
1699
+
1700
+ setMockResponse(
1701
+ "/pages/assets/check-missing",
1702
+ "POST",
1703
+ async (_, init) => {
1704
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1705
+ assertLater(() => {
1706
+ expect(init.headers).toMatchObject({
1707
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1708
+ });
1709
+ expect(body).toMatchObject({
1710
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1711
+ });
1712
+ });
1713
+ return body.hashes;
1714
+ }
1715
+ );
1716
+
1717
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1718
+ assertLater(() => {
1719
+ expect(init.headers).toMatchObject({
1720
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1721
+ });
1722
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1723
+ expect(body).toMatchObject([
1724
+ {
1725
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1726
+ value: Buffer.from("This is a readme").toString("base64"),
1727
+ metadata: {
1728
+ contentType: "text/markdown",
1729
+ },
1730
+ base64: true,
1731
+ },
1732
+ ]);
1733
+ });
1734
+ });
1735
+
1736
+ setMockResponse(
1737
+ `/pages/assets/upsert-hashes`,
1738
+ "POST",
1739
+ async (_, init) => {
1740
+ assertLater(() => {
1741
+ expect(init.headers).toMatchObject({
1742
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1743
+ });
1744
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1745
+ expect(body).toMatchObject({
1746
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1747
+ });
1748
+ });
1749
+
1750
+ return Promise.resolve(true);
1751
+ }
1752
+ );
1753
+
1754
+ setMockResponse(
1755
+ "/accounts/:accountId/pages/projects/foo/deployments",
1756
+ async ([_url, accountId], init) => {
1757
+ assertLater(async () => {
1758
+ expect(accountId).toEqual("some-account-id");
1759
+ expect(init.method).toEqual("POST");
1760
+ const body = init.body as FormData;
1761
+ const manifest = JSON.parse(body.get("manifest") as string);
1762
+ const customWorkerJS = await (
1763
+ body.get("_worker.js") as Blob
1764
+ ).text();
1765
+ const customRoutesJSON = await (
1766
+ body.get("_routes.json") as Blob
1767
+ ).text();
1768
+
1769
+ // make sure this is all we uploaded
1770
+ expect([...body.keys()]).toEqual([
1771
+ "manifest",
1772
+ "_worker.js",
1773
+ "_routes.json",
1774
+ ]);
1775
+
1776
+ expect(manifest).toMatchInlineSnapshot(`
1777
+ Object {
1778
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
1779
+ }
1780
+ `);
1781
+
1782
+ expect(customWorkerJS).toMatchInlineSnapshot(`
1783
+ "
1784
+ export default {
1785
+ async fetch(request, env) {
1786
+ const url = new URL(request.url);
1787
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1788
+ };
1789
+ "
1790
+ `);
1791
+
1792
+ const customRoutes = JSON.parse(customRoutesJSON);
1793
+ expect(customRoutes).toMatchObject({
1794
+ version: ROUTES_SPEC_VERSION,
1795
+ description: "Custom _routes.json file",
1796
+ include: ["/api/*"],
1797
+ exclude: [],
1798
+ });
1799
+ });
1800
+
1801
+ return {
1802
+ url: "https://abcxyz.foo.pages.dev/",
1803
+ };
1804
+ }
1805
+ );
1806
+
1807
+ setMockResponse(
1808
+ "/accounts/:accountId/pages/projects/foo",
1809
+ "GET",
1810
+ async ([_url, accountId]) => {
1811
+ assertLater(() => {
1812
+ expect(accountId).toEqual("some-account-id");
1813
+ });
1814
+ return { deployment_configs: { production: {}, preview: {} } };
1815
+ }
1816
+ );
1817
+
1818
+ await runWrangler("pages publish public --project-name=foo");
1819
+
1820
+ expect(std.out).toMatchInlineSnapshot(`
1821
+ "✨ Success! Uploaded 1 files (TIMINGS)
1822
+
1823
+ ✨ Uploading _worker.js
1824
+ ✨ Uploading _routes.json
1825
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
1826
+ `);
1827
+
1828
+ expect(std.warn).toMatchInlineSnapshot(`
1829
+ "▲ [WARNING] _routes.json is an experimental feature and is subject to change. Please use with care.
1830
+
1831
+ "
1832
+ `);
1833
+ expect(std.err).toMatchInlineSnapshot(`""`);
1834
+ });
1835
+
1836
+ it("should not deploy Advanced Mode projects that provide an invalid _routes.json file", async () => {
1837
+ // set up the directory of static files to upload.
1838
+ mkdirSync("public");
1839
+ writeFileSync("public/README.md", "This is a readme");
1840
+
1841
+ // set up _routes.json
1842
+ writeFileSync(
1843
+ "public/_routes.json",
1844
+ `
1845
+ {
1846
+ "description": "Custom _routes.json file",
1847
+ "include": [],
1848
+ "exclude": []
1849
+ }
1850
+ `
1851
+ );
1852
+
1853
+ // set up _worker.js
1854
+ writeFileSync(
1855
+ "public/_worker.js",
1856
+ `
1857
+ export default {
1858
+ async fetch(request, env) {
1859
+ const url = new URL(request.url);
1860
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1861
+ };
1862
+ `
1863
+ );
1864
+
1865
+ mockGetToken("<<funfetti-auth-jwt>>");
1866
+
1867
+ setMockResponse(
1868
+ "/pages/assets/check-missing",
1869
+ "POST",
1870
+ async (_, init) => {
1871
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1872
+ assertLater(() => {
1873
+ expect(init.headers).toMatchObject({
1874
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1875
+ });
1876
+ expect(body).toMatchObject({
1877
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1878
+ });
1879
+ });
1880
+ return body.hashes;
1881
+ }
1882
+ );
1883
+
1884
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1885
+ assertLater(() => {
1886
+ expect(init.headers).toMatchObject({
1887
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1888
+ });
1889
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1890
+ expect(body).toMatchObject([
1891
+ {
1892
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1893
+ value: Buffer.from("This is a readme").toString("base64"),
1894
+ metadata: {
1895
+ contentType: "text/markdown",
1896
+ },
1897
+ base64: true,
1898
+ },
1899
+ ]);
1900
+ });
1901
+ });
1902
+
1903
+ setMockResponse(
1904
+ "/accounts/:accountId/pages/projects/foo",
1905
+ "GET",
1906
+ async ([_url, accountId]) => {
1907
+ assertLater(() => {
1908
+ expect(accountId).toEqual("some-account-id");
1909
+ });
1910
+ return { deployment_configs: { production: {}, preview: {} } };
1911
+ }
1912
+ );
1913
+
1914
+ await expect(runWrangler("pages publish public --project-name=foo"))
1915
+ .rejects
1916
+ .toThrow(`Invalid _routes.json file found at: public/_routes.json
1917
+ Please make sure the JSON object has the following format:
1918
+ {
1919
+ version: ${ROUTES_SPEC_VERSION};
1920
+ include: string[];
1921
+ exclude: string[];
1922
+ }
1923
+ and that at least one include rule is provided.
1924
+ `);
1925
+ });
1926
+
1927
+ it("should ignore the entire /functions directory if _worker.js is provided", async () => {
1928
+ // set up the directory of static files to upload.
1929
+ mkdirSync("public");
1930
+ writeFileSync("public/README.md", "This is a readme");
1931
+
1932
+ // set up /functions
1933
+ mkdirSync("functions");
1934
+ writeFileSync(
1935
+ "functions/hello.js",
1936
+ `
1937
+ export async function onRequest() {
1938
+ return new Response("Hello, world!");
1939
+ }
1940
+ `
1941
+ );
1942
+
1943
+ // set up _worker.js
1944
+ writeFileSync(
1945
+ "public/_worker.js",
1946
+ `
1947
+ export default {
1948
+ async fetch(request, env) {
1949
+ const url = new URL(request.url);
1950
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
1951
+ };
1952
+ `
1953
+ );
1954
+
1955
+ mockGetToken("<<funfetti-auth-jwt>>");
1956
+
1957
+ setMockResponse(
1958
+ "/pages/assets/check-missing",
1959
+ "POST",
1960
+ async (_, init) => {
1961
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
1962
+ assertLater(() => {
1963
+ expect(init.headers).toMatchObject({
1964
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1965
+ });
1966
+ expect(body).toMatchObject({
1967
+ hashes: ["13a03eaf24ae98378acd36ea00f77f2f"],
1968
+ });
1969
+ });
1970
+ return body.hashes;
1971
+ }
1972
+ );
1973
+
1974
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
1975
+ assertLater(() => {
1976
+ expect(init.headers).toMatchObject({
1977
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
1978
+ });
1979
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
1980
+ expect(body).toMatchObject([
1981
+ {
1982
+ key: "13a03eaf24ae98378acd36ea00f77f2f",
1983
+ value: Buffer.from("This is a readme").toString("base64"),
1984
+ metadata: {
1985
+ contentType: "text/markdown",
1986
+ },
1987
+ base64: true,
1988
+ },
1989
+ ]);
1990
+ });
1991
+ });
1992
+
1993
+ setMockResponse(
1994
+ "/accounts/:accountId/pages/projects/foo/deployments",
1995
+ async ([_url, accountId], init) => {
1996
+ assertLater(async () => {
1997
+ expect(accountId).toEqual("some-account-id");
1998
+ expect(init.method).toEqual("POST");
1999
+ const body = init.body as FormData;
2000
+ const manifest = JSON.parse(body.get("manifest") as string);
2001
+ const customWorkerJS = await (
2002
+ body.get("_worker.js") as Blob
2003
+ ).text();
2004
+
2005
+ // make sure this is all we uploaded
2006
+ expect([...body.keys()]).toEqual(["manifest", "_worker.js"]);
2007
+
2008
+ expect(manifest).toMatchInlineSnapshot(`
2009
+ Object {
2010
+ "/README.md": "13a03eaf24ae98378acd36ea00f77f2f",
2011
+ }
2012
+ `);
2013
+
2014
+ expect(customWorkerJS).toMatchInlineSnapshot(`
2015
+ "
2016
+ export default {
2017
+ async fetch(request, env) {
2018
+ const url = new URL(request.url);
2019
+ return url.pathname.startsWith('/api/') ? new Response('Ok') : env.ASSETS.fetch(request);
2020
+ };
2021
+ "
2022
+ `);
2023
+ });
2024
+
2025
+ return {
2026
+ url: "https://abcxyz.foo.pages.dev/",
2027
+ };
2028
+ }
2029
+ );
2030
+
2031
+ setMockResponse(
2032
+ "/accounts/:accountId/pages/projects/foo",
2033
+ "GET",
2034
+ async ([_url, accountId]) => {
2035
+ assertLater(() => {
2036
+ expect(accountId).toEqual("some-account-id");
2037
+ });
2038
+ return { deployment_configs: { production: {}, preview: {} } };
2039
+ }
2040
+ );
2041
+
2042
+ await runWrangler("pages publish public --project-name=foo");
2043
+
2044
+ expect(std.out).toMatchInlineSnapshot(`
2045
+ "✨ Success! Uploaded 1 files (TIMINGS)
2046
+
2047
+ ✨ Uploading _worker.js
2048
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
2049
+ `);
2050
+
2051
+ expect(std.err).toMatchInlineSnapshot('""');
2052
+ });
2053
+ });
2054
+
2055
+ describe("project upload", () => {
2056
+ const ENV_COPY = process.env;
2057
+
2058
+ mockAccountId();
2059
+ mockApiToken();
2060
+ runInTempDir();
2061
+
2062
+ beforeEach(() => {
2063
+ process.env.CI = "true";
2064
+ process.env.CF_PAGES_UPLOAD_JWT = "<<funfetti-auth-jwt>>";
2065
+ });
2066
+
2067
+ afterEach(() => {
2068
+ unsetAllMocks();
2069
+ process.env = ENV_COPY;
2070
+ });
2071
+
2072
+ it("should upload a directory of files with a provided JWT", async () => {
2073
+ writeFileSync("logo.png", "foobar");
2074
+
2075
+ setMockResponse(
2076
+ "/pages/assets/check-missing",
2077
+ "POST",
2078
+ async (_, init) => {
2079
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
2080
+ assertLater(() => {
2081
+ expect(init.headers).toMatchObject({
2082
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2083
+ });
2084
+ expect(body).toMatchObject({
2085
+ hashes: ["2082190357cfd3617ccfe04f340c6247"],
2086
+ });
2087
+ });
2088
+ return body.hashes;
2089
+ }
2090
+ );
2091
+
2092
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
2093
+ assertLater(() => {
2094
+ expect(init.headers).toMatchObject({
2095
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2096
+ });
2097
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
2098
+ expect(body).toMatchObject([
2099
+ {
2100
+ key: "2082190357cfd3617ccfe04f340c6247",
2101
+ value: Buffer.from("foobar").toString("base64"),
2102
+ metadata: {
2103
+ contentType: "image/png",
2104
+ },
2105
+ base64: true,
2106
+ },
2107
+ ]);
2108
+ });
2109
+ });
2110
+
2111
+ await runWrangler("pages project upload .");
2112
+
2113
+ expect(std.out).toMatchInlineSnapshot(`
2114
+ "✨ Success! Uploaded 1 files (TIMINGS)
2115
+
2116
+ ✨ Upload complete!"
2117
+ `);
2118
+ });
2119
+
2120
+ it("should avoid uploading some files", async () => {
2121
+ mkdirSync("some_dir/node_modules", { recursive: true });
2122
+ mkdirSync("some_dir/functions", { recursive: true });
2123
+
2124
+ writeFileSync("logo.png", "foobar");
2125
+ writeFileSync("some_dir/functions/foo.js", "func");
2126
+ writeFileSync("some_dir/_headers", "headersfile");
2127
+
2128
+ writeFileSync("_headers", "headersfile");
2129
+ writeFileSync("_redirects", "redirectsfile");
2130
+ writeFileSync("_worker.js", "workerfile");
2131
+ writeFileSync("_routes.json", "routesfile");
2132
+ mkdirSync(".git");
2133
+ writeFileSync(".git/foo", "gitfile");
2134
+ writeFileSync("some_dir/node_modules/some_package", "nodefile");
2135
+ mkdirSync("functions");
2136
+ writeFileSync("functions/foo.js", "func");
2137
+
2138
+ setMockResponse(
2139
+ "/pages/assets/check-missing",
2140
+ "POST",
2141
+ async (_, init) => {
2142
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
2143
+ assertLater(() => {
2144
+ expect(init.headers).toMatchObject({
2145
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2146
+ });
2147
+ expect(body).toMatchObject({
2148
+ hashes: [
2149
+ "2082190357cfd3617ccfe04f340c6247",
2150
+ "95dedb64e6d4940fc2e0f11f711cc2f4",
2151
+ "09a79777abda8ccc8bdd51dd3ff8e9e9",
2152
+ ],
2153
+ });
2154
+ });
2155
+ return body.hashes;
2156
+ }
2157
+ );
2158
+
2159
+ // Accumulate multiple requests then assert afterwards
2160
+ const requests: RequestInit[] = [];
2161
+ setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
2162
+ requests.push(init);
2163
+
2164
+ return createFetchResult(null, true);
2165
+ });
2166
+
2167
+ assertLater(() => {
2168
+ expect(requests.length).toBe(3);
2169
+
2170
+ const sortedRequests = requests.sort((a, b) => {
2171
+ return (JSON.parse(a.body as string)[0].key as string).localeCompare(
2172
+ JSON.parse(b.body as string)[0].key as string
2173
+ );
2174
+ });
2175
+
2176
+ expect(sortedRequests[0].headers).toMatchObject({
2177
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2178
+ });
2179
+
2180
+ let body = JSON.parse(
2181
+ sortedRequests[0].body as string
2182
+ ) as UploadPayloadFile[];
2183
+ expect(body).toMatchObject([
2184
+ {
2185
+ key: "09a79777abda8ccc8bdd51dd3ff8e9e9",
2186
+ value: Buffer.from("func").toString("base64"),
2187
+ metadata: {
2188
+ contentType: "application/javascript",
2189
+ },
2190
+ base64: true,
2191
+ },
2192
+ ]);
2193
+
2194
+ expect(sortedRequests[1].headers).toMatchObject({
2195
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2196
+ });
2197
+
2198
+ body = JSON.parse(
2199
+ sortedRequests[1].body as string
2200
+ ) as UploadPayloadFile[];
2201
+ expect(body).toMatchObject([
2202
+ {
2203
+ key: "2082190357cfd3617ccfe04f340c6247",
2204
+ value: Buffer.from("foobar").toString("base64"),
2205
+ metadata: {
2206
+ contentType: "image/png",
2207
+ },
2208
+ base64: true,
2209
+ },
2210
+ ]);
2211
+
2212
+ expect(sortedRequests[2].headers).toMatchObject({
2213
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
2214
+ });
2215
+
2216
+ body = JSON.parse(
2217
+ sortedRequests[2].body as string
2218
+ ) as UploadPayloadFile[];
2219
+ expect(body).toMatchObject([
2220
+ {
2221
+ key: "95dedb64e6d4940fc2e0f11f711cc2f4",
2222
+ value: Buffer.from("headersfile").toString("base64"),
2223
+ metadata: {
2224
+ contentType: "application/octet-stream",
2225
+ },
2226
+ base64: true,
2227
+ },
2228
+ ]);
2229
+ });
2230
+
1049
2231
  await runWrangler("pages project upload .");
1050
2232
 
1051
2233
  expect(std.out).toMatchInlineSnapshot(`
1052
- "✨ Success! Uploaded 1 files (TIMINGS)
2234
+ "✨ Success! Uploaded 3 files (TIMINGS)
1053
2235
 
1054
- ✨ Upload complete!"
1055
- `);
2236
+ ✨ Upload complete!"
2237
+ `);
1056
2238
  });
1057
2239
 
1058
2240
  it("should retry uploads", async () => {
@@ -1117,10 +2299,10 @@ describe("pages", () => {
1117
2299
  }
1118
2300
 
1119
2301
  expect(std.out).toMatchInlineSnapshot(`
1120
- "✨ Success! Uploaded 1 files (TIMINGS)
2302
+ "✨ Success! Uploaded 1 files (TIMINGS)
1121
2303
 
1122
- ✨ Upload complete!"
1123
- `);
2304
+ ✨ Upload complete!"
2305
+ `);
1124
2306
  });
1125
2307
 
1126
2308
  it("should try to use multiple buckets (up to the max concurrency)", async () => {
@@ -1203,10 +2385,10 @@ describe("pages", () => {
1203
2385
  );
1204
2386
 
1205
2387
  expect(std.out).toMatchInlineSnapshot(`
1206
- "✨ Success! Uploaded 4 files (TIMINGS)
2388
+ "✨ Success! Uploaded 4 files (TIMINGS)
1207
2389
 
1208
- ✨ Upload complete!"
1209
- `);
2390
+ ✨ Upload complete!"
2391
+ `);
1210
2392
  });
1211
2393
 
1212
2394
  it("should not error when directory names contain periods and houses a extensionless file", async () => {