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
@@ -16,22 +16,20 @@ import * as metrics from "../metrics";
16
16
  import { requireAuth } from "../user";
17
17
  import { buildFunctions } from "./build";
18
18
  import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
19
+ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
20
+ import { validateRoutes } from "./functions/routes-validation";
19
21
  import { listProjects } from "./projects";
22
+ import { promptSelectProject } from "./prompt-select-project";
20
23
  import { upload } from "./upload";
21
24
  import { pagesBetaWarning } from "./utils";
22
- import type { Deployment, PagesConfigCache, Project } from "./types";
23
- import type { ArgumentsCamelCase, Argv } from "yargs";
24
-
25
- type PublishArgs = {
26
- directory: string;
27
- "project-name"?: string;
28
- branch?: string;
29
- "commit-hash"?: string;
30
- "commit-message"?: string;
31
- "commit-dirty"?: boolean;
32
- };
25
+ import type { YargsOptionsToInterface } from "../yargs-types";
26
+ import type { PagesConfigCache } from "./types";
27
+ import type { Project, Deployment } from "@cloudflare/types";
28
+ import type { Argv } from "yargs";
29
+
30
+ type PublishArgs = YargsOptionsToInterface<typeof Options>;
33
31
 
34
- export function Options(yargs: Argv): Argv<PublishArgs> {
32
+ export function Options(yargs: Argv) {
35
33
  return yargs
36
34
  .positional("directory", {
37
35
  type: "string",
@@ -60,6 +58,10 @@ export function Options(yargs: Argv): Argv<PublishArgs> {
60
58
  description:
61
59
  "Whether or not the workspace should be considered dirty for this deployment",
62
60
  },
61
+ "skip-caching": {
62
+ type: "boolean",
63
+ description: "Skip asset caching which speeds up builds",
64
+ },
63
65
  config: {
64
66
  describe: "Pages does not support wrangler.toml",
65
67
  type: "string",
@@ -76,8 +78,9 @@ export const Handler = async ({
76
78
  commitHash,
77
79
  commitMessage,
78
80
  commitDirty,
81
+ skipCaching,
79
82
  config: wranglerConfig,
80
- }: ArgumentsCamelCase<PublishArgs>) => {
83
+ }: PublishArgs) => {
81
84
  if (wranglerConfig) {
82
85
  throw new FatalError("Pages does not support wrangler.toml", 1);
83
86
  }
@@ -132,24 +135,7 @@ export const Handler = async ({
132
135
 
133
136
  switch (existingOrNew) {
134
137
  case "existing": {
135
- projectName = await new Promise((resolve) => {
136
- const { unmount } = render(
137
- <>
138
- <Text>Select a project:</Text>
139
- <SelectInput
140
- items={projects.map((project) => ({
141
- key: project.name,
142
- label: project.name,
143
- value: project,
144
- }))}
145
- onSelect={async (selected) => {
146
- resolve(selected.value.name);
147
- unmount();
148
- }}
149
- />
150
- </>
151
- );
152
- });
138
+ projectName = await promptSelectProject({ accountId });
153
139
  break;
154
140
  }
155
141
  case "new": {
@@ -205,7 +191,6 @@ export const Handler = async ({
205
191
  }
206
192
 
207
193
  // We infer git info by default is not passed in
208
-
209
194
  let isGitDir = true;
210
195
  try {
211
196
  execSync(`git rev-parse --is-inside-work-tree`, {
@@ -249,24 +234,96 @@ export const Handler = async ({
249
234
  }
250
235
  }
251
236
 
237
+ let _headers: string | undefined,
238
+ _redirects: string | undefined,
239
+ _routesGenerated: string | undefined,
240
+ _routesCustom: string | undefined,
241
+ _workerJS: string | undefined;
242
+
243
+ try {
244
+ _headers = readFileSync(join(directory, "_headers"), "utf-8");
245
+ } catch {}
246
+
247
+ try {
248
+ _redirects = readFileSync(join(directory, "_redirects"), "utf-8");
249
+ } catch {}
250
+
251
+ try {
252
+ /**
253
+ * Developers can specify a custom _routes.json file, for projects with Pages
254
+ * Functions or projects in Advanced Mode
255
+ */
256
+ _routesCustom = readFileSync(join(directory, "_routes.json"), "utf-8");
257
+ } catch {}
258
+
259
+ try {
260
+ _workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
261
+ } catch {}
262
+
263
+ // Grab the bindings from the API, we need these for shims and other such hacky inserts
264
+ const project = await fetchResult<Project>(
265
+ `/accounts/${accountId}/pages/projects/${projectName}`
266
+ );
267
+ let isProduction = true;
268
+ if (branch) {
269
+ isProduction = project.production_branch === branch;
270
+ }
271
+
272
+ /**
273
+ * Evaluate if this is an Advanced Mode or Pages Functions project. If Advanced Mode, we'll
274
+ * go ahead and upload `_worker.js` as is, but if Pages Functions, we need to attempt to build
275
+ * Functions first and exit if it failed
276
+ */
252
277
  let builtFunctions: string | undefined = undefined;
253
278
  const functionsDirectory = join(cwd(), "functions");
254
- if (existsSync(functionsDirectory)) {
279
+ const routesOutputPath = !existsSync(join(directory, "_routes.json"))
280
+ ? join(tmpdir(), `_routes-${Math.random()}.json`)
281
+ : undefined;
282
+
283
+ // Routing configuration displayed in the Functions tab of a deployment in Dash
284
+ let filepathRoutingConfig: string | undefined;
285
+
286
+ if (!_workerJS && existsSync(functionsDirectory)) {
255
287
  const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
288
+ const outputConfigPath = join(
289
+ tmpdir(),
290
+ `functions-filepath-routing-config-${Math.random()}.json`
291
+ );
256
292
 
257
- await new Promise((resolve) =>
258
- buildFunctions({
293
+ try {
294
+ await buildFunctions({
259
295
  outfile,
296
+ outputConfigPath,
260
297
  functionsDirectory,
261
- onEnd: () => resolve(null),
298
+ onEnd: () => {},
262
299
  buildOutputDirectory: dirname(outfile),
263
- })
264
- );
300
+ routesOutputPath,
301
+ local: false,
302
+ d1Databases: Object.keys(
303
+ project.deployment_configs[isProduction ? "production" : "preview"]
304
+ .d1_databases ?? {}
305
+ ),
306
+ });
265
307
 
266
- builtFunctions = readFileSync(outfile, "utf-8");
308
+ builtFunctions = readFileSync(outfile, "utf-8");
309
+ filepathRoutingConfig = readFileSync(outputConfigPath, "utf-8");
310
+ } catch (e) {
311
+ if (e instanceof FunctionsNoRoutesError) {
312
+ logger.warn(
313
+ getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
314
+ );
315
+ } else {
316
+ throw e;
317
+ }
318
+ }
267
319
  }
268
320
 
269
- const manifest = await upload({ directory, accountId, projectName });
321
+ const manifest = await upload({
322
+ directory,
323
+ accountId,
324
+ projectName,
325
+ skipCaching: skipCaching ?? false,
326
+ });
270
327
 
271
328
  const formData = new FormData();
272
329
 
@@ -288,34 +345,100 @@ export const Handler = async ({
288
345
  formData.append("commit_dirty", commitDirty);
289
346
  }
290
347
 
291
- let _headers: string | undefined,
292
- _redirects: string | undefined,
293
- _workerJS: string | undefined;
294
-
295
- try {
296
- _headers = readFileSync(join(directory, "_headers"), "utf-8");
297
- } catch {}
298
-
299
- try {
300
- _redirects = readFileSync(join(directory, "_redirects"), "utf-8");
301
- } catch {}
302
-
303
- try {
304
- _workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
305
- } catch {}
306
-
307
348
  if (_headers) {
308
349
  formData.append("_headers", new File([_headers], "_headers"));
350
+ logger.log(`✨ Uploading _headers`);
309
351
  }
310
352
 
311
353
  if (_redirects) {
312
354
  formData.append("_redirects", new File([_redirects], "_redirects"));
355
+ logger.log(`✨ Uploading _redirects`);
313
356
  }
314
357
 
315
- if (builtFunctions) {
316
- formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
317
- } else if (_workerJS) {
358
+ if (filepathRoutingConfig) {
359
+ formData.append(
360
+ "functions-filepath-routing-config.json",
361
+ new File(
362
+ [filepathRoutingConfig],
363
+ "functions-filepath-routing-config.json"
364
+ )
365
+ );
366
+ }
367
+
368
+ /**
369
+ * Advanced Mode
370
+ * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
371
+ *
372
+ * When using a _worker.js file, the entire /functions directory is ignored
373
+ * – this includes its routing and middleware characteristics.
374
+ */
375
+ if (_workerJS) {
318
376
  formData.append("_worker.js", new File([_workerJS], "_worker.js"));
377
+ logger.log(`✨ Uploading _worker.js`);
378
+
379
+ if (_routesCustom) {
380
+ // user provided a custom _routes.json file
381
+ try {
382
+ const routesCustomJSON = JSON.parse(_routesCustom);
383
+ validateRoutes(routesCustomJSON, join(directory, "_routes.json"));
384
+
385
+ formData.append(
386
+ "_routes.json",
387
+ new File([_routesCustom], "_routes.json")
388
+ );
389
+ logger.log(`✨ Uploading _routes.json`);
390
+ logger.warn(
391
+ `_routes.json is an experimental feature and is subject to change. Please use with care.`
392
+ );
393
+ } catch (err) {
394
+ if (err instanceof FatalError) {
395
+ throw err;
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Pages Functions
403
+ * https://developers.cloudflare.com/pages/platform/functions/
404
+ */
405
+ if (builtFunctions && !_workerJS) {
406
+ // if Functions were build successfully, proceed to uploading the build file
407
+ formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
408
+ logger.log(`✨ Uploading Functions`);
409
+
410
+ if (_routesCustom) {
411
+ // user provided a custom _routes.json file
412
+ try {
413
+ const routesCustomJSON = JSON.parse(_routesCustom);
414
+ validateRoutes(routesCustomJSON, join(directory, "_routes.json"));
415
+
416
+ formData.append(
417
+ "_routes.json",
418
+ new File([_routesCustom], "_routes.json")
419
+ );
420
+ logger.log(`✨ Uploading _routes.json`);
421
+ logger.warn(
422
+ `_routes.json is an experimental feature and is subject to change. Please use with care.`
423
+ );
424
+ } catch (err) {
425
+ if (err instanceof FatalError) {
426
+ throw err;
427
+ }
428
+ }
429
+ } else if (routesOutputPath) {
430
+ // no custom _routes.json file found, so fallback to the generated one
431
+ try {
432
+ _routesGenerated = readFileSync(routesOutputPath, "utf-8");
433
+
434
+ if (_routesGenerated) {
435
+ formData.append(
436
+ "_routes.json",
437
+ new File([_routesGenerated], "_routes.json")
438
+ );
439
+ }
440
+ } catch {}
441
+ }
319
442
  }
320
443
 
321
444
  const deploymentResponse = await fetchResult<Deployment>(
@@ -334,5 +457,5 @@ export const Handler = async ({
334
457
  logger.log(
335
458
  `✨ Deployment complete! Take a peek over at ${deploymentResponse.url}`
336
459
  );
337
- await metrics.sendMetricsEvent("deploy pages project");
460
+ await metrics.sendMetricsEvent("create pages deployment");
338
461
  };
@@ -13,6 +13,7 @@ export type Project = {
13
13
  };
14
14
  export type Deployment = {
15
15
  id: string;
16
+ created_on: string;
16
17
  environment: string;
17
18
  deployment_trigger: {
18
19
  metadata: {
@@ -1,39 +1,34 @@
1
1
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
- import {
3
- basename,
4
- dirname,
5
- extname,
6
- join,
7
- relative,
8
- resolve,
9
- sep,
10
- } from "node:path";
11
- import { hash as blake3hash } from "blake3-wasm";
2
+ import { dirname, join, relative, resolve, sep } from "node:path";
12
3
  import { render, Text } from "ink";
13
4
  import Spinner from "ink-spinner";
14
5
  import { getType } from "mime";
6
+ import { Minimatch } from "minimatch";
15
7
  import PQueue from "p-queue";
16
8
  import prettyBytes from "pretty-bytes";
17
9
  import React from "react";
18
10
  import { fetchResult } from "../cfetch";
19
11
  import { FatalError } from "../errors";
12
+ import isInteractive from "../is-interactive";
20
13
  import { logger } from "../logger";
21
14
  import {
15
+ MAX_ASSET_COUNT,
16
+ MAX_ASSET_SIZE,
22
17
  BULK_UPLOAD_CONCURRENCY,
23
18
  MAX_BUCKET_FILE_COUNT,
24
19
  MAX_BUCKET_SIZE,
20
+ MAX_CHECK_MISSING_ATTEMPTS,
25
21
  MAX_UPLOAD_ATTEMPTS,
26
22
  } from "./constants";
23
+ import { hashFile } from "./hash";
27
24
  import { pagesBetaWarning } from "./utils";
25
+ import type { YargsOptionsToInterface } from "../yargs-types";
28
26
  import type { UploadPayloadFile } from "./types";
29
- import type { ArgumentsCamelCase, Argv } from "yargs";
27
+ import type { Argv } from "yargs";
30
28
 
31
- type UploadArgs = {
32
- directory: string;
33
- "output-manifest-path"?: string;
34
- };
29
+ type UploadArgs = YargsOptionsToInterface<typeof Options>;
35
30
 
36
- export function Options(yargs: Argv): Argv<UploadArgs> {
31
+ export function Options(yargs: Argv) {
37
32
  return yargs
38
33
  .positional("directory", {
39
34
  type: "string",
@@ -45,6 +40,10 @@ export function Options(yargs: Argv): Argv<UploadArgs> {
45
40
  type: "string",
46
41
  description: "The name of the project you want to deploy to",
47
42
  },
43
+ "skip-caching": {
44
+ type: "boolean",
45
+ description: "Skip asset caching which speeds up builds",
46
+ },
48
47
  })
49
48
  .epilogue(pagesBetaWarning);
50
49
  }
@@ -52,7 +51,8 @@ export function Options(yargs: Argv): Argv<UploadArgs> {
52
51
  export const Handler = async ({
53
52
  directory,
54
53
  outputManifestPath,
55
- }: ArgumentsCamelCase<UploadArgs>) => {
54
+ skipCaching,
55
+ }: UploadArgs) => {
56
56
  if (!directory) {
57
57
  throw new FatalError("Must specify a directory.", 1);
58
58
  }
@@ -64,6 +64,7 @@ export const Handler = async ({
64
64
  const manifest = await upload({
65
65
  directory,
66
66
  jwt: process.env.CF_PAGES_UPLOAD_JWT,
67
+ skipCaching: skipCaching ?? false,
67
68
  });
68
69
 
69
70
  if (outputManifestPath) {
@@ -79,8 +80,14 @@ export const upload = async (
79
80
  | {
80
81
  directory: string;
81
82
  jwt: string;
83
+ skipCaching: boolean;
84
+ }
85
+ | {
86
+ directory: string;
87
+ accountId: string;
88
+ projectName: string;
89
+ skipCaching: boolean;
82
90
  }
83
- | { directory: string; accountId: string; projectName: string }
84
91
  ) => {
85
92
  async function fetchJwt(): Promise<string> {
86
93
  if ("jwt" in args) {
@@ -105,10 +112,12 @@ export const upload = async (
105
112
  "_worker.js",
106
113
  "_redirects",
107
114
  "_headers",
108
- ".DS_Store",
109
- "node_modules",
110
- ".git",
111
- ];
115
+ "_routes.json",
116
+ "functions",
117
+ "**/.DS_Store",
118
+ "**/node_modules",
119
+ "**/.git",
120
+ ].map((pattern) => new Minimatch(pattern));
112
121
 
113
122
  const directory = resolve(args.directory);
114
123
 
@@ -130,10 +139,13 @@ export const upload = async (
130
139
  await Promise.all(
131
140
  files.map(async (file) => {
132
141
  const filepath = join(dir, file);
142
+ const relativeFilepath = relative(startingDir, filepath);
133
143
  const filestat = await stat(filepath);
134
144
 
135
- if (IGNORE_LIST.includes(file)) {
136
- return;
145
+ for (const minimatch of IGNORE_LIST) {
146
+ if (minimatch.match(relativeFilepath)) {
147
+ return;
148
+ }
137
149
  }
138
150
 
139
151
  if (filestat.isSymbolicLink()) {
@@ -143,17 +155,12 @@ export const upload = async (
143
155
  if (filestat.isDirectory()) {
144
156
  fileMap = await walk(filepath, fileMap, startingDir);
145
157
  } else {
146
- const name = relative(startingDir, filepath).split(sep).join("/");
147
-
148
- const fileContent = await readFile(filepath);
158
+ const name = relativeFilepath.split(sep).join("/");
149
159
 
150
- const base64Content = fileContent.toString("base64");
151
- const extension = extname(basename(name)).substring(1);
152
-
153
- if (filestat.size > 25 * 1024 * 1024) {
160
+ if (filestat.size > MAX_ASSET_SIZE) {
154
161
  throw new FatalError(
155
162
  `Error: Pages only supports files up to ${prettyBytes(
156
- 25 * 1024 * 1024
163
+ MAX_ASSET_SIZE
157
164
  )} in size\n${name} is ${prettyBytes(filestat.size)} in size`,
158
165
  1
159
166
  );
@@ -164,9 +171,7 @@ export const upload = async (
164
171
  path: filepath,
165
172
  contentType: getType(name) || "application/octet-stream",
166
173
  sizeInBytes: filestat.size,
167
- hash: blake3hash(base64Content + extension)
168
- .toString("hex")
169
- .slice(0, 32),
174
+ hash: hashFile(filepath),
170
175
  });
171
176
  }
172
177
  })
@@ -177,9 +182,9 @@ export const upload = async (
177
182
 
178
183
  const fileMap = await walk(directory);
179
184
 
180
- if (fileMap.size > 20000) {
185
+ if (fileMap.size > MAX_ASSET_COUNT) {
181
186
  throw new FatalError(
182
- `Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
187
+ `Error: Pages only supports up to ${MAX_ASSET_COUNT.toLocaleString()} files in a deployment. Ensure you have specified your build output directory correctly.`,
183
188
  1
184
189
  );
185
190
  }
@@ -190,19 +195,42 @@ export const upload = async (
190
195
 
191
196
  const start = Date.now();
192
197
 
193
- const missingHashes = await fetchResult<string[]>(
194
- `/pages/assets/check-missing`,
195
- {
196
- method: "POST",
197
- headers: {
198
- "Content-Type": "application/json",
199
- Authorization: `Bearer ${jwt}`,
200
- },
201
- body: JSON.stringify({
202
- hashes: files.map(({ hash }) => hash),
203
- }),
198
+ let attempts = 0;
199
+ const getMissingHashes = async (skipCaching: boolean): Promise<string[]> => {
200
+ if (skipCaching) {
201
+ console.debug("Force skipping cache");
202
+ return files.map(({ hash }) => hash);
204
203
  }
205
- );
204
+
205
+ try {
206
+ return await fetchResult<string[]>(`/pages/assets/check-missing`, {
207
+ method: "POST",
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ Authorization: `Bearer ${jwt}`,
211
+ },
212
+ body: JSON.stringify({
213
+ hashes: files.map(({ hash }) => hash),
214
+ }),
215
+ });
216
+ } catch (e) {
217
+ if (attempts < MAX_CHECK_MISSING_ATTEMPTS) {
218
+ // Exponential backoff, 1 second first time, then 2 second, then 4 second etc.
219
+ await new Promise((resolvePromise) =>
220
+ setTimeout(resolvePromise, Math.pow(2, attempts++) * 1000)
221
+ );
222
+
223
+ if ((e as { code: number }).code === 8000013) {
224
+ // Looks like the JWT expired, fetch another one
225
+ jwt = await fetchJwt();
226
+ }
227
+ return getMissingHashes(skipCaching);
228
+ } else {
229
+ throw e;
230
+ }
231
+ }
232
+ };
233
+ const missingHashes = await getMissingHashes(args.skipCaching);
206
234
 
207
235
  const sortedFiles = files
208
236
  .filter((file) => missingHashes.includes(file.hash))
@@ -256,7 +284,7 @@ export const upload = async (
256
284
  // Don't upload empty buckets (can happen for tiny projects)
257
285
  if (bucket.files.length === 0) continue;
258
286
 
259
- let attempts = 0;
287
+ attempts = 0;
260
288
  const doUpload = async (): Promise<void> => {
261
289
  // Populate the payload only when actually uploading (this is limited to 3 concurrent uploads at 50 MiB per bucket meaning we'd only load in a max of ~150 MiB)
262
290
  // This is so we don't run out of memory trying to upload the files.
@@ -272,7 +300,8 @@ export const upload = async (
272
300
  );
273
301
 
274
302
  try {
275
- return await fetchResult(`/pages/assets/upload`, {
303
+ console.debug("POST /pages/assets/upload");
304
+ const res = await fetchResult(`/pages/assets/upload`, {
276
305
  method: "POST",
277
306
  headers: {
278
307
  "Content-Type": "application/json",
@@ -280,25 +309,28 @@ export const upload = async (
280
309
  },
281
310
  body: JSON.stringify(payload),
282
311
  });
312
+ console.debug("result:", res);
283
313
  } catch (e) {
284
314
  if (attempts < MAX_UPLOAD_ATTEMPTS) {
315
+ console.debug("failed:", e, "retrying...");
285
316
  // Exponential backoff, 1 second first time, then 2 second, then 4 second etc.
286
317
  await new Promise((resolvePromise) =>
287
318
  setTimeout(resolvePromise, Math.pow(2, attempts++) * 1000)
288
319
  );
289
320
 
290
- if ((e as { code: number }).code === 8000013) {
321
+ if ((e as { code: number }).code === 8000013 || isJwtExpired(jwt)) {
291
322
  // Looks like the JWT expired, fetch another one
292
323
  jwt = await fetchJwt();
293
324
  }
294
325
  return doUpload();
295
326
  } else {
327
+ console.debug("failed:", e);
296
328
  throw e;
297
329
  }
298
330
  }
299
331
  };
300
332
 
301
- queue.add(() =>
333
+ void queue.add(() =>
302
334
  doUpload().then(
303
335
  () => {
304
336
  counter += bucket.files.length;
@@ -307,7 +339,9 @@ export const upload = async (
307
339
  (error) => {
308
340
  return Promise.reject(
309
341
  new FatalError(
310
- "Failed to upload files. Please try again.",
342
+ `Failed to upload files. Please try again. Error: ${JSON.stringify(
343
+ error
344
+ )})`,
311
345
  error.code || 1
312
346
  )
313
347
  );
@@ -346,7 +380,7 @@ export const upload = async (
346
380
  } catch (e) {
347
381
  await new Promise((resolvePromise) => setTimeout(resolvePromise, 1000));
348
382
 
349
- if ((e as { code: number }).code === 8000013) {
383
+ if ((e as { code: number }).code === 8000013 || isJwtExpired(jwt)) {
350
384
  // Looks like the JWT expired, fetch another one
351
385
  jwt = await fetchJwt();
352
386
  }
@@ -380,6 +414,23 @@ export const upload = async (
380
414
  );
381
415
  };
382
416
 
417
+ // Decode and check that the current JWT has not expired
418
+ function isJwtExpired(token: string): boolean | undefined {
419
+ try {
420
+ const decodedJwt = JSON.parse(
421
+ Buffer.from(token.split(".")[1], "base64").toString()
422
+ );
423
+
424
+ const dateNow = new Date().getTime() / 1000;
425
+
426
+ return decodedJwt.exp <= dateNow;
427
+ } catch (e) {
428
+ if (e instanceof Error) {
429
+ throw new Error(`Invalid token: ${e.message}`);
430
+ }
431
+ }
432
+ }
433
+
383
434
  function formatTime(duration: number) {
384
435
  return `(${(duration / 1000).toFixed(2)} sec)`;
385
436
  }
@@ -388,7 +439,7 @@ function Progress({ done, total }: { done: number; total: number }) {
388
439
  return (
389
440
  <>
390
441
  <Text>
391
- <Spinner type="earth" />
442
+ {isInteractive() ? <Spinner type="earth" /> : null}
392
443
  {` Uploading... (${done}/${total})\n`}
393
444
  </Text>
394
445
  </>
@@ -1,6 +1,6 @@
1
- import type { BuildResult } from "esbuild";
1
+ import type { BundleResult } from "../bundle";
2
2
 
3
- export const RUNNING_BUILDERS: BuildResult[] = [];
3
+ export const RUNNING_BUILDERS: BundleResult[] = [];
4
4
 
5
5
  export const CLEANUP_CALLBACKS: (() => void)[] = [];
6
6
  export const CLEANUP = () => {
@@ -10,3 +10,14 @@ export const CLEANUP = () => {
10
10
 
11
11
  export const pagesBetaWarning =
12
12
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
13
+
14
+ export function isUrl(maybeUrl?: string): maybeUrl is string {
15
+ if (!maybeUrl) return false;
16
+
17
+ try {
18
+ new URL(maybeUrl);
19
+ return true;
20
+ } catch (e) {
21
+ return false;
22
+ }
23
+ }