wrangler 0.0.0-e9ab55a → 0.0.0-e9e9872

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 (201) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +33 -14
  3. package/bin/wrangler.js +126 -25
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +4894 -0
  6. package/package.json +159 -125
  7. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  8. package/src/__tests__/config-cache.test.ts +42 -0
  9. package/src/__tests__/configuration.test.ts +3946 -0
  10. package/src/__tests__/dev.test.tsx +1155 -63
  11. package/src/__tests__/guess-worker-format.test.ts +85 -0
  12. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  13. package/src/__tests__/helpers/cmd-shim.d.ts +11 -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 +38 -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 +163 -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 +108 -10
  34. package/src/__tests__/kv.test.ts +1433 -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 +6586 -93
  40. package/src/__tests__/pubsub.test.ts +367 -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 +492 -0
  44. package/src/__tests__/tail.test.ts +698 -0
  45. package/src/__tests__/user.test.ts +143 -0
  46. package/src/__tests__/whoami.test.tsx +230 -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 +52 -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 +453 -0
  60. package/src/config/index.ts +238 -0
  61. package/src/config/validation-helpers.ts +584 -0
  62. package/src/config/validation.ts +1745 -0
  63. package/src/config-cache.ts +83 -0
  64. package/src/create-worker-preview.ts +255 -0
  65. package/src/create-worker-upload-form.ts +263 -0
  66. package/src/dev/dev-vars.ts +36 -0
  67. package/src/dev/dev.tsx +417 -0
  68. package/src/dev/local.tsx +354 -0
  69. package/src/dev/remote.tsx +382 -0
  70. package/src/dev/use-esbuild.ts +150 -0
  71. package/src/dev.tsx +527 -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/faye-websocket.d.ts +6 -0
  78. package/src/generate.ts +33 -0
  79. package/src/git-client.ts +42 -0
  80. package/src/global-wrangler-config-path.ts +26 -0
  81. package/src/https-options.ts +127 -0
  82. package/src/index.tsx +1869 -1901
  83. package/src/init.ts +549 -0
  84. package/src/inspect.ts +615 -496
  85. package/src/intl-polyfill.d.ts +139 -0
  86. package/src/is-interactive.ts +12 -0
  87. package/src/kv.ts +430 -0
  88. package/src/logger.ts +73 -0
  89. package/src/miniflare-cli/README.md +30 -0
  90. package/src/miniflare-cli/enum-keys.ts +17 -0
  91. package/src/miniflare-cli/index.ts +53 -0
  92. package/src/miniflare-cli/request-context.ts +40 -0
  93. package/src/module-collection.ts +247 -53
  94. package/src/open-in-browser.ts +17 -0
  95. package/src/package-manager.ts +219 -0
  96. package/src/pages/build.tsx +202 -0
  97. package/src/pages/constants.ts +7 -0
  98. package/src/pages/deployments.tsx +101 -0
  99. package/src/pages/dev.tsx +964 -0
  100. package/src/pages/functions/buildPlugin.ts +105 -0
  101. package/src/pages/functions/buildWorker.ts +151 -0
  102. package/src/pages/functions/filepath-routing.test.ts +234 -0
  103. package/src/pages/functions/filepath-routing.ts +189 -0
  104. package/src/pages/functions/identifiers.ts +78 -0
  105. package/src/pages/functions/routes.ts +151 -0
  106. package/src/pages/index.tsx +84 -0
  107. package/src/pages/projects.tsx +157 -0
  108. package/src/pages/publish.tsx +335 -0
  109. package/src/pages/types.ts +40 -0
  110. package/src/pages/upload.tsx +396 -0
  111. package/src/pages/utils.ts +12 -0
  112. package/src/parse.ts +286 -0
  113. package/src/paths.ts +26 -0
  114. package/src/preview.ts +31 -0
  115. package/src/proxy.ts +428 -254
  116. package/src/publish.ts +764 -366
  117. package/src/pubsub/index.ts +286 -0
  118. package/src/pubsub/pubsub-commands.tsx +577 -0
  119. package/src/r2.ts +50 -0
  120. package/src/selfsigned.d.ts +29 -0
  121. package/src/sites.tsx +357 -103
  122. package/src/tail/filters.ts +279 -0
  123. package/src/tail/index.ts +299 -0
  124. package/src/tail/printing.ts +67 -0
  125. package/src/update-check.ts +19 -0
  126. package/src/user/choose-account.tsx +60 -0
  127. package/src/user/env-vars.ts +46 -0
  128. package/src/user/generate-auth-url.ts +33 -0
  129. package/src/user/generate-random-state.ts +16 -0
  130. package/src/user/index.ts +3 -0
  131. package/src/user/user.tsx +1170 -0
  132. package/src/whoami.tsx +90 -0
  133. package/src/worker-namespace.ts +190 -0
  134. package/src/worker.ts +191 -0
  135. package/src/zones.ts +76 -0
  136. package/templates/checked-fetch.js +17 -0
  137. package/templates/gitignore +171 -0
  138. package/templates/new-worker-scheduled.js +17 -0
  139. package/templates/new-worker-scheduled.ts +32 -0
  140. package/templates/new-worker.js +15 -0
  141. package/templates/new-worker.ts +30 -0
  142. package/templates/no-op-worker.js +10 -0
  143. package/templates/pages-template-plugin.ts +155 -0
  144. package/templates/pages-template-worker.ts +161 -0
  145. package/templates/static-asset-facade.js +49 -0
  146. package/templates/tsconfig.json +105 -0
  147. package/wrangler-dist/cli.d.ts +298 -0
  148. package/wrangler-dist/cli.js +107673 -103262
  149. package/pages/functions/buildWorker.ts +0 -62
  150. package/pages/functions/filepath-routing.test.ts +0 -39
  151. package/pages/functions/filepath-routing.ts +0 -228
  152. package/pages/functions/identifiers.ts +0 -78
  153. package/pages/functions/routes.ts +0 -158
  154. package/pages/functions/template-worker.ts +0 -136
  155. package/src/__tests__/mock-cfetch.ts +0 -171
  156. package/src/__tests__/mock-dialogs.ts +0 -65
  157. package/src/__tests__/run-in-tmp.ts +0 -19
  158. package/src/__tests__/run-wrangler.ts +0 -32
  159. package/src/api/form_data.ts +0 -131
  160. package/src/api/preview.ts +0 -128
  161. package/src/api/worker.ts +0 -155
  162. package/src/config.ts +0 -646
  163. package/src/kv.tsx +0 -276
  164. package/src/pages.tsx +0 -1040
  165. package/src/tail.tsx +0 -73
  166. package/src/user.tsx +0 -1027
  167. package/static-asset-facade.js +0 -47
  168. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  169. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  170. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  171. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  172. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  173. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  174. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  175. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  176. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  177. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  178. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  179. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  180. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  181. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  182. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  183. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  184. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  185. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  186. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  187. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  188. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  189. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  190. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  191. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  192. package/vendor/wrangler-mime/LICENSE +0 -21
  193. package/vendor/wrangler-mime/Mime.js +0 -97
  194. package/vendor/wrangler-mime/README.md +0 -187
  195. package/vendor/wrangler-mime/cli.js +0 -46
  196. package/vendor/wrangler-mime/index.js +0 -4
  197. package/vendor/wrangler-mime/lite.js +0 -4
  198. package/vendor/wrangler-mime/package.json +0 -52
  199. package/vendor/wrangler-mime/types/other.js +0 -1
  200. package/vendor/wrangler-mime/types/standard.js +0 -1
  201. package/wrangler-dist/cli.js.map +0 -7
