wrangler 0.0.0-e9ab55a → 0.0.0-ea7ee45

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 (197) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +33 -14
  3. package/bin/wrangler.js +123 -25
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +4893 -0
  6. package/package.json +156 -125
  7. package/src/__tests__/config-cache.test.ts +36 -0
  8. package/src/__tests__/configuration.test.ts +3829 -0
  9. package/src/__tests__/dev.test.tsx +1155 -63
  10. package/src/__tests__/guess-worker-format.test.ts +85 -0
  11. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  14. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  15. package/src/__tests__/helpers/mock-bin.ts +36 -0
  16. package/src/__tests__/helpers/mock-cfetch.ts +211 -0
  17. package/src/__tests__/helpers/mock-console.ts +97 -0
  18. package/src/__tests__/helpers/mock-dialogs.ts +153 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +74 -0
  21. package/src/__tests__/helpers/mock-kv.ts +33 -0
  22. package/src/__tests__/helpers/mock-oauth-flow.ts +250 -0
  23. package/src/__tests__/helpers/mock-process.ts +39 -0
  24. package/src/__tests__/helpers/mock-stdin.ts +108 -0
  25. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +39 -0
  27. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  28. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  29. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  30. package/src/__tests__/https-options.test.ts +130 -0
  31. package/src/__tests__/index.test.ts +259 -284
  32. package/src/__tests__/init.test.ts +1992 -0
  33. package/src/__tests__/jest.setup.ts +95 -10
  34. package/src/__tests__/kv.test.ts +1402 -909
  35. package/src/__tests__/logger.test.ts +141 -0
  36. package/src/__tests__/package-manager.test.ts +353 -0
  37. package/src/__tests__/pages.test.ts +1254 -0
  38. package/src/__tests__/parse.test.ts +330 -0
  39. package/src/__tests__/publish.test.ts +6504 -93
  40. package/src/__tests__/pubsub.test.ts +276 -0
  41. package/src/__tests__/r2.test.ts +233 -0
  42. package/src/__tests__/route.test.ts +45 -0
  43. package/src/__tests__/secret.test.ts +486 -0
  44. package/src/__tests__/tail.test.ts +690 -0
  45. package/src/__tests__/user.test.ts +143 -0
  46. package/src/__tests__/whoami.test.tsx +220 -0
  47. package/src/__tests__/worker-namespace.test.ts +327 -0
  48. package/src/abort.d.ts +3 -0
  49. package/src/api/dev.ts +39 -0
  50. package/src/api/index.ts +1 -0
  51. package/src/bundle-reporter.tsx +29 -0
  52. package/src/bundle.ts +212 -0
  53. package/src/cfetch/index.ts +88 -69
  54. package/src/cfetch/internal.ts +124 -44
  55. package/src/cli.ts +24 -6
  56. package/src/config/README.md +107 -0
  57. package/src/config/config.ts +243 -0
  58. package/src/config/diagnostics.ts +80 -0
  59. package/src/config/environment.ts +430 -0
  60. package/src/config/index.ts +225 -0
  61. package/src/config/validation-helpers.ts +584 -0
  62. package/src/config/validation.ts +1693 -0
  63. package/src/config-cache.ts +44 -0
  64. package/src/create-worker-preview.ts +255 -0
  65. package/src/create-worker-upload-form.ts +254 -0
  66. package/src/dev/dev-vars.ts +36 -0
  67. package/src/dev/dev.tsx +417 -0
  68. package/src/dev/local.tsx +350 -0
  69. package/src/dev/remote.tsx +367 -0
  70. package/src/dev/use-esbuild.ts +150 -0
  71. package/src/dev.tsx +515 -773
  72. package/src/dialogs.tsx +117 -59
  73. package/src/durable.ts +102 -0
  74. package/src/entry.ts +328 -0
  75. package/src/environment-variables.ts +35 -0
  76. package/src/errors.ts +11 -0
  77. package/src/generate.ts +33 -0
  78. package/src/git-client.ts +42 -0
  79. package/src/https-options.ts +123 -0
  80. package/src/index.tsx +1844 -1901
  81. package/src/init.ts +549 -0
  82. package/src/inspect.ts +615 -496
  83. package/src/intl-polyfill.d.ts +139 -0
  84. package/src/kv.ts +408 -0
  85. package/src/logger.ts +73 -0
  86. package/src/miniflare-cli/README.md +30 -0
  87. package/src/miniflare-cli/enum-keys.ts +17 -0
  88. package/src/miniflare-cli/index.ts +52 -0
  89. package/src/miniflare-cli/request-context.ts +40 -0
  90. package/src/module-collection.ts +247 -53
  91. package/src/open-in-browser.ts +17 -0
  92. package/src/package-manager.ts +174 -0
  93. package/src/pages/build.tsx +202 -0
  94. package/src/pages/constants.ts +7 -0
  95. package/src/pages/deployments.tsx +101 -0
  96. package/src/pages/dev.tsx +964 -0
  97. package/src/pages/functions/buildPlugin.ts +105 -0
  98. package/src/pages/functions/buildWorker.ts +151 -0
  99. package/src/pages/functions/filepath-routing.test.ts +234 -0
  100. package/src/pages/functions/filepath-routing.ts +189 -0
  101. package/src/pages/functions/identifiers.ts +78 -0
  102. package/src/pages/functions/routes.ts +151 -0
  103. package/src/pages/index.tsx +84 -0
  104. package/src/pages/projects.tsx +157 -0
  105. package/src/pages/publish.tsx +335 -0
  106. package/src/pages/types.ts +40 -0
  107. package/src/pages/upload.tsx +384 -0
  108. package/src/pages/utils.ts +12 -0
  109. package/src/parse.ts +286 -0
  110. package/src/paths.ts +26 -0
  111. package/src/preview.ts +31 -0
  112. package/src/proxy.ts +428 -254
  113. package/src/publish.ts +750 -366
  114. package/src/pubsub/index.ts +286 -0
  115. package/src/pubsub/pubsub-commands.tsx +594 -0
  116. package/src/r2.ts +50 -0
  117. package/src/selfsigned.d.ts +29 -0
  118. package/src/sites.tsx +357 -103
  119. package/src/tail/filters.ts +279 -0
  120. package/src/tail/index.ts +299 -0
  121. package/src/tail/printing.ts +67 -0
  122. package/src/update-check.ts +19 -0
  123. package/src/user/choose-account.tsx +60 -0
  124. package/src/user/env-vars.ts +46 -0
  125. package/src/user/generate-auth-url.ts +33 -0
  126. package/src/user/generate-random-state.ts +16 -0
  127. package/src/user/index.ts +3 -0
  128. package/src/user/user.tsx +1130 -0
  129. package/src/whoami.tsx +90 -0
  130. package/src/worker-namespace.ts +190 -0
  131. package/src/worker.ts +185 -0
  132. package/src/zones.ts +76 -0
  133. package/templates/checked-fetch.js +17 -0
  134. package/templates/gitignore +171 -0
  135. package/templates/new-worker-scheduled.js +17 -0
  136. package/templates/new-worker-scheduled.ts +32 -0
  137. package/templates/new-worker.js +15 -0
  138. package/templates/new-worker.ts +30 -0
  139. package/templates/no-op-worker.js +10 -0
  140. package/templates/pages-template-plugin.ts +155 -0
  141. package/templates/pages-template-worker.ts +161 -0
  142. package/templates/static-asset-facade.js +43 -0
  143. package/templates/tsconfig.json +105 -0
  144. package/wrangler-dist/cli.js +107117 -103435
  145. package/pages/functions/buildWorker.ts +0 -62
  146. package/pages/functions/filepath-routing.test.ts +0 -39
  147. package/pages/functions/filepath-routing.ts +0 -228
  148. package/pages/functions/identifiers.ts +0 -78
  149. package/pages/functions/routes.ts +0 -158
  150. package/pages/functions/template-worker.ts +0 -136
  151. package/src/__tests__/mock-cfetch.ts +0 -171
  152. package/src/__tests__/mock-dialogs.ts +0 -65
  153. package/src/__tests__/run-in-tmp.ts +0 -19
  154. package/src/__tests__/run-wrangler.ts +0 -32
  155. package/src/api/form_data.ts +0 -131
  156. package/src/api/preview.ts +0 -128
  157. package/src/api/worker.ts +0 -155
  158. package/src/config.ts +0 -646
  159. package/src/kv.tsx +0 -276
  160. package/src/pages.tsx +0 -1040
  161. package/src/tail.tsx +0 -73
  162. package/src/user.tsx +0 -1027
  163. package/static-asset-facade.js +0 -47
  164. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  165. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  166. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  167. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  168. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  169. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  170. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  171. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  172. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  173. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  174. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  175. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  176. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  177. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  178. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  179. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  180. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  181. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  182. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  183. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  184. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  185. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  186. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  187. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  188. package/vendor/wrangler-mime/LICENSE +0 -21
  189. package/vendor/wrangler-mime/Mime.js +0 -97
  190. package/vendor/wrangler-mime/README.md +0 -187
  191. package/vendor/wrangler-mime/cli.js +0 -46
  192. package/vendor/wrangler-mime/index.js +0 -4
  193. package/vendor/wrangler-mime/lite.js +0 -4
  194. package/vendor/wrangler-mime/package.json +0 -52
  195. package/vendor/wrangler-mime/types/other.js +0 -1
  196. package/vendor/wrangler-mime/types/standard.js +0 -1
  197. package/wrangler-dist/cli.js.map +0 -7
