wrangler 0.0.0-e2f9bb2 → 0.0.0-e322475

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 (103) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/kv-asset-handler.js +1 -0
  5. package/package.json +15 -7
  6. package/pages/functions/buildPlugin.ts +13 -0
  7. package/pages/functions/buildWorker.ts +14 -1
  8. package/pages/functions/template-plugin.ts +3 -2
  9. package/src/__tests__/configuration.test.ts +652 -229
  10. package/src/__tests__/dev.test.tsx +372 -79
  11. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  12. package/src/__tests__/helpers/mock-console.ts +11 -1
  13. package/src/__tests__/helpers/mock-dialogs.ts +75 -11
  14. package/src/__tests__/helpers/mock-process.ts +24 -0
  15. package/src/__tests__/index.test.ts +81 -72
  16. package/src/__tests__/init.test.ts +1360 -410
  17. package/src/__tests__/jest.setup.ts +38 -0
  18. package/src/__tests__/kv.test.ts +218 -152
  19. package/src/__tests__/logger.test.ts +26 -0
  20. package/src/__tests__/package-manager.test.ts +154 -7
  21. package/src/__tests__/pages.test.ts +478 -53
  22. package/src/__tests__/parse.test.ts +8 -0
  23. package/src/__tests__/publish.test.ts +1614 -333
  24. package/src/__tests__/r2.test.ts +55 -36
  25. package/src/__tests__/secret.test.ts +39 -4
  26. package/src/__tests__/user.test.ts +1 -0
  27. package/src/__tests__/whoami.test.tsx +34 -0
  28. package/src/abort.d.ts +3 -0
  29. package/src/bundle-reporter.tsx +29 -0
  30. package/src/bundle.ts +42 -7
  31. package/src/cfetch/index.ts +21 -4
  32. package/src/cfetch/internal.ts +25 -20
  33. package/src/config/config.ts +11 -2
  34. package/src/config/environment.ts +41 -15
  35. package/src/config/index.ts +162 -0
  36. package/src/config/validation-helpers.ts +10 -1
  37. package/src/config/validation.ts +234 -78
  38. package/src/create-worker-preview.ts +32 -22
  39. package/src/create-worker-upload-form.ts +22 -8
  40. package/src/dev/dev.tsx +48 -67
  41. package/src/dev/local.tsx +16 -7
  42. package/src/dev/remote.tsx +57 -21
  43. package/src/dev/use-esbuild.ts +1 -7
  44. package/src/dialogs.tsx +48 -0
  45. package/src/durable.ts +102 -0
  46. package/src/entry.ts +71 -10
  47. package/src/generate-auth-url.ts +33 -0
  48. package/src/generate-random-state.ts +16 -0
  49. package/src/git-client.ts +42 -0
  50. package/src/index.tsx +619 -338
  51. package/src/inspect.ts +39 -0
  52. package/src/kv.ts +116 -27
  53. package/src/logger.ts +8 -1
  54. package/src/open-in-browser.ts +3 -12
  55. package/src/package-manager.ts +50 -3
  56. package/src/pages.tsx +434 -207
  57. package/src/parse.ts +44 -5
  58. package/src/proxy.ts +66 -44
  59. package/src/publish.ts +407 -137
  60. package/src/sites.tsx +103 -28
  61. package/src/user.tsx +35 -26
  62. package/src/whoami.tsx +3 -2
  63. package/src/worker.ts +10 -1
  64. package/src/zones.ts +73 -0
  65. package/templates/new-worker-scheduled.js +17 -0
  66. package/templates/new-worker-scheduled.ts +32 -0
  67. package/templates/new-worker.ts +16 -1
  68. package/templates/no-op-worker.js +10 -0
  69. package/templates/static-asset-facade.js +2 -6
  70. package/wrangler-dist/cli.js +46122 -32396
  71. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  72. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  73. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  74. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  75. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  76. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  77. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  78. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  79. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  80. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  81. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  82. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  83. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  84. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  85. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  86. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  87. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  88. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  89. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  90. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  91. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  92. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  93. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  94. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  95. package/vendor/wrangler-mime/LICENSE +0 -21
  96. package/vendor/wrangler-mime/Mime.js +0 -97
  97. package/vendor/wrangler-mime/README.md +0 -187
  98. package/vendor/wrangler-mime/cli.js +0 -46
  99. package/vendor/wrangler-mime/index.js +0 -4
  100. package/vendor/wrangler-mime/lite.js +0 -4
  101. package/vendor/wrangler-mime/package.json +0 -52
  102. package/vendor/wrangler-mime/types/other.js +0 -1
  103. package/vendor/wrangler-mime/types/standard.js +0 -1
