wrangler 0.0.0-e64812e → 0.0.0-e8e684c7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +27 -3
  2. package/bin/wrangler.js +150 -62
  3. package/kv-asset-handler.js +1 -0
  4. package/miniflare-dist/index.mjs +663 -7
  5. package/package.json +173 -152
  6. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  7. package/src/__tests__/config-cache.test.ts +30 -24
  8. package/src/__tests__/configuration.test.ts +4015 -3363
  9. package/src/__tests__/dev.test.tsx +1290 -952
  10. package/src/__tests__/generate.test.ts +91 -0
  11. package/src/__tests__/guess-worker-format.test.ts +68 -68
  12. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  13. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  14. package/src/__tests__/helpers/mock-bin.ts +20 -20
  15. package/src/__tests__/helpers/mock-cfetch.ts +179 -94
  16. package/src/__tests__/helpers/mock-console.ts +49 -39
  17. package/src/__tests__/helpers/mock-dialogs.ts +94 -71
  18. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +30 -30
  20. package/src/__tests__/helpers/mock-istty.ts +65 -18
  21. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  22. package/src/__tests__/helpers/mock-kv.ts +26 -26
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
  24. package/src/__tests__/helpers/mock-process.ts +39 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  26. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  27. package/src/__tests__/helpers/run-in-tmp.ts +27 -28
  28. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  29. package/src/__tests__/helpers/write-worker-source.ts +16 -16
  30. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  31. package/src/__tests__/https-options.test.ts +142 -109
  32. package/src/__tests__/index.test.ts +222 -233
  33. package/src/__tests__/init.test.ts +2139 -1603
  34. package/src/__tests__/jest.setup.ts +114 -34
  35. package/src/__tests__/kv.test.ts +1222 -1032
  36. package/src/__tests__/logger.test.ts +100 -74
  37. package/src/__tests__/metrics.test.ts +450 -0
  38. package/src/__tests__/package-manager.test.ts +303 -303
  39. package/src/__tests__/pages.test.ts +1173 -652
  40. package/src/__tests__/parse.test.ts +252 -252
  41. package/src/__tests__/publish.test.ts +6788 -5301
  42. package/src/__tests__/pubsub.test.ts +367 -0
  43. package/src/__tests__/r2.test.ts +328 -225
  44. package/src/__tests__/route.test.ts +18 -18
  45. package/src/__tests__/secret.test.ts +470 -459
  46. package/src/__tests__/tail.test.ts +676 -564
  47. package/src/__tests__/test-old-node-version.js +31 -0
  48. package/src/__tests__/user.test.ts +136 -112
  49. package/src/__tests__/validate-dev-props.test.ts +56 -0
  50. package/src/__tests__/version.test.ts +35 -0
  51. package/src/__tests__/whoami.test.tsx +267 -83
  52. package/src/__tests__/worker-namespace.test.ts +329 -0
  53. package/src/abort.d.ts +1 -1
  54. package/src/api/dev.ts +93 -0
  55. package/src/api/index.ts +1 -0
  56. package/src/bundle-reporter.tsx +29 -0
  57. package/src/bundle.ts +430 -157
  58. package/src/cfetch/index.ts +80 -80
  59. package/src/cfetch/internal.ts +244 -91
  60. package/src/cli.ts +21 -7
  61. package/src/config/config.ts +224 -195
  62. package/src/config/diagnostics.ts +61 -61
  63. package/src/config/environment.ts +422 -357
  64. package/src/config/index.ts +219 -193
  65. package/src/config/validation-helpers.ts +379 -357
  66. package/src/config/validation.ts +1690 -1367
  67. package/src/config-cache.ts +81 -41
  68. package/src/create-worker-preview.ts +208 -136
  69. package/src/create-worker-upload-form.ts +275 -238
  70. package/src/dev/dev-vars.ts +13 -13
  71. package/src/dev/dev.tsx +477 -351
  72. package/src/dev/local.tsx +426 -295
  73. package/src/dev/remote.tsx +406 -211
  74. package/src/dev/use-esbuild.ts +154 -94
  75. package/src/dev/validate-dev-props.ts +31 -0
  76. package/src/dev-registry.tsx +160 -0
  77. package/src/dev.tsx +696 -0
  78. package/src/dialogs.tsx +97 -97
  79. package/src/dispatch-namespace.ts +206 -0
  80. package/src/durable.ts +87 -87
  81. package/src/entry.ts +234 -228
  82. package/src/environment-variables.ts +23 -23
  83. package/src/errors.ts +6 -6
  84. package/src/faye-websocket.d.ts +6 -0
  85. package/src/generate.ts +131 -0
  86. package/src/git-client.ts +42 -0
  87. package/src/global-wrangler-config-path.ts +26 -0
  88. package/src/https-options.ts +84 -80
  89. package/src/index.tsx +2164 -2768
  90. package/src/init.ts +622 -0
  91. package/src/inspect.ts +678 -593
  92. package/src/intl-polyfill.d.ts +123 -123
  93. package/src/is-ci.ts +14 -0
  94. package/src/is-interactive.ts +12 -0
  95. package/src/kv.ts +299 -277
  96. package/src/logger.ts +46 -39
  97. package/src/metrics/index.ts +5 -0
  98. package/src/metrics/metrics-config.ts +239 -0
  99. package/src/metrics/metrics-dispatcher.ts +96 -0
  100. package/src/metrics/metrics-usage-headers.ts +24 -0
  101. package/src/metrics/send-event.ts +92 -0
  102. package/src/miniflare-cli/assets.ts +554 -0
  103. package/src/miniflare-cli/enum-keys.ts +8 -8
  104. package/src/miniflare-cli/index.ts +198 -32
  105. package/src/miniflare-cli/request-context.ts +18 -18
  106. package/src/module-collection.ts +212 -212
  107. package/src/open-in-browser.ts +4 -6
  108. package/src/package-manager.ts +173 -128
  109. package/src/pages/build.tsx +238 -0
  110. package/src/pages/constants.ts +12 -0
  111. package/src/pages/deployments.tsx +106 -0
  112. package/src/pages/dev.tsx +528 -0
  113. package/src/pages/errors.ts +22 -0
  114. package/src/pages/functions/buildPlugin.ts +109 -0
  115. package/src/pages/functions/buildWorker.ts +155 -0
  116. package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
  117. package/src/pages/functions/filepath-routing.ts +189 -0
  118. package/src/pages/functions/identifiers.ts +78 -0
  119. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  120. package/src/pages/functions/routes-consolidation.ts +73 -0
  121. package/src/pages/functions/routes-transformation.test.ts +271 -0
  122. package/src/pages/functions/routes-transformation.ts +122 -0
  123. package/src/pages/functions/routes.ts +151 -0
  124. package/src/pages/functions.tsx +96 -0
  125. package/src/pages/index.tsx +94 -0
  126. package/src/pages/projects.tsx +169 -0
  127. package/src/pages/publish.tsx +391 -0
  128. package/src/pages/types.ts +49 -0
  129. package/src/pages/upload.tsx +413 -0
  130. package/src/pages/utils.ts +12 -0
  131. package/src/parse.ts +209 -125
  132. package/src/paths.ts +6 -6
  133. package/src/preview.ts +31 -0
  134. package/src/proxy.ts +410 -389
  135. package/src/publish.ts +773 -534
  136. package/src/pubsub/index.ts +286 -0
  137. package/src/pubsub/pubsub-commands.tsx +623 -0
  138. package/src/r2.ts +100 -19
  139. package/src/selfsigned.d.ts +23 -23
  140. package/src/sites.tsx +278 -201
  141. package/src/tail/filters.ts +111 -109
  142. package/src/tail/index.ts +230 -217
  143. package/src/tail/printing.ts +86 -46
  144. package/src/update-check.ts +11 -11
  145. package/src/user/choose-account.tsx +69 -0
  146. package/src/user/env-vars.ts +46 -0
  147. package/src/user/generate-auth-url.ts +33 -0
  148. package/src/user/generate-random-state.ts +16 -0
  149. package/src/user/index.ts +3 -0
  150. package/src/user/user.tsx +1191 -0
  151. package/src/whoami.tsx +141 -42
  152. package/src/worker.ts +123 -99
  153. package/src/zones.ts +167 -0
  154. package/templates/checked-fetch.js +17 -0
  155. package/templates/first-party-worker-module-facade.ts +18 -0
  156. package/templates/format-dev-errors.ts +32 -0
  157. package/templates/new-worker-scheduled.js +3 -3
  158. package/templates/new-worker-scheduled.ts +15 -15
  159. package/templates/new-worker.js +3 -3
  160. package/templates/new-worker.ts +15 -15
  161. package/templates/no-op-worker.js +10 -0
  162. package/templates/pages-shim.ts +9 -0
  163. package/templates/pages-template-plugin.ts +167 -0
  164. package/templates/pages-template-worker.ts +172 -0
  165. package/templates/serve-static-assets.ts +63 -0
  166. package/templates/service-bindings-module-facade.js +54 -0
  167. package/templates/service-bindings-sw-facade.js +42 -0
  168. package/templates/tsconfig.json +95 -95
  169. package/wrangler-dist/cli.d.ts +338 -0
  170. package/wrangler-dist/cli.js +95449 -67524
  171. package/pages/functions/buildPlugin.ts +0 -105
  172. package/pages/functions/buildWorker.ts +0 -151
  173. package/pages/functions/filepath-routing.ts +0 -189
  174. package/pages/functions/identifiers.ts +0 -78
  175. package/pages/functions/routes.ts +0 -156
  176. package/pages/functions/template-plugin.ts +0 -147
  177. package/pages/functions/template-worker.ts +0 -143
  178. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  179. package/src/pages.tsx +0 -2038
  180. package/src/user.tsx +0 -1203
  181. package/templates/static-asset-facade.js +0 -47
  182. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  183. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  184. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  185. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  186. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  187. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  188. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  189. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  190. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  191. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  192. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  193. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  194. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  195. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  196. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  197. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  198. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  199. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  200. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  201. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  202. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  203. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  204. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  205. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  206. package/vendor/wrangler-mime/LICENSE +0 -21
  207. package/vendor/wrangler-mime/Mime.js +0 -97
  208. package/vendor/wrangler-mime/README.md +0 -187
  209. package/vendor/wrangler-mime/cli.js +0 -46
  210. package/vendor/wrangler-mime/index.js +0 -4
  211. package/vendor/wrangler-mime/lite.js +0 -4
  212. package/vendor/wrangler-mime/package.json +0 -52
  213. package/vendor/wrangler-mime/types/other.js +0 -1
  214. package/vendor/wrangler-mime/types/standard.js +0 -1