package/src/api/dev.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { startDev } from "../dev";
2
+ import { logger } from "../logger";
3
+ interface DevOptions {
4
+ env?: string;
5
+ ip?: string;
6
+ port?: number;
7
+ localProtocol?: "http" | "https";
8
+ assets?: string;
9
+ site?: string;
10
+ siteInclude?: string[];
11
+ siteExclude?: string[];
12
+ nodeCompat?: boolean;
13
+ experimentalEnableLocalPersistence?: boolean;
14
+ _: (string | number)[]; //yargs wants this
15
+ $0: string; //yargs wants this
16
+ }
17
+
18
+ export async function unstable_dev(script: string, options: DevOptions) {
19
+ logger.warn(
20
+ `unstable_dev() is experimental\nunstable_dev()'s behaviour will likely change in future releases`
21
+ );
22
+
23
+ return new Promise<{ stop: () => void }>((resolve) => {
24
+ //lmao
25
+ return new Promise<Awaited<ReturnType<typeof startDev>>>((ready) => {
26
+ const devServer = startDev({
27
+ script: script,
28
+ ...options,
29
+ local: true,
30
+ onReady: () => ready(devServer),
31
+ inspect: false,
32
+ logLevel: "none",
33
+ showInteractiveDevSession: false,
34
+ });
35
+ }).then((devServer) => {
36
+ resolve({ stop: devServer.stop });
37
+ });
38
+ });
39
+ }
@@ -0,0 +1 @@
1
+ export { unstable_dev } from "./dev";
@@ -0,0 +1,29 @@
1
+ import { Blob } from "node:buffer";
2
+ import { gzipSync } from "node:zlib";
3
+ import { logger } from "./logger";
4
+ import type { CfModule } from "./worker";
5
+
6
+ async function getSize(modules: CfModule[]) {
7
+ const gzipSize = gzipSync(
8
+ await new Blob(modules.map((file) => file.content)).arrayBuffer()
9
+ ).byteLength;
10
+ const aggregateSize = new Blob(modules.map((file) => file.content)).size;
11
+
12
+ return { size: aggregateSize, gzipSize };
13
+ }
14
+
15
+ export async function printBundleSize(
16
+ main: {
17
+ name: string;
18
+ content: string;
19
+ },
20
+ modules: CfModule[]
21
+ ) {
22
+ const { size, gzipSize } = await getSize([...modules, main]);
23
+
24
+ const bundleReport = `${(size / 1024).toFixed(2)} KiB / gzip: ${(
25
+ gzipSize / 1024
26
+ ).toFixed(2)} KiB`;
27
+
28
+ logger.log(`Total Upload: ${bundleReport}`);
29
+ }
package/src/bundle.ts ADDED
@@ -0,0 +1,212 @@
1
+ import assert from "node:assert";
2
+ import * as fs from "node:fs";
3
+ import { builtinModules } from "node:module";
4
+ import * as path from "node:path";
5
+ import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
6
+ import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
7
+ import * as esbuild from "esbuild";
8
+ import createModuleCollector from "./module-collection";
9
+ import type { Config } from "./config";
10
+ import type { Entry } from "./entry";
11
+ import type { CfModule } from "./worker";
12
+
13
+ type BundleResult = {
14
+ modules: CfModule[];
15
+ resolvedEntryPointPath: string;
16
+ bundleType: "esm" | "commonjs";
17
+ stop: (() => void) | undefined;
18
+ };
19
+
20
+ /**
21
+ * Searches for any uses of node's builtin modules, and throws an error if it
22
+ * finds anything. This plugin is only used when nodeCompat is not enabled.
23
+ * Supports both regular node builtins, and the new "node:<MODULE>" format.
24
+ */
25
+ const checkForNodeBuiltinsPlugin = {
26
+ name: "checkForNodeBuiltins",
27
+ setup(build: esbuild.PluginBuild) {
28
+ build.onResolve(
29
+ {
30
+ filter: new RegExp(
31
+ "^(" +
32
+ builtinModules.join("|") +
33
+ "|" +
34
+ builtinModules.map((module) => "node:" + module).join("|") +
35
+ ")$"
36
+ ),
37
+ },
38
+ () => {
39
+ throw new Error(
40
+ `Detected a Node builtin module import while Node compatibility is disabled.\nAdd node_compat = true to your wrangler.toml file to enable Node compatibility.`
41
+ );
42
+ }
43
+ );
44
+ },
45
+ };
46
+
47
+ /**
48
+ * Generate a bundle for the worker identified by the arguments passed in.
49
+ */
50
+ export async function bundleWorker(
51
+ entry: Entry,
52
+ destination: string,
53
+ options: {
54
+ serveAssetsFromWorker: boolean;
55
+ jsxFactory: string | undefined;
56
+ jsxFragment: string | undefined;
57
+ rules: Config["rules"];
58
+ watch?: esbuild.WatchMode;
59
+ tsconfig: string | undefined;
60
+ minify: boolean | undefined;
61
+ nodeCompat: boolean | undefined;
62
+ define: Config["define"];
63
+ checkFetch: boolean;
64
+ }
65
+ ): Promise<BundleResult> {
66
+ const {
67
+ serveAssetsFromWorker,
68
+ jsxFactory,
69
+ jsxFragment,
70
+ rules,
71
+ watch,
72
+ tsconfig,
73
+ minify,
74
+ nodeCompat,
75
+ checkFetch,
76
+ } = options;
77
+ const entryDirectory = path.dirname(entry.file);
78
+ const moduleCollector = createModuleCollector({
79
+ wrangler1xlegacyModuleReferences: {
80
+ rootDirectory: entryDirectory,
81
+ fileNames: new Set(
82
+ fs
83
+ .readdirSync(entryDirectory, { withFileTypes: true })
84
+ .filter(
85
+ (dirEntry) =>
86
+ dirEntry.isFile() && dirEntry.name !== path.basename(entry.file)
87
+ )
88
+ .map((dirEnt) => dirEnt.name)
89
+ ),
90
+ },
91
+ format: entry.format,
92
+ rules,
93
+ });
94
+
95
+ const result = await esbuild.build({
96
+ ...getEntryPoint(entry.file, serveAssetsFromWorker),
97
+ bundle: true,
98
+ absWorkingDir: entry.directory,
99
+ outdir: destination,
100
+ inject: checkFetch
101
+ ? [path.resolve(__dirname, "../templates/checked-fetch.js")]
102
+ : [],
103
+ external: ["__STATIC_CONTENT_MANIFEST"],
104
+ format: entry.format === "modules" ? "esm" : "iife",
105
+ target: "es2020",
106
+ sourcemap: true,
107
+ minify,
108
+ metafile: true,
109
+ conditions: ["worker", "browser"],
110
+ ...(process.env.NODE_ENV && {
111
+ define: {
112
+ "process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
113
+ ...(nodeCompat ? { global: "globalThis" } : {}),
114
+ ...(checkFetch ? { fetch: "checkedFetch" } : {}),
115
+ ...options.define,
116
+ },
117
+ }),
118
+ loader: {
119
+ ".js": "jsx",
120
+ ".mjs": "jsx",
121
+ ".cjs": "jsx",
122
+ },
123
+ plugins: [
124
+ moduleCollector.plugin,
125
+ ...(nodeCompat
126
+ ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
127
+ : // we use checkForNodeBuiltinsPlugin to throw a nicer error
128
+ // if we find node builtins when nodeCompat isn't turned on
129
+ [checkForNodeBuiltinsPlugin]),
130
+ ],
131
+ ...(jsxFactory && { jsxFactory }),
132
+ ...(jsxFragment && { jsxFragment }),
133
+ ...(tsconfig && { tsconfig }),
134
+ watch,
135
+ });
136
+
137
+ const entryPointOutputs = Object.entries(result.metafile.outputs).filter(
138
+ ([_path, output]) => output.entryPoint !== undefined
139
+ );
140
+ assert(
141
+ entryPointOutputs.length > 0,
142
+ `Cannot find entry-point "${entry.file}" in generated bundle.` +
143
+ listEntryPoints(entryPointOutputs)
144
+ );
145
+ assert(
146
+ entryPointOutputs.length < 2,
147
+ "More than one entry-point found for generated bundle." +
148
+ listEntryPoints(entryPointOutputs)
149
+ );
150
+
151
+ const entryPointExports = entryPointOutputs[0][1].exports;
152
+ const bundleType = entryPointExports.length > 0 ? "esm" : "commonjs";
153
+
154
+ return {
155
+ modules: moduleCollector.modules,
156
+ resolvedEntryPointPath: path.resolve(
157
+ entry.directory,
158
+ entryPointOutputs[0][0]
159
+ ),
160
+ bundleType,
161
+ stop: result.stop,
162
+ };
163
+ }
164
+
165
+ type EntryPoint = { stdin: esbuild.StdinOptions } | { entryPoints: string[] };
166
+
167
+ /**
168
+ * Create an object that describes the entry point for esbuild.
169
+ *
170
+ * If we are using the experimental asset handling, then the entry point is
171
+ * actually a shim worker that will either return an asset from a KV store,
172
+ * or delegate to the actual worker.
173
+ */
174
+ function getEntryPoint(
175
+ entryFile: string,
176
+ serveAssetsFromWorker: boolean
177
+ ): EntryPoint {
178
+ if (serveAssetsFromWorker) {
179
+ return {
180
+ stdin: {
181
+ contents: fs
182
+ .readFileSync(
183
+ path.join(__dirname, "../templates/static-asset-facade.js"),
184
+ "utf8"
185
+ )
186
+ // on windows, escape backslashes in the path (`\`)
187
+ .replace("__ENTRY_POINT__", entryFile.replaceAll("\\", "\\\\"))
188
+ .replace(
189
+ "__KV_ASSET_HANDLER__",
190
+ path
191
+ .join(__dirname, "../kv-asset-handler.js")
192
+ .replaceAll("\\", "\\\\")
193
+ ),
194
+ sourcefile: "static-asset-facade.js",
195
+ resolveDir: path.dirname(entryFile),
196
+ },
197
+ };
198
+ } else {
199
+ return { entryPoints: [entryFile] };
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Generate a string that describes the entry-points that were identified by esbuild.
205
+ */
206
+ function listEntryPoints(
207
+ outputs: [string, ValueOf<esbuild.Metafile["outputs"]>][]
208
+ ): string {
209
+ return outputs.map(([_input, output]) => output.entryPoint).join("\n");
210
+ }
211
+
212
+ type ValueOf<T> = T[keyof T];
@@ -1,52 +1,47 @@
1
- import type { RequestInit } from "node-fetch";
2
- import { URLSearchParams } from "url";
1
+ import { URLSearchParams } from "node:url";
2
+ import { ParseError } from "../parse";
3
3
  import { fetchInternal } from "./internal";
4
+ import type { RequestInit } from "undici";
4
5
 
5
6
  // Check out https://api.cloudflare.com/ for API docs.
6
7
 
7
- export { CF_API_BASE_URL } from "./internal";
8
+ export { getCloudflareAPIBaseURL as getCloudflareApiBaseUrl } from "./internal";
8
9
 
9
10
  export interface FetchError {
10
- code: number;
11
- message: string;
11
+ code: number;
12
+ message: string;
13
+ error_chain?: FetchError[];
12
14
  }
13
15
  export interface FetchResult<ResponseType = unknown> {
14
- success: boolean;
15
- result: ResponseType;
16
- errors: FetchError[];
17
- messages: string[];
18
- result_info?: unknown;
16
+ success: boolean;
17
+ result: ResponseType;
18
+ errors: FetchError[];
19
+ messages: string[];
20
+ result_info?: unknown;
19
21
  }
20
22
 
21
- /**
22
- * Make a fetch request for a raw JSON value.
23
- */
24
- export async function fetchRaw<ResponseType>(
25
- resource: string,
26
- init: RequestInit = {},
27
- queryParams?: URLSearchParams
28
- ): Promise<ResponseType> {
29
- return fetchInternal<ResponseType>(resource, init, queryParams);
30
- }
23
+ export { fetchKVGetValue } from "./internal";
31
24
 
32
25
  /**
33
26
  * Make a fetch request, and extract the `result` from the JSON response.
34
27
  */
35
28
  export async function fetchResult<ResponseType>(
36
- resource: string,
37
- init: RequestInit = {},
38
- queryParams?: URLSearchParams
29
+ resource: string,
30
+ init: RequestInit = {},
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
39
33
  ): Promise<ResponseType> {
40
- const json = await fetchInternal<FetchResult<ResponseType>>(
41
- resource,
42
- init,
43
- queryParams
44
- );
45
- if (json.success) {
46
- return json.result;
47
- } else {
48
- throwFetchError(resource, json);
49
- }
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
+ }
50
45
  }
51
46
 
52
47
  /**
@@ -55,48 +50,72 @@ export async function fetchResult<ResponseType>(
55
50
  * and repeating the request if the results are paginated.
56
51
  */
57
52
  export async function fetchListResult<ResponseType>(
58
- resource: string,
59
- init: RequestInit = {},
60
- queryParams?: URLSearchParams
53
+ resource: string,
54
+ init: RequestInit = {},
55
+ queryParams?: URLSearchParams
61
56
  ): Promise<ResponseType[]> {
62
- const results: ResponseType[] = [];
63
- let getMoreResults = true;
64
- let cursor: string | undefined;
65
- while (getMoreResults) {
66
- if (cursor) {
67
- queryParams = new URLSearchParams(queryParams);
68
- queryParams.set("cursor", cursor);
69
- }
70
- const json = await fetchInternal<FetchResult<ResponseType[]>>(
71
- resource,
72
- init,
73
- queryParams
74
- );
75
- if (json.success) {
76
- results.push(...json.result);
77
- if (hasCursor(json.result_info)) {
78
- cursor = json.result_info?.cursor;
79
- } else {
80
- getMoreResults = false;
81
- }
82
- } else {
83
- throwFetchError(resource, json);
84
- }
85
- }
86
- 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;
87
82
  }
88
83
 
89
84
  function throwFetchError(
90
- resource: string,
91
- response: FetchResult<unknown>
85
+ resource: string,
86
+ response: FetchResult<unknown>
92
87
  ): never {
93
- response.messages.forEach((message) => console.warn(message));
94
- const errors = response.errors
95
- .map((error) => `${error.code}: ${error.message}`)
96
- .join("\n");
97
- throw new Error(`Failed to fetch ${resource} - ${errors}`);
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;
98
102
  }
99
103
 
100
104
  function hasCursor(result_info: unknown): result_info is { cursor: string } {
101
- return (result_info as { cursor } | undefined)?.cursor !== undefined;
105
+ const cursor = (result_info as { cursor: string } | undefined)?.cursor;
106
+ return cursor !== undefined && cursor !== null && cursor !== "";
107
+ }
108
+
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
+ );
102
121
  }