@@ -17,6 +17,33 @@ describe("wrangler", () => {
17
17
 
18
18
  describe("r2", () => {
19
19
  describe("bucket", () => {
20
+ it("should show the correct help when an invalid command is passed", async () => {
21
+ await expect(() =>
22
+ runWrangler("r2 bucket foo")
23
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unknown argument: foo"`);
24
+ expect(std.err).toMatchInlineSnapshot(`
25
+ "X [ERROR] Unknown argument: foo
26
+
27
+ "
28
+ `);
29
+ expect(std.out).toMatchInlineSnapshot(`
30
+ "
31
+ wrangler r2 bucket
32
+
33
+ Manage R2 buckets
34
+
35
+ Commands:
36
+ wrangler r2 bucket create <name> Create a new R2 bucket
37
+ wrangler r2 bucket list List R2 buckets
38
+ wrangler r2 bucket delete <name> Delete an R2 bucket
39
+
40
+ Flags:
41
+ -c, --config Path to .toml configuration file [string]
42
+ -h, --help Show help [boolean]
43
+ -v, --version Show version number [boolean]"
44
+ `);
45
+ });
46
+
20
47
  describe("list", () => {
21
48
  function mockListRequest(buckets: R2BucketInfo[]) {
22
49
  const requests = { count: 0 };
@@ -69,10 +96,7 @@ describe("wrangler", () => {
69
96
  );
70
97
  expect(std.out).toMatchInlineSnapshot(`
71
98
  "
72
- "
73
- `);
74
- expect(std.err).toMatchInlineSnapshot(`
75
- "wrangler r2 bucket create <name>
99
+ wrangler r2 bucket create <name>
76
100
 
77
101
  Create a new R2 bucket
78
102
 
@@ -80,11 +104,12 @@ describe("wrangler", () => {
80
104
  name The name of the new bucket [string] [required]
81
105
 
82
106
  Flags:
83
- -c, --config Path to .toml configuration file [string]
84
- -h, --help Show help [boolean]
85
- -v, --version Show version number [boolean]
86
- --legacy-env Use legacy environments [boolean]
87
- X [ERROR] Not enough non-option arguments: got 0, need at least 1
107
+ -c, --config Path to .toml configuration file [string]
108
+ -h, --help Show help [boolean]
109
+ -v, --version Show version number [boolean]"
110
+ `);
111
+ expect(std.err).toMatchInlineSnapshot(`
112
+ "X [ERROR] Not enough non-option arguments: got 0, need at least 1
88
113
 
89
114
  "
90
115
  `);
@@ -98,10 +123,7 @@ describe("wrangler", () => {
98
123
  );
99
124
  expect(std.out).toMatchInlineSnapshot(`
100
125
  "
101
- "
102
- `);
103
- expect(std.err).toMatchInlineSnapshot(`
104
- "wrangler r2 bucket create <name>
126
+ wrangler r2 bucket create <name>
105
127
 
106
128
  Create a new R2 bucket
107
129
 
@@ -109,11 +131,12 @@ describe("wrangler", () => {
109
131
  name The name of the new bucket [string] [required]
110
132
 
111
133
  Flags:
112
- -c, --config Path to .toml configuration file [string]
113
- -h, --help Show help [boolean]
114
- -v, --version Show version number [boolean]
115
- --legacy-env Use legacy environments [boolean]
116
- X [ERROR] Unknown arguments: def, ghi
134
+ -c, --config Path to .toml configuration file [string]
135
+ -h, --help Show help [boolean]
136
+ -v, --version Show version number [boolean]"
137
+ `);
138
+ expect(std.err).toMatchInlineSnapshot(`
139
+ "X [ERROR] Unknown arguments: def, ghi
117
140
 
118
141
  "
119
142
  `);
@@ -153,10 +176,7 @@ describe("wrangler", () => {
153
176
  );
154
177
  expect(std.out).toMatchInlineSnapshot(`
155
178
  "
156
- "
157
- `);
158
- expect(std.err).toMatchInlineSnapshot(`
159
- "wrangler r2 bucket delete <name>
179
+ wrangler r2 bucket delete <name>
160
180
 
161
181
  Delete an R2 bucket
162
182
 
@@ -164,11 +184,12 @@ describe("wrangler", () => {
164
184
  name The name of the bucket to delete [string] [required]
165
185
 
166
186
  Flags:
167
- -c, --config Path to .toml configuration file [string]
168
- -h, --help Show help [boolean]
169
- -v, --version Show version number [boolean]
170
- --legacy-env Use legacy environments [boolean]
171
- X [ERROR] Not enough non-option arguments: got 0, need at least 1
187
+ -c, --config Path to .toml configuration file [string]
188
+ -h, --help Show help [boolean]
189
+ -v, --version Show version number [boolean]"
190
+ `);
191
+ expect(std.err).toMatchInlineSnapshot(`
192
+ "X [ERROR] Not enough non-option arguments: got 0, need at least 1
172
193
 
173
194
  "
174
195
  `);
@@ -182,10 +203,7 @@ describe("wrangler", () => {
182
203
  );
183
204
  expect(std.out).toMatchInlineSnapshot(`
184
205
  "
185
- "
186
- `);
187
- expect(std.err).toMatchInlineSnapshot(`
188
- "wrangler r2 bucket delete <name>
206
+ wrangler r2 bucket delete <name>
189
207
 
190
208
  Delete an R2 bucket
191
209
 
@@ -193,11 +211,12 @@ describe("wrangler", () => {
193
211
  name The name of the bucket to delete [string] [required]
194
212
 
195
213
  Flags:
196
- -c, --config Path to .toml configuration file [string]
197
- -h, --help Show help [boolean]
198
- -v, --version Show version number [boolean]
199
- --legacy-env Use legacy environments [boolean]
200
- X [ERROR] Unknown arguments: def, ghi
214
+ -c, --config Path to .toml configuration file [string]
215
+ -h, --help Show help [boolean]
216
+ -v, --version Show version number [boolean]"
217
+ `);
218
+ expect(std.err).toMatchInlineSnapshot(`
219
+ "X [ERROR] Unknown arguments: def, ghi
201
220
 
202
221
  "
203
222
  `);
@@ -60,6 +60,22 @@ describe("wrangler secret", () => {
60
60
  describe("interactive", () => {
61
61
  useMockStdin({ isTTY: true });
62
62
 
63
+ it("should trim stdin secret value", async () => {
64
+ mockPrompt({
65
+ text: "Enter a secret value:",
66
+ type: "password",
67
+ result: `hunter2
68
+ `,
69
+ });
70
+
71
+ mockPutRequest({ name: `secret-name`, text: `hunter2` });
72
+ await runWrangler("secret put secret-name --name script-name");
73
+ expect(std.out).toMatchInlineSnapshot(`
74
+ "🌀 Creating the secret for script script-name
75
+ ✨ Success! Uploaded secret secret-name"
76
+ `);
77
+ });
78
+
63
79
  it("should create a secret", async () => {
64
80
  mockPrompt({
65
81
  text: "Enter a secret value:",
@@ -132,7 +148,7 @@ describe("wrangler secret", () => {
132
148
  }
133
149
  expect(std.out).toMatchInlineSnapshot(`
134
150
  "
135
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
151
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
136
152
  `);
137
153
  expect(std.err).toMatchInlineSnapshot(`
138
154
  "X [ERROR] Missing script name
@@ -146,6 +162,25 @@ describe("wrangler secret", () => {
146
162
  describe("non-interactive", () => {
147
163
  const mockStdIn = useMockStdin({ isTTY: false });
148
164
 
165
+ it("should trim stdin secret value, from piped input", async () => {
166
+ mockPutRequest({ name: "the-key", text: "the-secret" });
167
+ // Pipe the secret in as three chunks to test that we reconstitute it correctly.
168
+ mockStdIn.send(
169
+ `the`,
170
+ `-`,
171
+ `secret
172
+ ` // whitespace & newline being removed
173
+ );
174
+ await runWrangler("secret put the-key --name script-name");
175
+
176
+ expect(std.out).toMatchInlineSnapshot(`
177
+ "🌀 Creating the secret for script script-name
178
+ ✨ Success! Uploaded secret the-key"
179
+ `);
180
+ expect(std.warn).toMatchInlineSnapshot(`""`);
181
+ expect(std.err).toMatchInlineSnapshot(`""`);
182
+ });
183
+
149
184
  it("should create a secret, from piped input", async () => {
150
185
  mockPutRequest({ name: "the-key", text: "the-secret" });
151
186
  // Pipe the secret in as three chunks to test that we reconstitute it correctly.
@@ -169,7 +204,7 @@ describe("wrangler secret", () => {
169
204
 
170
205
  expect(std.out).toMatchInlineSnapshot(`
171
206
  "
172
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
207
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
173
208
  `);
174
209
  expect(std.warn).toMatchInlineSnapshot(`""`);
175
210
  });
@@ -337,7 +372,7 @@ describe("wrangler secret", () => {
337
372
  }
338
373
  expect(std.out).toMatchInlineSnapshot(`
339
374
  "
340
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
375
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
341
376
  `);
342
377
  expect(std.err).toMatchInlineSnapshot(`
343
378
  "X [ERROR] Missing script name
@@ -433,7 +468,7 @@ describe("wrangler secret", () => {
433
468
  }
434
469
  expect(std.out).toMatchInlineSnapshot(`
435
470
  "
436
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
471
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
437
472
  `);
438
473
  expect(std.err).toMatchInlineSnapshot(`
439
474
  "X [ERROR] Missing script name
@@ -46,6 +46,7 @@ describe("User", () => {
46
46
 
47
47
  expect(std.out).toMatchInlineSnapshot(`
48
48
  "Attempting to login via OAuth...
49
+ Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
49
50
  Successfully logged in."
50
51
  `);
51
52
 
@@ -9,6 +9,8 @@ import { runInTempDir } from "./helpers/run-in-tmp";
9
9
  import type { UserInfo } from "../whoami";
10
10
 
11
11
  describe("getUserInfo()", () => {
12
+ const ENV_COPY = process.env;
13
+
12
14
  runInTempDir({ homedir: "./home" });
13
15
  const std = mockConsoleMethods();
14
16
  const { setIsTTY } = useMockIsTTY();
@@ -17,6 +19,10 @@ describe("getUserInfo()", () => {
17
19
  setIsTTY(true);
18
20
  });
19
21
 
22
+ afterEach(() => {
23
+ process.env = ENV_COPY;
24
+ });
25
+
20
26
  it("should return undefined if there is no config file", async () => {
21
27
  const userInfo = await getUserInfo();
22
28
  expect(userInfo).toBeUndefined();
@@ -28,6 +34,34 @@ describe("getUserInfo()", () => {
28
34
  expect(userInfo).toBeUndefined();
29
35
  });
30
36
 
37
+ it("should say it's using an API token when one is set", async () => {
38
+ process.env = {
39
+ CLOUDFLARE_API_TOKEN: "123456789",
40
+ };
41
+ setMockResponse("/user", () => {
42
+ return { email: "user@example.com" };
43
+ });
44
+ setMockResponse("/accounts", () => {
45
+ return [
46
+ { name: "Account One", id: "account-1" },
47
+ { name: "Account Two", id: "account-2" },
48
+ { name: "Account Three", id: "account-3" },
49
+ ];
50
+ });
51
+
52
+ const userInfo = await getUserInfo();
53
+ expect(userInfo).toEqual({
54
+ authType: "API",
55
+ apiToken: "123456789",
56
+ email: "user@example.com",
57
+ accounts: [
58
+ { name: "Account One", id: "account-1" },
59
+ { name: "Account Two", id: "account-2" },
60
+ { name: "Account Three", id: "account-3" },
61
+ ],
62
+ });
63
+ });
64
+
31
65
  it("should return the user's email and accounts if authenticated via config token", async () => {
32
66
  writeAuthConfigFile({ oauth_token: "some-oauth-token" });
33
67
 
package/src/abort.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare interface AbortSignal {
2
+ addEventListener(event: "abort", handler: () => void);
3
+ }
@@ -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 CHANGED
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import * as fs from "node:fs";
3
+ import { builtinModules } from "node:module";
3
4
  import * as path from "node:path";
4
5
  import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
5
6
  import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
@@ -16,6 +17,33 @@ type BundleResult = {
16
17
  stop: (() => void) | undefined;
17
18
  };
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
+
19
47
  /**
20
48
  * Generate a bundle for the worker identified by the arguments passed in.
21
49
  */
@@ -60,13 +88,14 @@ export async function bundleWorker(
60
88
  format: entry.format,
61
89
  rules,
62
90
  });
91
+
63
92
  const result = await esbuild.build({
64
93
  ...getEntryPoint(entry.file, serveAssetsFromWorker),
65
94
  bundle: true,
66
95
  absWorkingDir: entry.directory,
67
96
  outdir: destination,
68
97
  external: ["__STATIC_CONTENT_MANIFEST"],
69
- format: "esm",
98
+ format: entry.format === "modules" ? "esm" : "iife",
70
99
  target: "es2020",
71
100
  sourcemap: true,
72
101
  minify,
@@ -87,7 +116,9 @@ export async function bundleWorker(
87
116
  moduleCollector.plugin,
88
117
  ...(nodeCompat
89
118
  ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
90
- : []),
119
+ : // we use checkForNodeBuiltinsPlugin to throw a nicer error
120
+ // if we find node builtins when nodeCompat isn't turned on
121
+ [checkForNodeBuiltinsPlugin]),
91
122
  ],
92
123
  ...(jsxFactory && { jsxFactory }),
93
124
  ...(jsxFragment && { jsxFragment }),
@@ -123,9 +154,7 @@ export async function bundleWorker(
123
154
  };
124
155
  }
125
156
 
126
- type EntryPoint =
127
- | { stdin: esbuild.StdinOptions; nodePaths: string[] }
128
- | { entryPoints: string[] };
157
+ type EntryPoint = { stdin: esbuild.StdinOptions } | { entryPoints: string[] };
129
158
 
130
159
  /**
131
160
  * Create an object that describes the entry point for esbuild.
@@ -146,11 +175,17 @@ function getEntryPoint(
146
175
  path.join(__dirname, "../templates/static-asset-facade.js"),
147
176
  "utf8"
148
177
  )
149
- .replace("__ENTRY_POINT__", entryFile),
178
+ // on windows, escape backslashes in the path (`\`)
179
+ .replace("__ENTRY_POINT__", entryFile.replaceAll("\\", "\\\\"))
180
+ .replace(
181
+ "__KV_ASSET_HANDLER__",
182
+ path
183
+ .join(__dirname, "../kv-asset-handler.js")
184
+ .replaceAll("\\", "\\\\")
185
+ ),
150
186
  sourcefile: "static-asset-facade.js",
151
187
  resolveDir: path.dirname(entryFile),
152
188
  },
153
- nodePaths: [path.join(__dirname, "../vendor")],
154
189
  };
155
190
  } else {
156
191
  return { entryPoints: [entryFile] };
@@ -10,6 +10,7 @@ export { getCloudflareAPIBaseURL as getCloudflareApiBaseUrl } from "./internal";
10
10
  export interface FetchError {
11
11
  code: number;
12
12
  message: string;
13
+ error_chain?: FetchError[];
13
14
  }
14
15
  export interface FetchResult<ResponseType = unknown> {
15
16
  success: boolean;
@@ -27,12 +28,14 @@ export { fetchKVGetValue } from "./internal";
27
28
  export async function fetchResult<ResponseType>(
28
29
  resource: string,
29
30
  init: RequestInit = {},
30
- queryParams?: URLSearchParams
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
31
33
  ): Promise<ResponseType> {
32
34
  const json = await fetchInternal<FetchResult<ResponseType>>(
33
35
  resource,
34
36
  init,
35
- queryParams
37
+ queryParams,
38
+ abortSignal
36
39
  );
37
40
  if (json.success) {
38
41
  return json.result;
@@ -83,9 +86,9 @@ function throwFetchError(
83
86
  response: FetchResult<unknown>
84
87
  ): never {
85
88
  const error = new ParseError({
86
- text: "Received a bad response from the API",
89
+ text: `A request to the Cloudflare API (${resource}) failed.`,
87
90
  notes: response.errors.map((err) => ({
88
- text: err.code ? `${err.message} [code: ${err.code}]` : err.message,
91
+ text: renderError(err),
89
92
  })),
90
93
  });
91
94
  // add the first error code directly to this error
@@ -102,3 +105,17 @@ function hasCursor(result_info: unknown): result_info is { cursor: string } {
102
105
  const cursor = (result_info as { cursor: string } | undefined)?.cursor;
103
106
  return cursor !== undefined && cursor !== null && cursor !== "";
104
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
+ );
121
+ }
@@ -1,7 +1,9 @@
1
+ import assert from "node:assert";
1
2
  import { fetch, Headers } from "undici";
3
+ import { version as wranglerVersion } from "../../package.json";
2
4
  import { getEnvironmentVariableFactory } from "../environment-variables";
3
5
  import { ParseError, parseJSON } from "../parse";
4
- import { getAPIToken, loginOrRefreshIfRequired } from "../user";
6
+ import { loginOrRefreshIfRequired, requireApiToken } from "../user";
5
7
  import type { URLSearchParams } from "node:url";
6
8
  import type { RequestInit, HeadersInit } from "undici";
7
9
 
@@ -26,12 +28,18 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
26
28
  export async function fetchInternal<ResponseType>(
27
29
  resource: string,
28
30
  init: RequestInit = {},
29
- queryParams?: URLSearchParams
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
30
33
  ): Promise<ResponseType> {
34
+ assert(
35
+ resource.startsWith("/"),
36
+ `CF API fetch - resource path must start with a "/" but got "${resource}"`
37
+ );
31
38
  await requireLoggedIn();
32
39
  const apiToken = requireApiToken();
33
40
  const headers = cloneHeaders(init.headers);
34
- addAuthorizationHeader(headers, apiToken);
41
+ addAuthorizationHeaderIfUnspecified(headers, apiToken);
42
+ addUserAgent(headers);
35
43
 
36
44
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
37
45
  const method = init.method ?? "GET";
@@ -41,11 +49,12 @@ export async function fetchInternal<ResponseType>(
41
49
  method,
42
50
  ...init,
43
51
  headers,
52
+ signal: abortSignal,
44
53
  }
45
54
  );
46
55
  const jsonText = await response.text();
47
56
  try {
48
- return parseJSON(jsonText) as ResponseType;
57
+ return parseJSON<ResponseType>(jsonText);
49
58
  } catch (err) {
50
59
  throw new ParseError({
51
60
  text: "Received a malformed response from the API",
@@ -86,24 +95,17 @@ async function requireLoggedIn(): Promise<void> {
86
95
  }
87
96
  }
88
97
 
89
- function requireApiToken(): string {
90
- const authToken = getAPIToken();
91
- if (!authToken) {
92
- throw new Error("No API token found.");
93
- }
94
- return authToken;
95
- }
96
-
97
- function addAuthorizationHeader(
98
+ function addAuthorizationHeaderIfUnspecified(
98
99
  headers: Record<string, string>,
99
100
  apiToken: string
100
101
  ): void {
101
- if ("Authorization" in headers) {
102
- throw new Error(
103
- "The request already specifies an authorisation header - cannot add a new one."
104
- );
102
+ if (!("Authorization" in headers)) {
103
+ headers["Authorization"] = `Bearer ${apiToken}`;
105
104
  }
106
- headers["Authorization"] = `Bearer ${apiToken}`;
105
+ }
106
+
107
+ function addUserAgent(headers: Record<string, string>): void {
108
+ headers["User-Agent"] = `wrangler/${wranglerVersion}`;
107
109
  }
108
110
 
109
111
  /**
@@ -112,13 +114,16 @@ function addAuthorizationHeader(
112
114
  * doesn't return json. We inline the implementation and try not to share
113
115
  * any code with the other calls. We should push back on any new APIs that
114
116
  * try to introduce non-"standard" response structures.
117
+ *
118
+ * Note: any calls to fetchKVGetValue must call encodeURIComponent on key
119
+ * before passing it
115
120
  */
116
121
 
117
122
  export async function fetchKVGetValue(
118
123
  accountId: string,
119
124
  namespaceId: string,
120
125
  key: string
121
- ): Promise<string> {
126
+ ): Promise<ArrayBuffer> {
122
127
  await requireLoggedIn();
123
128
  const apiToken = requireApiToken();
124
129
  const headers = { Authorization: `Bearer ${apiToken}` };
@@ -128,7 +133,7 @@ export async function fetchKVGetValue(
128
133
  headers,
129
134
  });
130
135
  if (response.ok) {
131
- return await response.text();
136
+ return await response.arrayBuffer();
132
137
  } else {
133
138
  throw new Error(
134
139
  `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
@@ -114,6 +114,15 @@ export interface ConfigFields<Dev extends RawDevConfig> {
114
114
  }
115
115
  | undefined;
116
116
 
117
+ /**
118
+ * Serve a folder of static assets with your Worker, without any additional code.
119
+ * This can either be a string, or an object with additional config fields.
120
+ */
121
+ assets:
122
+ | string
123
+ | { bucket: string; include: string[]; exclude: string[] }
124
+ | undefined;
125
+
117
126
  /**
118
127
  * A list of wasm modules that your worker should be bound to. This is
119
128
  * the "legacy" way of binding to a wasm module. ES module workers should
@@ -161,7 +170,7 @@ export interface DevConfig {
161
170
  *
162
171
  * @default `8787`
163
172
  */
164
- port: number;
173
+ port: number | undefined;
165
174
 
166
175
  /**
167
176
  * Protocol that local wrangler dev server listens to requests on.
@@ -184,7 +193,7 @@ export interface DevConfig {
184
193
  /**
185
194
  * Host to forward requests to, defaults to the host of the first route of project
186
195
  */
187
- host: Config["route"];
196
+ host: string | undefined;
188
197
  }
189
198
 
190
199
  export type RawDevConfig = Partial<DevConfig>;