package/src/api/dev.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { startDev } from "../dev";
2
+ import { logger } from "../logger";
3
+
4
+ import type { RequestInit, Response } from "undici";
5
+
6
+ interface DevOptions {
7
+ env?: string;
8
+ ip?: string;
9
+ port?: number;
10
+ localProtocol?: "http" | "https";
11
+ assets?: string;
12
+ site?: string;
13
+ siteInclude?: string[];
14
+ siteExclude?: string[];
15
+ nodeCompat?: boolean;
16
+ experimentalEnableLocalPersistence?: boolean;
17
+ _: (string | number)[]; //yargs wants this
18
+ $0: string; //yargs wants this
19
+ }
20
+ /**
21
+ * unstable_dev starts a wrangler dev server, and returns a promise that resolves with utility functions to interact with it.
22
+ * @param {string} script
23
+ * @param {DevOptions} options
24
+ */
25
+ export async function unstable_dev(script: string, options: DevOptions) {
26
+ logger.warn(
27
+ `unstable_dev() is experimental\nunstable_dev()'s behaviour will likely change in future releases`
28
+ );
29
+
30
+ return new Promise<{
31
+ stop: () => void;
32
+ fetch: (init?: RequestInit) => Promise<Response | undefined>;
33
+ }>((resolve) => {
34
+ //lmao
35
+ return new Promise<Awaited<ReturnType<typeof startDev>>>((ready) => {
36
+ const devServer = startDev({
37
+ script: script,
38
+ ...options,
39
+ local: true,
40
+ onReady: () => ready(devServer),
41
+ inspect: false,
42
+ logLevel: "none",
43
+ showInteractiveDevSession: false,
44
+ });
45
+ }).then((devServer) => {
46
+ resolve({
47
+ stop: devServer.stop,
48
+ fetch: devServer.fetch,
49
+ });
50
+ });
51
+ });
52
+ }
@@ -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
  }