@@ -8,16 +8,16 @@ import type { RequestInit } from "undici";
8
8
  export { getCloudflareAPIBaseURL as getCloudflareApiBaseUrl } from "./internal";
9
9
 
10
10
  export interface FetchError {
11
- code: number;
12
- message: string;
13
- error_chain?: FetchError[];
11
+ code: number;
12
+ message: string;
13
+ error_chain?: FetchError[];
14
14
  }
15
15
  export interface FetchResult<ResponseType = unknown> {
16
- success: boolean;
17
- result: ResponseType;
18
- errors: FetchError[];
19
- messages: string[];
20
- result_info?: unknown;
16
+ success: boolean;
17
+ result: ResponseType;
18
+ errors: FetchError[];
19
+ messages: string[];
20
+ result_info?: unknown;
21
21
  }
22
22
 
23
23
  export { fetchKVGetValue } from "./internal";
@@ -26,22 +26,22 @@ export { fetchKVGetValue } from "./internal";
26
26
  * Make a fetch request, and extract the `result` from the JSON response.
27
27
  */
28
28
  export async function fetchResult<ResponseType>(
29
- resource: string,
30
- init: RequestInit = {},
31
- queryParams?: URLSearchParams,
32
- abortSignal?: AbortSignal
29
+ resource: string,
30
+ init: RequestInit = {},
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
33
33
  ): Promise<ResponseType> {
34
- const json = await fetchInternal<FetchResult<ResponseType>>(
35
- resource,
36
- init,
37
- queryParams,
38
- abortSignal
39
- );
40
- if (json.success) {
41
- return json.result;
42
- } else {
43
- throwFetchError(resource, json);
44
- }
34
+ const json = await fetchInternal<FetchResult<ResponseType>>(
35
+ resource,
36
+ init,
37
+ queryParams,
38
+ abortSignal
39
+ );
40
+ if (json.success) {
41
+ return json.result;
42
+ } else {
43
+ throwFetchError(resource, json);
44
+ }
45
45
  }
