wrangler 0.0.0-e2f9bb2 → 0.0.0-e3278fa

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 (90) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/package.json +12 -7
  5. package/pages/functions/buildPlugin.ts +13 -0
  6. package/pages/functions/buildWorker.ts +14 -1
  7. package/pages/functions/template-plugin.ts +3 -2
  8. package/src/__tests__/configuration.test.ts +370 -87
  9. package/src/__tests__/dev.test.tsx +298 -77
  10. package/src/__tests__/helpers/mock-dialogs.ts +43 -2
  11. package/src/__tests__/index.test.ts +79 -72
  12. package/src/__tests__/init.test.ts +718 -123
  13. package/src/__tests__/jest.setup.ts +8 -0
  14. package/src/__tests__/kv.test.ts +173 -146
  15. package/src/__tests__/package-manager.test.ts +154 -7
  16. package/src/__tests__/pages.test.ts +478 -53
  17. package/src/__tests__/parse.test.ts +8 -0
  18. package/src/__tests__/publish.test.ts +1094 -215
  19. package/src/__tests__/r2.test.ts +55 -36
  20. package/src/__tests__/secret.test.ts +39 -4
  21. package/src/__tests__/whoami.test.tsx +34 -0
  22. package/src/abort.d.ts +3 -0
  23. package/src/bundle.ts +34 -6
  24. package/src/cfetch/index.ts +21 -4
  25. package/src/cfetch/internal.ts +23 -18
  26. package/src/config/config.ts +2 -2
  27. package/src/config/environment.ts +40 -14
  28. package/src/config/index.ts +162 -0
  29. package/src/config/validation-helpers.ts +10 -1
  30. package/src/config/validation.ts +180 -65
  31. package/src/create-worker-preview.ts +32 -22
  32. package/src/create-worker-upload-form.ts +22 -8
  33. package/src/dev/dev.tsx +12 -18
  34. package/src/dev/local.tsx +10 -0
  35. package/src/dev/remote.tsx +40 -16
  36. package/src/dialogs.tsx +48 -0
  37. package/src/durable.ts +102 -0
  38. package/src/entry.ts +64 -9
  39. package/src/index.tsx +548 -317
  40. package/src/inspect.ts +39 -0
  41. package/src/kv.ts +114 -25
  42. package/src/open-in-browser.ts +5 -12
  43. package/src/package-manager.ts +50 -3
  44. package/src/pages.tsx +434 -207
  45. package/src/parse.ts +23 -4
  46. package/src/proxy.ts +59 -35
  47. package/src/publish.ts +374 -127
  48. package/src/sites.tsx +61 -28
  49. package/src/user.tsx +23 -2
  50. package/src/whoami.tsx +3 -2
  51. package/src/worker.ts +10 -1
  52. package/src/zones.ts +73 -0
  53. package/templates/new-worker-scheduled.js +17 -0
  54. package/templates/new-worker-scheduled.ts +32 -0
  55. package/templates/new-worker.ts +16 -1
  56. package/templates/static-asset-facade.js +0 -4
  57. package/wrangler-dist/cli.js +96965 -83414
  58. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  59. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  60. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  61. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  62. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  63. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  64. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  65. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  66. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  67. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  68. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  69. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  70. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  71. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  72. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  73. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  74. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  75. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  76. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  77. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  78. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  79. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  80. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  81. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  82. package/vendor/wrangler-mime/LICENSE +0 -21
  83. package/vendor/wrangler-mime/Mime.js +0 -97
  84. package/vendor/wrangler-mime/README.md +0 -187
  85. package/vendor/wrangler-mime/cli.js +0 -46
  86. package/vendor/wrangler-mime/index.js +0 -4
  87. package/vendor/wrangler-mime/lite.js +0 -4
  88. package/vendor/wrangler-mime/package.json +0 -52
  89. package/vendor/wrangler-mime/types/other.js +0 -1
  90. 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
@@ -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
+ }
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.
@@ -150,7 +179,6 @@ function getEntryPoint(
150
179
  sourcefile: "static-asset-facade.js",
151
180
  resolveDir: path.dirname(entryFile),
152
181
  },