@@ -1,9 +1,21 @@
1
- import fetch from "node-fetch";
2
- import type { RequestInit, HeadersInit } from "node-fetch";
3
- import { getAPIToken, loginOrRefreshIfRequired } from "../user";
1
+ import assert from "node:assert";
2
+ import { fetch, Headers } from "undici";
3
+ import { version as wranglerVersion } from "../../package.json";
4
+ import { getEnvironmentVariableFactory } from "../environment-variables";
5
+ import { ParseError, parseJSON } from "../parse";
6
+ import { loginOrRefreshIfRequired, requireApiToken } from "../user";
7
+ import type { ApiCredentials } from "../user";
8
+ import type { URLSearchParams } from "node:url";
9
+ import type { RequestInit, HeadersInit } from "undici";
4
10
 
5
- export const CF_API_BASE_URL =
6
- process.env.CF_API_BASE_URL || "https://api.cloudflare.com/client/v4";
11
+ /**
12
+ * Get the URL to use to access the Cloudflare API.
13
+ */
14
+ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
15
+ variableName: "CLOUDFLARE_API_BASE_URL",
16
+ deprecatedName: "CF_API_BASE_URL",
17
+ defaultValue: "https://api.cloudflare.com/client/v4",
18
+ });
7
19
 
8
20
  /**
9
21
  * Make a fetch request to the Cloudflare API.
@@ -15,55 +27,123 @@ export const CF_API_BASE_URL =
15
27
  * This function should not be used directly, instead use the functions in `cfetch/index.ts`.
16
28
  */