46
46
 
47
47
  /**
@@ -50,72 +50,72 @@ export async function fetchResult<ResponseType>(
50
50
  * and repeating the request if the results are paginated.
51
51
  */
52
52
  export async function fetchListResult<ResponseType>(
53
- resource: string,
54
- init: RequestInit = {},
55
- queryParams?: URLSearchParams
53
+ resource: string,
54
+ init: RequestInit = {},
55
+ queryParams?: URLSearchParams
56
56
  ): Promise<ResponseType[]> {
57
- const results: ResponseType[] = [];
58
- let getMoreResults = true;
59
- let cursor: string | undefined;
60
- while (getMoreResults) {
61
- if (cursor) {
62
- queryParams = new URLSearchParams(queryParams);
63
- queryParams.set("cursor", cursor);
64
- }
65
- const json = await fetchInternal<FetchResult<ResponseType[]>>(
66
- resource,
67
- init,
68
- queryParams
69
- );
70
- if (json.success) {
71
- results.push(...json.result);
72
- if (hasCursor(json.result_info)) {
73
- cursor = json.result_info?.cursor;
74
- } else {
75
- getMoreResults = false;
76
- }
77
- } else {
78
- throwFetchError(resource, json);
79
- }
80
- }
81
- return results;
57
+ const results: ResponseType[] = [];
58
+ let getMoreResults = true;
59
+ let cursor: string | undefined;
60
+ while (getMoreResults) {
61
+ if (cursor) {
62
+ queryParams = new URLSearchParams(queryParams);
63
+ queryParams.set("cursor", cursor);
64
+ }
65
+ const json = await fetchInternal<FetchResult<ResponseType[]>>(
66
+ resource,
67
+ init,
68
+ queryParams
69
+ );
70
+ if (json.success) {
71
+ results.push(...json.result);
72
+ if (hasCursor(json.result_info)) {
73
+ cursor = json.result_info?.cursor;
74
+ } else {
75
+ getMoreResults = false;
76
+ }
77
+ } else {
78
+ throwFetchError(resource, json);
79
+ }
80
+ }
81
+ return results;
82
82
  }
