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
@@ -2,28 +2,39 @@ import assert from "node:assert";
2
2
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { URLSearchParams } from "node:url";
5
+ import chalk from "chalk";
5
6
  import tmp from "tmp-promise";
6
- import { bundleWorker } from "./bundle";
7
- import { printBundleSize } from "./bundle-reporter";
8
- import { fetchListResult, fetchResult } from "./cfetch";
9
- import { printBindings } from "./config";
10
- import { createWorkerUploadForm } from "./create-worker-upload-form";
11
- import { confirm } from "./dialogs";
12
- import { getMigrationsToUpload } from "./durable";
13
- import { logger } from "./logger";
14
- import { ParseError } from "./parse";
15
- import { syncAssets } from "./sites";
16
- import { getZoneForRoute } from "./zones";
17
- import type { Config } from "./config";
7
+ import { bundleWorker } from "../bundle";
8
+ import {
9
+ printBundleSize,
10
+ printOffendingDependencies,
11
+ } from "../bundle-reporter";
12
+ import { fetchListResult, fetchResult } from "../cfetch";
13
+ import { printBindings } from "../config";
14
+ import { createWorkerUploadForm } from "../create-worker-upload-form";
15
+ import { confirm, fromDashMessagePrompt } from "../dialogs";
16
+ import { getMigrationsToUpload } from "../durable";
17
+ import { logger } from "../logger";
18
+ import { getMetricsUsageHeaders } from "../metrics";
19
+ import { ParseError } from "../parse";
20
+ import { getQueue, putConsumer } from "../queues/client";
21
+ import { getWorkersDevSubdomain } from "../routes";
22
+ import { syncAssets } from "../sites";
23
+ import { identifyD1BindingsAsBeta } from "../worker";
24
+ import { getZoneForRoute } from "../zones";
25
+ import type { FetchError } from "../cfetch";
26
+ import type { Config } from "../config";
18
27
  import type {
19
28
  Route,
20
29
  ZoneIdRoute,
21
30
  ZoneNameRoute,
22
31
  CustomDomainRoute,
23
- } from "./config/environment";
24
- import type { Entry } from "./entry";
25
- import type { AssetPaths } from "./sites";
26
- import type { CfWorkerInit } from "./worker";
32
+ } from "../config/environment";
33
+ import type { DeploymentListRes } from "../deployments";
34
+ import type { Entry } from "../entry";
35
+ import type { PutConsumerBody } from "../queues/client";
36
+ import type { AssetPaths } from "../sites";
37
+ import type { CfWorkerInit } from "../worker";
27
38
 