153
- nodePaths: [path.join(__dirname, "../vendor")],
154
182
  };
155
183
  } else {
156
184
  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,6 +114,9 @@ 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(
@@ -161,7 +161,7 @@ export interface DevConfig {
161
161
  *
162
162
  * @default `8787`
163
163
  */
164
- port: number;
164
+ port: number | undefined;
165
165
 
166
166
  /**
167
167
  * Protocol that local wrangler dev server listens to requests on.
@@ -184,7 +184,7 @@ export interface DevConfig {
184
184
  /**
185
185
  * Host to forward requests to, defaults to the host of the first route of project
186
186
  */
187
- host: Config["route"];
187
+ host: string | undefined;
188
188
  }
189
189
 
190
190
  export type RawDevConfig = Partial<DevConfig>;
@@ -8,6 +8,24 @@ export interface Environment
8
8
  extends EnvironmentInheritable,
9
9
  EnvironmentNonInheritable {}
10
10
 
11
+ export type SimpleRoute = string;
12
+ export type ZoneIdRoute = {
13
+ pattern: string;
14
+ zone_id: string;
15
+ custom_domain?: boolean;
16
+ };
17
+ export type ZoneNameRoute = {
18
+ pattern: string;
19
+ zone_name: string;
20
+ custom_domain?: boolean;
21
+ };
22
+ export type CustomDomainRoute = { pattern: string; custom_domain: boolean };
23
+ export type Route =
24
+ | SimpleRoute
25
+ | ZoneIdRoute
26
+ | ZoneNameRoute
27
+ | CustomDomainRoute;
28
+
11
29
  /**
12
30
  * The `EnvironmentInheritable` interface declares all the configuration fields for an environment
13
31
  * that can be inherited (and overridden) from the top-level environment.
@@ -74,13 +92,7 @@ interface EnvironmentInheritable {
74
92
  *
75
93
  * @inheritable
76
94
  */
77
- routes:
78
- | (
79
- | string
80
- | { pattern: string; zone_id: string }
81
- | { pattern: string; zone_name: string }
82
- )[]
83
- | undefined;
95
+ routes: Route[] | undefined;
84
96
 
85
97
  /**
86
98
  * A route that your worker should be published to. Literally
@@ -91,13 +103,7 @@ interface EnvironmentInheritable {
91
103
  *
92
104
  * @inheritable
93
105
  */
94
- route:
95
- | (
96
- | string
97
- | { pattern: string; zone_id: string }
98
- | { pattern: string; zone_name: string }
99
- )
100
- | undefined;
106
+ route: Route | undefined;
101
107
 
102
108
  /**
103
109
  * Path to a custom tsconfig
@@ -236,6 +242,8 @@ interface EnvironmentNonInheritable {
236
242
  class_name: string;
237
243
  /** The script where the Durable Object is defined (if it's external to this worker) */
238
244
  script_name?: string;
245
+ /** The service environment of the script_name to bind to */
246
+ environment?: string;
239
247
  }[];
240
248
  };
241
249
 
@@ -279,6 +287,24 @@ interface EnvironmentNonInheritable {
279
287
  preview_bucket_name?: string;
280
288
  }[];
281
289
 
290
+ /**
291
+ * Specifies service bindings (worker-to-worker) that are bound to this Worker environment.
292
+ *
293
+ * NOTE: This field is not automatically inherited from the top level environment,
294
+ * and so must be specified in every named environment.
295
+ *
296
+ * @default `[]`
297
+ * @nonInheritable
298
+ */
299
+ services: {
300
+ /** The binding name used to refer to the bound service. */
301
+ binding: string;
302
+ /** The name of the service. */
303
+ service: string;
304
+ /** The environment of the service (e.g. production, staging, etc). */
305
+ environment?: string;
306
+ }[];
307
+
282
308
  /**
283
309
  * "Unsafe" tables for features that aren't directly supported by wrangler.
284
310
  *