83
83
 
84
84
  function throwFetchError(
85
- resource: string,
86
- response: FetchResult<unknown>
85
+ resource: string,
86
+ response: FetchResult<unknown>
87
87
  ): never {
88
- const error = new ParseError({
89
- text: `A request to the Cloudflare API (${resource}) failed.`,
90
- notes: response.errors.map((err) => ({
91
- text: renderError(err),
92
- })),
93
- });
94
- // add the first error code directly to this error
95
- // so consumers can use it for specific behaviour
96
- const code = response.errors[0]?.code;
97
- if (code) {
98
- //@ts-expect-error non-standard property on Error
99
- error.code = code;
100
- }
101
- throw error;
88
+ const error = new ParseError({
89
+ text: `A request to the Cloudflare API (${resource}) failed.`,
90
+ notes: response.errors.map((err) => ({
91
+ text: renderError(err),
92
+ })),
93
+ });
94
+ // add the first error code directly to this error
95
+ // so consumers can use it for specific behaviour
96
+ const code = response.errors[0]?.code;
97
+ if (code) {
98
+ //@ts-expect-error non-standard property on Error
99
+ error.code = code;
100
+ }
101
+ throw error;
102
102
  }
103
103
 
104
104
  function hasCursor(result_info: unknown): result_info is { cursor: string } {
105
- const cursor = (result_info as { cursor: string } | undefined)?.cursor;
106
- return cursor !== undefined && cursor !== null && cursor !== "";
105
+ const cursor = (result_info as { cursor: string } | undefined)?.cursor;
106
+ return cursor !== undefined && cursor !== null && cursor !== "";
107
107
  }
108
108
 