28
39
  type Props = {
29
40
  config: Config;
@@ -35,6 +46,8 @@ type Props = {
35
46
  compatibilityDate: string | undefined;
36
47
  compatibilityFlags: string[] | undefined;
37
48
  assetPaths: AssetPaths | undefined;
49
+ vars: Record<string, string> | undefined;
50
+ defines: Record<string, string> | undefined;
38
51
  triggers: string[] | undefined;
39
52
  routes: string[] | undefined;
40
53
  legacyEnv: boolean | undefined;
@@ -47,14 +60,64 @@ type Props = {
47
60
  outDir: string | undefined;
48
61
  dryRun: boolean | undefined;
49
62
  noBundle: boolean | undefined;
63
+ keepVars: boolean | undefined;
64
+ logpush: boolean | undefined;
50
65
  };
51
66
 
52
67
  type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
53
68
 
69
+ export type CustomDomain = {
70
+ id: string;
71
+ zone_id: string;
72
+ zone_name: string;
73
+ hostname: string;
74
+ service: string;
75
+ environment: string;
76
+ };
77
+ type UpdatedCustomDomain = CustomDomain & { modified: boolean };
78
+ type ConflictingCustomDomain = CustomDomain & {
79
+ external_dns_record_id?: string;
80
+ external_cert_id?: string;
81
+ };
82
+
83
+ export type CustomDomainChangeset = {
84
+ added: CustomDomain[];
85
+ removed: CustomDomain[];
86
+ updated: UpdatedCustomDomain[];
87
+ conflicting: ConflictingCustomDomain[];
88
+ };
89
+
54
90
  function sleep(ms: number) {
55
91
  return new Promise((resolve) => setTimeout(resolve, ms));
56
92
  }
57
93
 
94
+ const scriptStartupErrorRegex = /startup/i;
95
+
96
+ function errIsScriptSizeOrStartupErr(err: unknown) {
97
+ if (!err) return false;
98
+
99
+ // 10027 = workers.api.error.script_too_large
100
+ if ((err as { code: number }).code === 10027) {
101
+ return true;
102
+ }
103
+
104
+ // 10021 = validation error
105
+ // no explicit error code for more granular errors than "invalid script"
106
+ // but the error will contain a string error message directly from the
107
+ // validator.
108
+ // the error always SHOULD look like "Script startup exceeded CPU limit."
109
+ // (or the less likely "Script startup exceeded memory limits.")
110
+ if (
111
+ (err as { code: number }).code === 10021 &&
112
+ err instanceof ParseError &&
113
+ scriptStartupErrorRegex.test(err.notes[0]?.text)
114
+ ) {
115
+ return true;
116
+ }
117
+
118
+ return false;
119
+ }
120
+
58
121
  function renderRoute(route: Route): string {
59
122
  let result = "";
60
123
  if (typeof route === "string") {
@@ -79,58 +142,27 @@ function renderRoute(route: Route): string {
79
142
  return result;
80
143
  }
81
144
 
82
- // this function takes a string with quotes in it
83
- // (i.e. `hello "world", if that really is your name`)
84
- // and peels out the first instance of a substring
85
- // bounded by quotes (so, in the example above, `world`)
86
- //
87
- // this is useful because the /domains api will return
88
- // which domains conflicted in an error message, bounded
89
- // by a string, which we can use to provide helpful
90
- // messages to a user
91
- function getQuoteBoundedSubstring(content: string) {
92
- const matches = content.split('"');
93
- return matches[1] ?? "";
94
- }
95
-
96
- function isOriginConflictError(
97
- e: unknown
98
- ): e is { code: 100116; message: string; notes: Array<{ text: string }> } {
99
- return (
100
- typeof e === "object" &&
101
- e !== null &&
102
- (e as { code: number }).code === 100116
103
- );
104
- }
105
-
106
- function isDNSConflictError(
107
- e: unknown
108
- ): e is { code: 100117; message: string; notes: Array<{ text: string }> } {
109
- return (
110
- typeof e === "object" &&
111
- e !== null &&
112
- (e as { code: number }).code === 100117
113
- );
114
- }
115
-
116
- // empty error class to throw and then explicitly catch via `instanceof`
117
- class CustomDomainOverrideRejected extends Error {}
118
-
119
145
  // publishing to custom domains involves a few more steps than just updating
120
146
  // the routing table, and thus the api implementing it is fairly defensive -
121
147
  // it will error eagerly on conflicts against existing domains or existing
122
148
  // managed DNS records
123
- //
124
- // however, you can pass params to override the errors. we start on the
125
- // defensive path, and if one of these errors occur, we prompt the user
126
- // for confirmation that they do indeed want to override the conflicts, and
127
- // then retry the request with the right override added
149
+
150
+ // however, you can pass params to override the errors. to know if we should
151
+ // override the current state, we generate a "changeset" of required actions
152
+ // to get to the state we want (specified by the list of custom domains). the
153
+ // changeset returns an "updated" collection (existing custom domains
154
+ // connected to other scripts) and a "conflicting" collection (the requested
155
+ // custom domains that have a managed, conflicting DNS record preventing the
156
+ // host's use as a custom domain). with this information, we can prompt to
157
+ // the user what will occur if we create the custom domains requested, and
158
+ // add the override param if they confirm the action
128
159
  //
129
160
  // if a user does not confirm that they want to override, we skip publishing
130
161
  // to these custom domains, but continue on through the rest of the
131
162
  // publish stage
132
- function publishCustomDomains(
163
+ async function publishCustomDomains(
133
164
  workerUrl: string,
165
+ accountId: string,
134
166
  domains: Array<RouteObject>
135
167
  ): Promise<string[]> {
136
168
  const config = {
@@ -146,88 +178,117 @@ function publishCustomDomains(
146
178
  };
147
179
  });
148
180
 
181
+ const fail = () => {
182
+ return [
183
+ domains.length > 1
184
+ ? `Publishing to ${domains.length} Custom Domains was skipped, fix conflicts and try again`
185
+ : `Publishing to Custom Domain "${domains[0].pattern}" was skipped, fix conflict and try again`,
186
+ ];
187
+ };
188
+
149
189
  if (!process.stdout.isTTY) {
150
190
  // running in non-interactive mode.
151
191
  // existing origins / dns records are not indicative of errors,
152
192
  // so we aggressively update rather than aggressively fail
153
193
  config.override_existing_origin = true;
154
194
  config.override_existing_dns_record = true;
195
+ } else {
196
+ // get a changeset for operations required to achieve a state with the requested domains
197
+ const changeset = await fetchResult<CustomDomainChangeset>(
198
+ `${workerUrl}/domains/changeset?replace_state=true`,
199
+ {
200
+ method: "POST",
201
+ body: JSON.stringify(origins),
202
+ headers: {
203
+ "Content-Type": "application/json",
204
+ },
205
+ }
206
+ );
207
+
208
+ const updatesRequired = changeset.updated.filter(
209
+ (domain) => domain.modified
210
+ );
211
+ if (updatesRequired.length > 0) {
212
+ // find out which scripts the conflict domains are already attached to
213
+ // so we can provide that in the confirmation prompt
214
+ const existing = await Promise.all(
215
+ updatesRequired.map((domain) =>
216
+ fetchResult<CustomDomain>(
217
+ `/accounts/${accountId}/workers/domains/records/${domain.id}`
218
+ )
219
+ )
220
+ );
221
+ const existingRendered = existing
222
+ .map(
223
+ (domain) =>
224
+ `\t• ${domain.hostname} (used as a domain for "${domain.service}")`
225
+ )
226
+ .join("\n");
227
+ const message = `Custom Domains already exist for these domains:
228
+ ${existingRendered}
229
+ Update them to point to this script instead?`;
230
+ if (!(await confirm(message))) return fail();
231
+ config.override_existing_origin = true;
232
+ }
233
+
234
+ if (changeset.conflicting.length > 0) {
235
+ const conflicitingRendered = changeset.conflicting
236
+ .map((domain) => `\t• ${domain.hostname}`)
237
+ .join("\n");
238
+ const message = `You already have DNS records that conflict for these Custom Domains:
239
+ ${conflicitingRendered}
240
+ Update them to point to this script instead?`;
241
+ if (!(await confirm(message))) return fail();
242
+ config.override_existing_dns_record = true;
243
+ }
155
244
  }
156
245
 
157
- // Mixing promise chains with async/await is funky, but it allows us to keep related
158
- // logic synchronous-looking (i.e. prompting for confirmation and then re-requesting)
159
- // while retaining the flexibility of promise chain fall-throughs. We can group error
160
- // handling logic in dedicated catch calls, and all we have to do is re-throw an
161
- // error and it will pass down to the next catch call
162
- return fetchResult(`${workerUrl}/domains`, {
246
+ // publish to domains
247
+ await fetchResult(`${workerUrl}/domains/records`, {
163
248
  method: "PUT",
164
249
  body: JSON.stringify({ ...config, origins }),
165
250
  headers: {
166
251
  "Content-Type": "application/json",
167
252
  },
168
- })
169
- .catch(async (err) => {
170
- if (isOriginConflictError(err)) {
171
- const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
172
- const shouldContinue = await confirm(
173
- `Custom Domains already exist for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
174
- );
175
- if (!shouldContinue) {
176
- throw new CustomDomainOverrideRejected();
177
- }
178
- config.override_existing_origin = true;
179
- await fetchResult(`${workerUrl}/domains`, {
180
- method: "PUT",
181
- body: JSON.stringify({ ...config, origins }),
182
- headers: {
183
- "Content-Type": "application/json",
184
- },
185
- });
186
- } else {
187
- throw err;
188
- }
189
- })
190
- .catch(async (err) => {
191
- if (isDNSConflictError(err)) {
192
- const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
193
- const shouldContinue = await confirm(
194
- `You already have conflicting DNS records for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
195
- );
196
- if (!shouldContinue) {
197
- throw new CustomDomainOverrideRejected();
198
- }
199
- config.override_existing_dns_record = true;
200
- await fetchResult(`${workerUrl}/domains`, {
201
- method: "PUT",
202
- body: JSON.stringify({ ...config, origins }),
203
- headers: {
204
- "Content-Type": "application/json",
205
- },
206
- });
207
- } else {
208
- throw err;
209
- }
210
- })
211
- .then(() => domains.map((domain) => renderRoute(domain)))
212
- .catch((err) => {
213
- if (err instanceof CustomDomainOverrideRejected) {
214
- return [
215
- domains.length > 1
216
- ? `Publishing to ${domains.length} Custom Domains was skipped, fix conflicts and try again`
217
- : `Publishing to Custom Domain "${domains[0].pattern}" was skipped, fix conflict and try again`,
218
- ];
219
- }
220
- throw err;
221
- });
253
+ });
254
+
255
+ return domains.map((domain) => renderRoute(domain));
222
256
  }
223
257
 
224
258
  export default async function publish(props: Props): Promise<void> {
225
259
  // TODO: warn if git/hg has uncommitted changes
226
- const { config, accountId } = props;
260
+ const { config, accountId, name } = props;
261
+ if (accountId && name) {
262
+ try {
263
+ const serviceMetaData = await fetchResult(
264
+ `/accounts/${accountId}/workers/services/${name}`
265
+ );
266
+ const { default_environment } = serviceMetaData as {
267
+ default_environment: {
268
+ script: { last_deployed_from: "dash" | "wrangler" | "api" };
269
+ };
270
+ };
271
+
272
+ if (
273
+ (await fromDashMessagePrompt(
274
+ default_environment.script.last_deployed_from
275
+ )) === false
276
+ )
277
+ return;
278
+ } catch (e) {
279
+ // code: 10090, message: workers.api.error.service_not_found
280
+ // is thrown from the above fetchResult on the first publish of a Worker
281
+ if ((e as { code?: number }).code !== 10090) {
282
+ logger.error(e);
283
+ }
284
+ }
285
+ }
227
286
 
228
287
  if (!(props.compatibilityDate || config.compatibility_date)) {
229
288
  const compatibilityDateStr = `${new Date().getFullYear()}-${(
230
- new Date().getMonth() + ""
289
+ new Date().getMonth() +
290
+ 1 +
291
+ ""
231
292
  ).padStart(2, "0")}-${(new Date().getDate() + "").padStart(2, "0")}`;
232
293
 
233
294
  throw new Error(`A compatibility_date is required when publishing. Add the following to your wrangler.toml file:.
@@ -266,6 +327,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
266
327
 
267
328
  const jsxFactory = props.jsxFactory || config.jsx_factory;
268
329
  const jsxFragment = props.jsxFragment || config.jsx_fragment;
330
+ const keepVars = props.keepVars ?? config.keep_vars;
269
331
 
270
332
  const minify = props.minify ?? config.minify;
271
333
 
@@ -322,7 +384,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
322
384
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
323
385
  : `/accounts/${accountId}/workers/scripts/${scriptName}`;
324
386
 
325
- let available_on_subdomain; // we'll set this later
387
+ let available_on_subdomain: boolean | undefined = undefined; // we'll set this later
388
+ let scriptTag: string | null = null;
326
389
 
327
390
  const { format } = props.entry;
328
391
 
@@ -365,14 +428,29 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
365
428
  );
366
429
  }
367
430
 
431
+ // If we are using d1 bindings, and are not bundling the worker
432
+ // we should error here as the d1 shim won't be added
433
+ const betaD1Shims = identifyD1BindingsAsBeta(config.d1_databases);
434
+ if (
435
+ Array.isArray(betaD1Shims) &&
436
+ betaD1Shims.length > 0 &&
437
+ props.noBundle
438
+ ) {
439
+ throw new Error(
440
+ "While in beta, you cannot use D1 bindings without bundling your worker. Please remove `no_bundle` from your wrangler.toml file or remove the `--no-bundle` flag to access D1 bindings."
441
+ );
442
+ }
443
+
368
444
  const {
369
445
  modules,
446
+ dependencies,
370
447
  resolvedEntryPointPath,
371
448
  bundleType,
372
449
  }: Awaited<ReturnType<typeof bundleWorker>> = props.noBundle
373
450
  ? // we can skip the whole bundling step and mock a bundle here
374
451
  {
375
452
  modules: [],
453
+ dependencies: {},
376
454
  resolvedEntryPointPath: props.entry.file,
377
455
  bundleType: props.entry.format === "modules" ? "esm" : "commonjs",
378
456
  stop: undefined,
@@ -383,14 +461,33 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
383
461
  {
384
462
  serveAssetsFromWorker:
385
463
  !props.isWorkersSite && Boolean(props.assetPaths),
464
+ betaD1Shims: identifyD1BindingsAsBeta(config.d1_databases)?.map(
465
+ (db) => db.binding
466
+ ),
386
467
  jsxFactory,
387
468
  jsxFragment,
388
469
  rules: props.rules,
389
470
  tsconfig: props.tsconfig ?? config.tsconfig,
390
471
  minify,
391
472
  nodeCompat,
392
- define: config.define,
473
+ define: { ...config.define, ...props.defines },
393
474
  checkFetch: false,
475
+ assets: config.assets && {
476
+ ...config.assets,
477
+ // enable the cache when publishing
478
+ bypassCache: false,
479
+ },
480
+ services: config.services,
481
+ // We don't set workerDefinitions here,
482
+ // because we don't want to apply the dev-time
483
+ // facades on top of it
484
+ workerDefinitions: undefined,
485
+ firstPartyWorkerDevFacade: false,
486
+ // We want to know if the build is for development or publishing
487
+ // This could potentially cause issues as we no longer have identical behaviour between dev and publish?
488
+ targetConsumer: "publish",
489
+ local: false,
490
+ experimentalLocal: false,
394
491
  }
395
492
  );
396
493
 
@@ -426,7 +523,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
426
523
  ? { binding: "__STATIC_CONTENT", id: assets.namespace }
427
524
  : []
428
525
  ),
429
- vars: config.vars,
526
+ vars: { ...config.vars, ...props.vars },
430
527
  wasm_modules: config.wasm_modules,
431
528
  text_blobs: {
432
529
  ...config.text_blobs,
@@ -437,9 +534,15 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
437
534
  },
438
535
  data_blobs: config.data_blobs,
439
536
  durable_objects: config.durable_objects,
537
+ queues: config.queues.producers?.map((producer) => {
538
+ return { binding: producer.binding, queue_name: producer.queue };
539
+ }),
440
540
  r2_buckets: config.r2_buckets,
541
+ d1_databases: identifyD1BindingsAsBeta(config.d1_databases),
441
542
  services: config.services,
442
- worker_namespaces: config.worker_namespaces,
543
+ analytics_engine_datasets: config.analytics_engine_datasets,
544
+ dispatch_namespaces: config.dispatch_namespaces,
545
+ logfwdr: config.logfwdr,
443
546
  unsafe: config.unsafe?.bindings,
444
547
  };
445
548
 
@@ -465,51 +568,83 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
465
568
  compatibility_flags:
466
569
  props.compatibilityFlags ?? config.compatibility_flags,
467
570
  usage_model: config.usage_model,
571
+ keepVars,
572
+ logpush: props.logpush !== undefined ? props.logpush : config.logpush,
468
573
  };
469
574
 
470
- void printBundleSize(
575
+ // As this is not deterministic for testing, we detect if in a jest environment and run asynchronously
576
+ // We do not care about the timing outside of testing
577
+ const bundleSizePromise = printBundleSize(
471
578
  { name: path.basename(resolvedEntryPointPath), content: content },
472
579
  modules
473
580
  );
581
+ if (process.env.JEST_WORKER_ID !== undefined) await bundleSizePromise;
582
+ else void bundleSizePromise;
474
583
 
475
584
  const withoutStaticAssets = {
476
585
  ...bindings,
477
586
  kv_namespaces: config.kv_namespaces,
478
587
  text_blobs: config.text_blobs,
479
588
  };
480
- printBindings(withoutStaticAssets);
589
+
590
+ // mask anything that was overridden in cli args
591
+ // so that we don't log potential secrets into the terminal
592
+ const maskedVars = { ...withoutStaticAssets.vars };
593
+ for (const key of Object.keys(maskedVars)) {
594
+ if (maskedVars[key] !== config.vars[key]) {
595
+ // This means it was overridden in cli args
596
+ // so let's mask it
597
+ maskedVars[key] = "(hidden)";
598
+ }
599
+ }
600
+
601
+ printBindings({ ...withoutStaticAssets, vars: maskedVars });
602
+
603
+ await ensureQueuesExist(config);
481
604
 
482
605
  if (!props.dryRun) {
483
606
  // Upload the script so it has time to propagate.
484
607
  // We can also now tell whether available_on_subdomain is set
485
- const result = await fetchResult<{
486
- available_on_subdomain: boolean;
487
- id: string | null;
488
- etag: string | null;
489
- pipeline_hash: string | null;
490
- }>(
491
- workerUrl,
492
- {
493
- method: "PUT",
494
- body: createWorkerUploadForm(worker),
495
- },
496
- new URLSearchParams({
497
- include_subdomain_availability: "true",
498
- // pass excludeScript so the whole body of the
499
- // script doesn't get included in the response
500
- excludeScript: "true",
501
- })
502
- );
503
-
504
- available_on_subdomain = result.available_on_subdomain;
608
+ try {
609
+ const result = await fetchResult<{
610
+ available_on_subdomain: boolean;
611
+ id: string | null;
612
+ etag: string | null;
613
+ pipeline_hash: string | null;
614
+ tag: string | null;
615
+ }>(
616
+ workerUrl,
617
+ {
618
+ method: "PUT",
619
+ body: createWorkerUploadForm(worker),
620
+ headers: await getMetricsUsageHeaders(config.send_metrics),
621
+ },
622
+ new URLSearchParams({
623
+ include_subdomain_availability: "true",
624
+ // pass excludeScript so the whole body of the
625
+ // script doesn't get included in the response
626
+ excludeScript: "true",
627
+ })
628
+ );
505
629
 
506
- // Print some useful information returned after publishing
507
- // Not all fields will be populated for every worker
508
- // These fields are likely to be scraped by tools, so do not rename
509
- if (result.id) logger.log("Worker ID: ", result.id);
510
- if (result.etag) logger.log("Worker ETag: ", result.etag);
511
- if (result.pipeline_hash)
512
- logger.log("Worker PipelineHash: ", result.pipeline_hash);
630
+ available_on_subdomain = result.available_on_subdomain;
631
+ scriptTag = result.tag;
632
+
633
+ if (config.first_party_worker) {
634
+ // Print some useful information returned after publishing
635
+ // Not all fields will be populated for every worker
636
+ // These fields are likely to be scraped by tools, so do not rename
637
+ if (result.id) logger.log("Worker ID: ", result.id);
638
+ if (result.etag) logger.log("Worker ETag: ", result.etag);
639
+ if (result.pipeline_hash)
640
+ logger.log("Worker PipelineHash: ", result.pipeline_hash);
641
+ }
642
+ } catch (err) {
643
+ if (errIsScriptSizeOrStartupErr(err)) {
644
+ printOffendingDependencies(dependencies);
645
+ }
646
+ throw err;
647
+ }
513
648
  }
514
649
  } finally {
515
650
  if (typeof destination !== "string") {
@@ -530,7 +665,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
530
665
 
531
666
  if (deployToWorkersDev) {
532
667
  // Deploy to a subdomain of `workers.dev`
533
- const userSubdomain = await getSubdomain(accountId);
668
+ const userSubdomain = await getWorkersDevSubdomain(accountId);
534
669
  const scriptURL =
535
670
  props.legacyEnv || !props.env
536
671
  ? `${scriptName}.${userSubdomain}.workers.dev`
@@ -568,6 +703,61 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
568
703
  "Content-Type": "application/json",
569
704
  },
570
705
  });
706
+ } else if (routes.length !== 0) {
707
+ // if you get to this point it's because
708
+ // you're trying to deploy a worker to a custom
709
+ // domain that's already bound to another worker.
710
+ // so this thing is about finding workers that have
711
+ // bindings to the routes you're trying to deploy to.
712
+ //
713
+ // the logic is kinda similar (read: duplicated) from publishRoutesFallback,
714
+ // except here we know we have a good API token or whatever so we don't need
715
+ // to bother with all the error handling tomfoolery.
716
+ const routesWithOtherBindings: Record<string, string[]> = {};
717
+ for (const route of routes) {
718
+ const zone = await getZoneForRoute(route);
719
+ if (!zone) {
720
+ continue;
721
+ }
722
+
723
+ const routePattern = typeof route === "string" ? route : route.pattern;
724
+ const routesInZone = await fetchListResult<{
725
+ pattern: string;
726
+ script: string;
727
+ }>(`/zones/${zone.id}/workers/routes`);
728
+
729
+ routesInZone.forEach(({ script, pattern }) => {
730
+ if (pattern === routePattern && script !== scriptName) {
731
+ if (!(script in routesWithOtherBindings)) {
732
+ routesWithOtherBindings[script] = [];
733
+ }
734
+
735
+ routesWithOtherBindings[script].push(pattern);
736
+ }
737
+ });
738
+ }
739
+
740
+ if (Object.keys(routesWithOtherBindings).length > 0) {
741
+ let errorMessage =
742
+ "Can't publish a worker to routes that are assigned to another worker.\n";
743
+
744
+ for (const worker in routesWithOtherBindings) {
745
+ const assignedRoutes = routesWithOtherBindings[worker];
746
+ errorMessage += `"${worker}" is already assigned to routes:\n${assignedRoutes.map(
747
+ (r) => ` - ${chalk.underline(r)}\n`
748
+ )}`;
749
+ }
750
+
751
+ const resolution =
752
+ "Unassign other workers from the routes you want to publish to, and then try again.";
753
+ const dashLink = `Visit ${chalk.blue(
754
+ chalk.underline(
755
+ `https://dash.cloudflare.com/${accountId}/workers/overview`
756
+ )
757
+ )} to unassign a worker from a route.`;
758
+
759
+ throw new Error(`${errorMessage}\n${resolution}\n${dashLink}`);
760
+ }
571
761
  }
572
762
  }
573
763
 
@@ -590,7 +780,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
590
780
 
591
781
  // Update custom domains for the script
592
782
  if (customDomainsOnly.length > 0) {
593
- deployments.push(publishCustomDomains(workerUrl, customDomainsOnly));
783
+ deployments.push(
784
+ publishCustomDomains(workerUrl, accountId, customDomainsOnly)
785
+ );
594
786
  }
595
787
 
596
788
  // Configure any schedules for the script.
@@ -608,48 +800,45 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
608
800
  );
609
801
  }
610
802
 
803
+ if (config.queues.consumers && config.queues.consumers.length) {
804
+ deployments.push(...updateQueueConsumers(config));
805
+ }
806
+
611
807
  const targets = await Promise.all(deployments);
612
808
  const deployMs = Date.now() - start - uploadMs;
613
809
 
614
810
  if (deployments.length > 0) {
615
811
  logger.log("Published", workerName, formatTime(deployMs));
616
812
  for (const target of targets.flat()) {
617
- logger.log(" ", target);
813
+ // Append protocol only on workers.dev domains
814
+ logger.log(
815
+ " ",
816
+ (target.endsWith("workers.dev") ? "https://" : "") + target
817
+ );
618
818
  }
619
819
  } else {
620
820
  logger.log("No publish targets for", workerName, formatTime(deployMs));
621
821
  }
622
- }
623
-
624
- function formatTime(duration: number) {
625
- return `(${(duration / 1000).toFixed(2)} sec)`;
626
- }
627
822
 
628
- async function getSubdomain(accountId: string): Promise<string> {
629
823
  try {
630
- const { subdomain } = await fetchResult(
631
- `/accounts/${accountId}/workers/subdomain`
824
+ const deploymentsList = await fetchResult<DeploymentListRes>(
825
+ `/accounts/${accountId}/workers/deployments/by-script/${scriptTag}`
632
826
  );
633
- return subdomain;
827
+
828
+ logger.log("Current Deployment ID:", deploymentsList.latest.id);
634
829
  } catch (e) {
635
- const error = e as { code?: number };
636
- if (typeof error === "object" && !!error && error.code === 10007) {
637
- // 10007 error code: not found
638
- // https://api.cloudflare.com/#worker-subdomain-get-subdomain
639
-
640
- const errorMessage =
641
- "Error: You need to register a workers.dev subdomain before publishing to workers.dev";
642
- const solutionMessage =
643
- "You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
644
- const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
645
-
646
- throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
830
+ if ((e as { code: number }).code === 10023) {
831
+ // TODO: remove this try/catch once deployments is completely rolled out
647
832
  } else {
648
833
  throw e;
649
834
  }
650
835
  }
651
836
  }
652
837
 
838
+ function formatTime(duration: number) {
839
+ return `(${(duration / 1000).toFixed(2)} sec)`;
840
+ }
841
+
653
842
  /**
654
843
  * Associate the newly deployed Worker with the given routes.
655
844
  */
@@ -762,16 +951,19 @@ async function publishRoutesFallback(
762
951
  }
763
952
  }
764
953
 
765
- const { pattern } = await fetchResult(`/zones/${zoneId}/workers/routes`, {
766
- method: "POST",
767
- body: JSON.stringify({
768
- pattern: routePattern,
769
- script: scriptName,
770
- }),
771
- headers: {
772
- "Content-Type": "application/json",
773
- },
774
- });
954
+ const { pattern } = await fetchResult<{ pattern: string }>(
955
+ `/zones/${zoneId}/workers/routes`,
956
+ {
957
+ method: "POST",
958
+ body: JSON.stringify({
959
+ pattern: routePattern,
960
+ script: scriptName,
961
+ }),
962
+ headers: {
963
+ "Content-Type": "application/json",
964
+ },
965
+ }
966
+ );
775
967
 
776
968
  deployedRoutes.push(pattern);
777
969
  }
@@ -791,3 +983,54 @@ async function publishRoutesFallback(
791
983
  function isAuthenticationError(e: unknown): e is ParseError {
792
984
  return e instanceof ParseError && (e as { code?: number }).code === 10000;
793
985
  }
986
+
987
+ async function ensureQueuesExist(config: Config) {
988
+ const producers = (config.queues.producers || []).map(
989
+ (producer) => producer.queue
990
+ );
991
+ const consumers = (config.queues.consumers || []).map(
992
+ (consumer) => consumer.queue
993
+ );
994
+
995
+ const queueNames = producers.concat(consumers);
996
+ for (const queue of queueNames) {
997
+ try {
998
+ await getQueue(config, queue);
999
+ } catch (err) {
1000
+ const queueErr = err as FetchError;
1001
+ if (queueErr.code === 11000) {
1002
+ // queue_not_found
1003
+ throw new Error(
1004
+ `Queue "${queue}" does not exist. To create it, run: wrangler queues create ${queue}`
1005
+ );
1006
+ }
1007
+ throw err;
1008
+ }
1009
+ }
1010
+ }
1011
+
1012
+ function updateQueueConsumers(config: Config): Promise<string[]>[] {
1013
+ const consumers = config.queues.consumers || [];
1014
+ return consumers.map((consumer) => {
1015
+ const body: PutConsumerBody = {
1016
+ dead_letter_queue: consumer.dead_letter_queue,
1017
+ settings: {
1018
+ batch_size: consumer.max_batch_size,
1019
+ max_retries: consumer.max_retries,
1020
+ max_wait_time_ms: consumer.max_batch_timeout
1021
+ ? 1000 * consumer.max_batch_timeout
1022
+ : undefined,
1023
+ },
1024
+ };
1025
+
1026
+ if (config.name === undefined) {
1027
+ // TODO: how can we reliably get the current script name?
1028
+ throw new Error("Script name is required to update queue consumers");
1029
+ }
1030
+ const scriptName = config.name;
1031
+ const envName = undefined; // TODO: script environment for wrangler publish?
1032
+ return putConsumer(config, consumer.queue, scriptName, envName, body).then(
1033
+ () => [`Consumer for ${consumer.queue}`]
1034
+ );
1035
+ });
1036
+ }