17
29
  export async function fetchInternal<ResponseType>(
18
- resource: string,
19
- init: RequestInit = {},
20
- queryParams?: URLSearchParams
30
+ resource: string,
31
+ init: RequestInit = {},
32
+ queryParams?: URLSearchParams,
33
+ abortSignal?: AbortSignal
21
34
  ): Promise<ResponseType> {
22
- await requireLoggedIn();
23
- const apiToken = requireApiToken();
24
- const headers = cloneHeaders(init.headers);
25
- addAuthorizationHeader(headers, apiToken);
35
+ assert(
36
+ resource.startsWith("/"),
37
+ `CF API fetch - resource path must start with a "/" but got "${resource}"`
38
+ );
39
+ await requireLoggedIn();
40
+ const apiToken = requireApiToken();
41
+ const headers = cloneHeaders(init.headers);
42
+ addAuthorizationHeaderIfUnspecified(headers, apiToken);
43
+ addUserAgent(headers);
26
44
 
27
- const queryString = queryParams ? `?${queryParams.toString()}` : "";
28
- const response = await fetch(`${CF_API_BASE_URL}${resource}${queryString}`, {
29
- method: "GET",
30
- ...init,
31
- headers,
32
- });
45
+ const queryString = queryParams ? `?${queryParams.toString()}` : "";
46
+ const method = init.method ?? "GET";
47
+ const response = await fetch(
48
+ `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
49
+ {
50
+ method,
51
+ ...init,
52
+ headers,
53
+ signal: abortSignal,
54
+ }
55
+ );
56
+ const jsonText = await response.text();
57
+ try {
58
+ return parseJSON<ResponseType>(jsonText);
59
+ } catch (err) {
60
+ throw new ParseError({
61
+ text: "Received a malformed response from the API",
62
+ notes: [
63
+ {
64
+ text: truncate(jsonText, 100),
65
+ },
66
+ {
67
+ text: `${method} ${resource} -> ${response.status} ${response.statusText}`,
68
+ },
69
+ ],
70
+ });
71
+ }
72
+ }
33
73
 
34
- if (response.ok) {
35
- return (await response.json()) as ResponseType;
36
- } else {
37
- throw new Error(
38
- `Failed to fetch ${resource} - ${response.status}: ${response.statusText}`
39
- );
40
- }
74
+ function truncate(text: string, maxLength: number): string {
75
+ const { length } = text;
76
+ if (length <= maxLength) {
77
+ return text;
78
+ }
79
+ return `${text.substring(0, maxLength)}... (length = ${length})`;
41
80
  }
42
81
 
43
- function cloneHeaders(headers: HeadersInit | undefined): HeadersInit {
44
- return { ...headers };
82
+ function cloneHeaders(
83
+ headers: HeadersInit | undefined
84
+ ): Record<string, string> {
85
+ return headers instanceof Headers
86
+ ? Object.fromEntries(headers.entries())
87
+ : Array.isArray(headers)
88
+ ? Object.fromEntries(headers)
89
+ : { ...headers };
45
90
  }
46
91
 
47
92
  async function requireLoggedIn(): Promise<void> {
48
- const loggedIn = await loginOrRefreshIfRequired();
49
- if (!loggedIn) {
50
- throw new Error("Not logged in.");
51
- }
93
+ const loggedIn = await loginOrRefreshIfRequired();
94
+ if (!loggedIn) {
95
+ throw new Error("Not logged in.");
96
+ }
97
+ }
98
+
99
+ function addAuthorizationHeaderIfUnspecified(
100
+ headers: Record<string, string>,
101
+ auth: ApiCredentials
102
+ ): void {
103
+ if (!("Authorization" in headers)) {
104
+ if ("apiToken" in auth) {
105
+ headers["Authorization"] = `Bearer ${auth.apiToken}`;
106
+ } else {
107
+ headers["X-Auth-Key"] = auth.authKey;
108
+ headers["X-Auth-Email"] = auth.authEmail;
109
+ }
110
+ }
52
111
  }
53
112
 
54
- function requireApiToken(): string {
55
- const apiToken = getAPIToken();
56
- if (!apiToken) {
57
- throw new Error("No API token found.");
58
- }
59
- return apiToken;
113
+ function addUserAgent(headers: Record<string, string>): void {
114
+ headers["User-Agent"] = `wrangler/${wranglerVersion}`;
60
115
  }
61
116
 
62
- function addAuthorizationHeader(headers: HeadersInit, apiToken: string): void {
63
- if (headers["Authorization"]) {
64
- throw new Error(
65
- "The request already specifies an authorisation header - cannot add a new one."
66
- );
67
- }
68
- headers["Authorization"] = `Bearer ${apiToken}`;
117
+ /**
118
+ * The implementation for fetching a kv value from the cloudflare API.
119
+ * We special-case this one call, because it's the only API call that
120
+ * doesn't return json. We inline the implementation and try not to share
121
+ * any code with the other calls. We should push back on any new APIs that
122
+ * try to introduce non-"standard" response structures.
123
+ *
124
+ * Note: any calls to fetchKVGetValue must call encodeURIComponent on key
125
+ * before passing it
126
+ */
127
+
128
+ export async function fetchKVGetValue(
129
+ accountId: string,
130
+ namespaceId: string,
131
+ key: string
132
+ ): Promise<ArrayBuffer> {
133
+ await requireLoggedIn();
134
+ const auth = requireApiToken();
135
+ const headers: Record<string, string> = {};
136
+ addAuthorizationHeaderIfUnspecified(headers, auth);
137
+ const resource = `${getCloudflareAPIBaseURL()}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`;
138
+ const response = await fetch(resource, {
139
+ method: "GET",
140
+ headers,
141
+ });
142
+ if (response.ok) {
143
+ return await response.arrayBuffer();
144
+ } else {
145
+ throw new Error(
146
+ `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
147
+ );
148
+ }
69
149
  }