109
109
  function renderError(err: FetchError, level = 0): string {
110
- const chainedMessages =
111
- err.error_chain
112
- ?.map(
113
- (chainedError) =>
114
- `\n${" ".repeat(level)}- ${renderError(chainedError, level + 1)}`
115
- )
116
- .join("\n") ?? "";
117
- return (
118
- (err.code ? `${err.message} [code: ${err.code}]` : err.message) +
119
- chainedMessages
120
- );
110
+ const chainedMessages =
111
+ err.error_chain
112
+ ?.map(
113
+ (chainedError) =>
114
+ `\n${" ".repeat(level)}- ${renderError(chainedError, level + 1)}`
115
+ )
116
+ .join("\n") ?? "";
117
+ return (
118
+ (err.code ? `${err.message} [code: ${err.code}]` : err.message) +
119
+ chainedMessages
120
+ );
121
121
  }
@@ -1,18 +1,22 @@
1
- import { fetch, Headers } from "undici";
1
+ import assert from "node:assert";
2
+ import Busboy from "busboy";
3
+ import { fetch, File, FormData, Headers } from "undici";
2
4
  import { version as wranglerVersion } from "../../package.json";
3
5
  import { getEnvironmentVariableFactory } from "../environment-variables";
6
+ import { logger } from "../logger";
4
7
  import { ParseError, parseJSON } from "../parse";
5
- import { getAPIToken, loginOrRefreshIfRequired } from "../user";
8
+ import { loginOrRefreshIfRequired, requireApiToken } from "../user";
9
+ import type { ApiCredentials } from "../user";
6
10
  import type { URLSearchParams } from "node:url";
7
- import type { RequestInit, HeadersInit } from "undici";
11
+ import type { RequestInit, HeadersInit, Response } from "undici";
8
12
 
9
13
  /**
10
14
  * Get the URL to use to access the Cloudflare API.
11
15
  */
12
16
  export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
13
- variableName: "CLOUDFLARE_API_BASE_URL",
14
- deprecatedName: "CF_API_BASE_URL",
15
- defaultValue: "https://api.cloudflare.com/client/v4",
17
+ variableName: "CLOUDFLARE_API_BASE_URL",
18
+ deprecatedName: "CF_API_BASE_URL",
19
+ defaultValue: "https://api.cloudflare.com/client/v4",
16
20
  });
17
21
 
18
22
  /**
@@ -25,90 +29,107 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
25
29
  * This function should not be used directly, instead use the functions in `cfetch/index.ts`.
26
30
  */
27
31
  export async function fetchInternal<ResponseType>(
28
- resource: string,
29
- init: RequestInit = {},
30
- queryParams?: URLSearchParams,
31
- abortSignal?: AbortSignal
32
+ resource: string,
33
+ init: RequestInit = {},
34
+ queryParams?: URLSearchParams,
35
+ abortSignal?: AbortSignal
32
36
  ): Promise<ResponseType> {
33
- await requireLoggedIn();
34
- const apiToken = requireApiToken();
35
- const headers = cloneHeaders(init.headers);
36
- addAuthorizationHeaderIfUnspecified(headers, apiToken);
37
- addUserAgent(headers);
38
-
39
- const queryString = queryParams ? `?${queryParams.toString()}` : "";
40
- const method = init.method ?? "GET";
41
- const response = await fetch(
42
- `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
43
- {
44
- method,
45
- ...init,
46
- headers,
47
- signal: abortSignal,
48
- }
49
- );
50
- const jsonText = await response.text();
51
- try {
52
- return parseJSON<ResponseType>(jsonText);
53
- } catch (err) {
54
- throw new ParseError({
55
- text: "Received a malformed response from the API",
56
- notes: [
57
- {
58
- text: truncate(jsonText, 100),
59
- },
60
- {
61
- text: `${method} ${resource} -> ${response.status} ${response.statusText}`,
62
- },
63
- ],
64
- });
65
- }
37
+ assert(
38
+ resource.startsWith("/"),
39
+ `CF API fetch - resource path must start with a "/" but got "${resource}"`
40
+ );
41
+ await requireLoggedIn();
42
+ const apiToken = requireApiToken();
43
+ const headers = cloneHeaders(init.headers);
44
+ addAuthorizationHeaderIfUnspecified(headers, apiToken);
45
+ addUserAgent(headers);
46
+
47
+ const queryString = queryParams ? `?${queryParams.toString()}` : "";
48
+ const method = init.method ?? "GET";
49
+
50
+ logger.debug(
51
+ `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}`
52
+ );
53
+ logger.debug("HEADERS:", JSON.stringify(headers, null, 2));
54
+ logger.debug("INIT:", JSON.stringify(init, null, 2));
55
+ logger.debug("-- END CF API REQUEST");
56
+ const response = await fetch(
57
+ `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
58
+ {
59
+ method,
60
+ ...init,
61
+ headers,
62
+ signal: abortSignal,
63
+ }
64
+ );
65
+ const jsonText = await response.text();
66
+ logger.debug(
67
+ "-- START CF API RESPONSE:",
68
+ response.statusText,
69
+ response.status
70
+ );
71
+ logger.debug("HEADERS:", JSON.stringify(response.headers, null, 2));
72
+ logger.debug("RESPONSE:", jsonText);
73
+ logger.debug("-- END CF API RESPONSE");
74
+
75
+ try {
76
+ return parseJSON<ResponseType>(jsonText);
77
+ } catch (err) {
78
+ throw new ParseError({
79
+ text: "Received a malformed response from the API",
80
+ notes: [
81
+ {
82
+ text: truncate(jsonText, 100),
83
+ },
84
+ {
85
+ text: `${method} ${resource} -> ${response.status} ${response.statusText}`,
86
+ },
87
+ ],
88
+ });
89
+ }
66
90
  }
67
91
 
68
92
  function truncate(text: string, maxLength: number): string {
69
- const { length } = text;
70
- if (length <= maxLength) {
71
- return text;
72
- }
73
- return `${text.substring(0, maxLength)}... (length = ${length})`;
93
+ const { length } = text;
94
+ if (length <= maxLength) {
95
+ return text;
96
+ }
97
+ return `${text.substring(0, maxLength)}... (length = ${length})`;
74
98
  }
75
99
 
76
100
  function cloneHeaders(
77
- headers: HeadersInit | undefined
101
+ headers: HeadersInit | undefined
78
102
  ): Record<string, string> {
79
- return headers instanceof Headers
80
- ? Object.fromEntries(headers.entries())
81
- : Array.isArray(headers)
82
- ? Object.fromEntries(headers)
83
- : { ...headers };
103
+ return headers instanceof Headers
104
+ ? Object.fromEntries(headers.entries())
105
+ : Array.isArray(headers)
106
+ ? Object.fromEntries(headers)
107
+ : { ...headers };
84
108
  }
85
109
 
86
110
  async function requireLoggedIn(): Promise<void> {
87
- const loggedIn = await loginOrRefreshIfRequired();
88
- if (!loggedIn) {
89
- throw new Error("Not logged in.");
90
- }
91
- }
92
-
93
- function requireApiToken(): string {
94
- const authToken = getAPIToken();
95
- if (!authToken) {
96
- throw new Error("No API token found.");
97
- }
98
- return authToken;
111
+ const loggedIn = await loginOrRefreshIfRequired();
112
+ if (!loggedIn) {
113
+ throw new Error("Not logged in.");
114
+ }
99
115
  }
100
116
 
101
117
  function addAuthorizationHeaderIfUnspecified(
102
- headers: Record<string, string>,
103
- apiToken: string
118
+ headers: Record<string, string>,
119
+ auth: ApiCredentials
104
120
  ): void {
105
- if (!("Authorization" in headers)) {
106
- headers["Authorization"] = `Bearer ${apiToken}`;
107
- }
121
+ if (!("Authorization" in headers)) {
122
+ if ("apiToken" in auth) {
123
+ headers["Authorization"] = `Bearer ${auth.apiToken}`;
124
+ } else {
125
+ headers["X-Auth-Key"] = auth.authKey;
126
+ headers["X-Auth-Email"] = auth.authEmail;
127
+ }
128
+ }
108
129
  }
109
130
 
110
131
  function addUserAgent(headers: Record<string, string>): void {
111
- headers["User-Agent"] = `wrangler/${wranglerVersion}`;
132
+ headers["User-Agent"] = `wrangler/${wranglerVersion}`;
112
133
  }
113
134
 
114
135
  /**
@@ -121,25 +142,157 @@ function addUserAgent(headers: Record<string, string>): void {
121
142
  * Note: any calls to fetchKVGetValue must call encodeURIComponent on key
122
143
  * before passing it
123
144
  */
124
-
125
145
  export async function fetchKVGetValue(
126
- accountId: string,
127
- namespaceId: string,
128
- key: string
146
+ accountId: string,
147
+ namespaceId: string,
148
+ key: string
149
+ ): Promise<ArrayBuffer> {
150
+ await requireLoggedIn();
151
+ const auth = requireApiToken();
152
+ const headers: Record<string, string> = {};
153
+ addAuthorizationHeaderIfUnspecified(headers, auth);
154
+ const resource = `${getCloudflareAPIBaseURL()}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`;
155
+ const response = await fetch(resource, {
156
+ method: "GET",
157
+ headers,
158
+ });
159
+ if (response.ok) {
160
+ return await response.arrayBuffer();
161
+ } else {
162
+ throw new Error(
163
+ `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
164
+ );
165
+ }
166
+ }
167
+
168
+ /**
169
+ * The implementation for fetching a R2 object from Cloudflare API.
170
+ * We have a special implementation to handle the non-standard API response
171
+ * that doesn't return JSON, likely due to the streaming nature.
172
+ *
173
+ * note: The implementation should be called from light wrappers for
174
+ * different methods (GET, PUT)
175
+ */
176
+ type ResponseWithBody = Response & { body: NonNullable<Response["body"]> };
177
+ export async function fetchR2Objects(
178
+ resource: string,
179
+ bodyInit: RequestInit = {}
180
+ ): Promise<ResponseWithBody> {
181
+ await requireLoggedIn();
182
+ const auth = requireApiToken();
183
+ const headers = cloneHeaders(bodyInit.headers);
184
+ addAuthorizationHeaderIfUnspecified(headers, auth);
185
+ addUserAgent(headers);
186
+
187
+ const response = await fetch(`${getCloudflareAPIBaseURL()}${resource}`, {
188
+ ...bodyInit,
189
+ headers,
190
+ });
191
+
192
+ if (response.ok && response.body) {
193
+ return response as ResponseWithBody;
194
+ } else {
195
+ throw new Error(
196
+ `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
197
+ );
198
+ }
199
+ }
200
+
201
+ /**
202
+ * This is a wrapper STOPGAP for getting the script which returns a raw text response.
203
+ */
204
+ export async function fetchDashboardScript(
205
+ resource: string,
206
+ bodyInit: RequestInit = {}
129
207
  ): Promise<string> {
130
- await requireLoggedIn();
131
- const apiToken = requireApiToken();
132
- const headers = { Authorization: `Bearer ${apiToken}` };
133
- const resource = `${getCloudflareAPIBaseURL()}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`;
134
- const response = await fetch(resource, {
135
- method: "GET",
136
- headers,
137
- });
138
- if (response.ok) {
139
- return await response.text();
140
- } else {
141
- throw new Error(
142
- `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
143
- );
144
- }
208
+ await requireLoggedIn();
209
+ const auth = requireApiToken();
210
+ const headers = cloneHeaders(bodyInit.headers);
211
+ addAuthorizationHeaderIfUnspecified(headers, auth);
212
+ addUserAgent(headers);
213
+
214
+ const response = await fetch(`${getCloudflareAPIBaseURL()}${resource}`, {
215
+ ...bodyInit,
216
+ headers,
217
+ });
218
+
219
+ if (!response.ok || !response.body) {
220
+ throw new Error(
221
+ `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
222
+ );
223
+ }
224
+
225
+ const usesModules = response.headers
226
+ .get("content-type")
227
+ ?.startsWith("multipart");
228
+
229
+ if (usesModules) {
230
+ // Response from edge contains generic "name = worker.js" for dashboard created scripts
231
+ const form = await formData(response);
232
+ const entries = Array.from(form.entries());
233
+ if (entries.length > 1)
234
+ throw new RangeError("Expected only one entry in multipart response");
235
+ const [_, file] = entries[0];
236
+
237
+ if (file instanceof File) {
238
+ return await file.text();
239
+ }
240
+
241
+ return file ?? "";
242
+
243
+ // Follow up on issue in Undici about multipart/form-data support & replace the workaround: https://github.com/nodejs/undici/issues/974
244
+ // This should be using a builtin formData() parser pattern.
245
+ } else {
246
+ return response.text();
247
+ }
248
+ }
249
+
250
+ async function formData({ headers, body }: Response): Promise<FormData> {
251
+ // undici doesn't include a multipart/form-data parser yet, so we parse
252
+ // form data with busboy instead
253
+ const contentType = headers.get("Content-Type") ?? "";
254
+ if (!/multipart\/form-data/.test(contentType))
255
+ throw Error("Need Content-Type for multipart/form-data");
256
+
257
+ const responseFormData = new FormData();
258
+
259
+ let busboy: Busboy.Busboy;
260
+
261
+ const parsedHeaders = Object.fromEntries(
262
+ Array.from(headers).map(([header, value]) => [header.toLowerCase(), value])
263
+ );
264
+ try {
265
+ busboy = Busboy({ headers: parsedHeaders });
266
+ } catch (err) {
267
+ // Error due to headers:
268
+ throw Object.assign(new TypeError(), { cause: err });
269
+ }
270
+
271
+ busboy.on("field", (name, value) => {
272
+ responseFormData.append(name, value);
273
+ });
274
+ busboy.on("file", (name, value, info) => {
275
+ const { filename, encoding, mimeType } = info;
276
+ const base64 = encoding.toLowerCase() === "base64";
277
+ const chunks: Buffer[] = [];
278
+ value.on("data", (chunk) => {
279
+ if (base64) chunk = Buffer.from(chunk.toString(), "base64");
280
+ chunks.push(chunk);
281
+ });
282
+ value.on("end", () => {
283
+ const file = new File(chunks, filename, { type: mimeType });
284
+ responseFormData.append(name, file);
285
+ });
286
+ });
287
+
288
+ const busboyResolve = new Promise((resolve, reject) => {
289
+ busboy.on("finish", resolve);
290
+ busboy.on("error", (err) => reject(err));
291
+ });
292
+
293
+ if (body !== null) for await (const chunk of body) busboy.write(chunk);
294
+ busboy.end();
295
+ await busboyResolve;
296
+
297
+ return responseFormData;
145
298
  }
package/src/cli.ts CHANGED
@@ -1,13 +1,27 @@
1
1
  import "dotenv/config"; // Grab locally specified env params from a `.env` file.
2
2
  import process from "process";
3
3
  import { hideBin } from "yargs/helpers";
4
+ import { unstable_dev } from "./api";
4
5
  import { FatalError } from "./errors";
5
6
  import { main } from ".";
6
7
 
7
- main(hideBin(process.argv)).catch((e) => {
8
- // The logging of any error that was thrown from `main()` is handled in the `yargs.fail()` handler.
9
- // Here we just want to ensure that the process exits with a non-zero code.
10
- // We don't want to do this inside the `main()` function, since that would kill the process when running our tests.
11
- const exitCode = (e instanceof FatalError && e.code) || 1;
12
- process.exit(exitCode);
13
- });
8
+ /**
9
+ * The main entrypoint for the CLI.
10
+ * main only gets called when the script is run directly, not when it's imported as a module.
11
+ */
12
+ if (typeof jest === "undefined" && require.main) {
13
+ main(hideBin(process.argv)).catch((e) => {
14
+ // The logging of any error that was thrown from `main()` is handled in the `yargs.fail()` handler.
15
+ // Here we just want to ensure that the process exits with a non-zero code.
16
+ // We don't want to do this inside the `main()` function, since that would kill the process when running our tests.
17
+ const exitCode = (e instanceof FatalError && e.code) || 1;
18
+ process.exit(exitCode);
19
+ });
20
+ }
21
+
22
+ /**
23
+ * This is how we're exporting the API.
24
+ * It makes it possible to import wrangler from 'wrangler',
25
+ * and call wrangler.unstable_dev().
26
+ */
27
+ export default { unstable_dev };