wrangler 2.0.3 → 2.0.7

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.
@@ -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
 
@@ -82,8 +106,10 @@ describe("wrangler", () => {
82
106
  Flags:
83
107
  -c, --config Path to .toml configuration file [string]
84
108
  -h, --help Show help [boolean]
85
- -v, --version Show version number [boolean]
86
- X [ERROR] Not enough non-option arguments: got 0, need at least 1
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
87
113
 
88
114
  "
89
115
  `);
@@ -97,10 +123,7 @@ describe("wrangler", () => {
97
123
  );
98
124
  expect(std.out).toMatchInlineSnapshot(`
99
125
  "
100
- "
101
- `);
102
- expect(std.err).toMatchInlineSnapshot(`
103
- "wrangler r2 bucket create <name>
126
+ wrangler r2 bucket create <name>
104
127
 
105
128
  Create a new R2 bucket
106
129
 
@@ -110,8 +133,10 @@ describe("wrangler", () => {
110
133
  Flags:
111
134
  -c, --config Path to .toml configuration file [string]
112
135
  -h, --help Show help [boolean]
113
- -v, --version Show version number [boolean]
114
- X [ERROR] Unknown arguments: def, ghi
136
+ -v, --version Show version number [boolean]"
137
+ `);
138
+ expect(std.err).toMatchInlineSnapshot(`
139
+ "X [ERROR] Unknown arguments: def, ghi
115
140
 
116
141
  "
117
142
  `);
@@ -151,10 +176,7 @@ describe("wrangler", () => {
151
176
  );
152
177
  expect(std.out).toMatchInlineSnapshot(`
153
178
  "
154
- "
155
- `);
156
- expect(std.err).toMatchInlineSnapshot(`
157
- "wrangler r2 bucket delete <name>
179
+ wrangler r2 bucket delete <name>
158
180
 
159
181
  Delete an R2 bucket
160
182
 
@@ -164,8 +186,10 @@ describe("wrangler", () => {
164
186
  Flags:
165
187
  -c, --config Path to .toml configuration file [string]
166
188
  -h, --help Show help [boolean]
167
- -v, --version Show version number [boolean]
168
- X [ERROR] Not enough non-option arguments: got 0, need at least 1
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
169
193
 
170
194
  "
171
195
  `);
@@ -179,10 +203,7 @@ describe("wrangler", () => {
179
203
  );
180
204
  expect(std.out).toMatchInlineSnapshot(`
181
205
  "
182
- "
183
- `);
184
- expect(std.err).toMatchInlineSnapshot(`
185
- "wrangler r2 bucket delete <name>
206
+ wrangler r2 bucket delete <name>
186
207
 
187
208
  Delete an R2 bucket
188
209
 
@@ -192,8 +213,10 @@ describe("wrangler", () => {
192
213
  Flags:
193
214
  -c, --config Path to .toml configuration file [string]
194
215
  -h, --help Show help [boolean]
195
- -v, --version Show version number [boolean]
196
- X [ERROR] Unknown arguments: def, ghi
216
+ -v, --version Show version number [boolean]"
217
+ `);
218
+ expect(std.err).toMatchInlineSnapshot(`
219
+ "X [ERROR] Unknown arguments: def, ghi
197
220
 
198
221
  "
199
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:",
@@ -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.
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,6 +88,7 @@ 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,
@@ -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 }),
@@ -27,12 +27,14 @@ export { fetchKVGetValue } from "./internal";
27
27
  export async function fetchResult<ResponseType>(
28
28
  resource: string,
29
29
  init: RequestInit = {},
30
- queryParams?: URLSearchParams
30
+ queryParams?: URLSearchParams,
31
+ abortSignal?: AbortSignal
31
32
  ): Promise<ResponseType> {
32
33
  const json = await fetchInternal<FetchResult<ResponseType>>(
33
34
  resource,
34
35
  init,
35
- queryParams
36
+ queryParams,
37
+ abortSignal
36
38
  );
37
39
  if (json.success) {
38
40
  return json.result;
@@ -26,12 +26,13 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
26
26
  export async function fetchInternal<ResponseType>(
27
27
  resource: string,
28
28
  init: RequestInit = {},
29
- queryParams?: URLSearchParams
29
+ queryParams?: URLSearchParams,
30
+ abortSignal?: AbortSignal
30
31
  ): Promise<ResponseType> {
31
32
  await requireLoggedIn();
32
33
  const apiToken = requireApiToken();
33
34
  const headers = cloneHeaders(init.headers);
34
- addAuthorizationHeader(headers, apiToken);
35
+ addAuthorizationHeaderIfUnspecified(headers, apiToken);
35
36
 
36
37
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
37
38
  const method = init.method ?? "GET";
@@ -41,11 +42,12 @@ export async function fetchInternal<ResponseType>(
41
42
  method,
42
43
  ...init,
43
44
  headers,
45
+ signal: abortSignal,
44
46
  }
45
47
  );
46
48
  const jsonText = await response.text();
47
49
  try {
48
- return parseJSON(jsonText) as ResponseType;
50
+ return parseJSON<ResponseType>(jsonText);
49
51
  } catch (err) {
50
52
  throw new ParseError({
51
53
  text: "Received a malformed response from the API",
@@ -94,16 +96,13 @@ function requireApiToken(): string {
94
96
  return authToken;
95
97
  }
96
98
 
97
- function addAuthorizationHeader(
99
+ function addAuthorizationHeaderIfUnspecified(
98
100
  headers: Record<string, string>,
99
101
  apiToken: string
100
102
  ): void {
101
- if ("Authorization" in headers) {
102
- throw new Error(
103
- "The request already specifies an authorisation header - cannot add a new one."
104
- );
103
+ if (!("Authorization" in headers)) {
104
+ headers["Authorization"] = `Bearer ${apiToken}`;
105
105
  }
106
- headers["Authorization"] = `Bearer ${apiToken}`;
107
106
  }
108
107
 
109
108
  /**
@@ -112,6 +111,9 @@ function addAuthorizationHeader(
112
111
  * doesn't return json. We inline the implementation and try not to share
113
112
  * any code with the other calls. We should push back on any new APIs that
114
113
  * try to introduce non-"standard" response structures.
114
+ *
115
+ * Note: any calls to fetchKVGetValue must call encodeURIComponent on key
116
+ * before passing it
115
117
  */
116
118
 
117
119
  export async function fetchKVGetValue(
@@ -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
  *
@@ -2,6 +2,7 @@ import { findUpSync } from "find-up";
2
2
  import { logger } from "../logger";
3
3
  import { parseTOML, readFileSync } from "../parse";
4
4
  import { normalizeAndValidateConfig } from "./validation";
5
+ import type { CfWorkerInit } from "../worker";
5
6
  import type { Config, RawConfig } from "./config";
6
7
 
7
8
  export type {
@@ -61,3 +62,164 @@ export function findWranglerToml(
61
62
  const configPath = findUpSync("wrangler.toml", { cwd: referencePath });
62
63
  return configPath;
63
64
  }
65
+
66
+ /**
67
+ * Print all the bindings a worker using a given config would have access to
68
+ */
69
+ export function printBindings(bindings: CfWorkerInit["bindings"]) {
70
+ const truncate = (item: string | Record<string, unknown>) => {
71
+ const s = typeof item === "string" ? item : JSON.stringify(item);
72
+ const maxLength = 40;
73
+ if (s.length < maxLength) {
74
+ return s;
75
+ }
76
+
77
+ return `${s.substring(0, maxLength - 3)}...`;
78
+ };
79
+
80
+ const output: { type: string; entries: { key: string; value: string }[] }[] =
81
+ [];
82
+
83
+ const {
84
+ data_blobs,
85
+ durable_objects,
86
+ kv_namespaces,
87
+ r2_buckets,
88
+ services,
89
+ text_blobs,
90
+ unsafe,
91
+ vars,
92
+ wasm_modules,
93
+ } = bindings;
94
+
95
+ if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) {
96
+ output.push({
97
+ type: "Data Blobs",
98
+ entries: Object.entries(data_blobs).map(([key, value]) => ({
99
+ key,
100
+ value: truncate(value),
101
+ })),
102
+ });
103
+ }
104
+
105
+ if (durable_objects !== undefined && durable_objects.bindings.length > 0) {
106
+ output.push({
107
+ type: "Durable Objects",
108
+ entries: durable_objects.bindings.map(
109
+ ({ name, class_name, script_name, environment }) => {
110
+ let value = class_name;
111
+ if (script_name) {
112
+ value += ` (defined in ${script_name})`;
113
+ }
114
+ if (environment) {
115
+ value += ` - ${environment}`;
116
+ }
117
+
118
+ return {
119
+ key: name,
120
+ value,
121
+ };
122
+ }
123
+ ),
124
+ });
125
+ }
126
+
127
+ if (kv_namespaces !== undefined && kv_namespaces.length > 0) {
128
+ output.push({
129
+ type: "KV Namespaces",
130
+ entries: kv_namespaces.map(({ binding, id }) => {
131
+ return {
132
+ key: binding,
133
+ value: id,
134
+ };
135
+ }),
136
+ });
137
+ }
138
+
139
+ if (r2_buckets !== undefined && r2_buckets.length > 0) {
140
+ output.push({
141
+ type: "R2 Buckets",
142
+ entries: r2_buckets.map(({ binding, bucket_name }) => {
143
+ return {
144
+ key: binding,
145
+ value: bucket_name,
146
+ };
147
+ }),
148
+ });
149
+ }
150
+
151
+ if (services !== undefined && services.length > 0) {
152
+ output.push({
153
+ type: "Services",
154
+ entries: services.map(({ binding, service, environment }) => {
155
+ let value = service;
156
+ if (environment) {
157
+ value += ` - ${environment}`;
158
+ }
159
+
160
+ return {
161
+ key: binding,
162
+ value,
163
+ };
164
+ }),
165
+ });
166
+ }
167
+
168
+ if (text_blobs !== undefined && Object.keys(text_blobs).length > 0) {
169
+ output.push({
170
+ type: "Text Blobs",
171
+ entries: Object.entries(text_blobs).map(([key, value]) => ({
172
+ key,
173
+ value: truncate(value),
174
+ })),
175
+ });
176
+ }
177
+
178
+ if (unsafe !== undefined && unsafe.length > 0) {
179
+ output.push({
180
+ type: "Unsafe",
181
+ entries: unsafe.map(({ name, type }) => ({
182
+ key: type,
183
+ value: name,
184
+ })),
185
+ });
186
+ }
187
+
188
+ if (vars !== undefined && Object.keys(vars).length > 0) {
189
+ output.push({
190
+ type: "Vars",
191
+ entries: Object.entries(vars).map(([key, value]) => ({
192
+ key,
193
+ value: `"${truncate(`${value}`)}"`,
194
+ })),
195
+ });
196
+ }
197
+
198
+ if (wasm_modules !== undefined && Object.keys(wasm_modules).length > 0) {
199
+ output.push({
200
+ type: "Wasm Modules",
201
+ entries: Object.entries(wasm_modules).map(([key, value]) => ({
202
+ key,
203
+ value: truncate(value),
204
+ })),
205
+ });
206
+ }
207
+
208
+ if (output.length === 0) {
209
+ return;
210
+ }
211
+
212
+ const message = [
213
+ `Your worker has access to the following bindings:`,
214
+ ...output
215
+ .map((bindingGroup) => {
216
+ return [
217
+ `- ${bindingGroup.type}:`,
218
+ bindingGroup.entries.map(({ key, value }) => ` - ${key}: ${value}`),
219
+ ];
220
+ })
221
+ .flat(2),
222
+ ].join("\n");
223
+
224
+ logger.log(message);
225
+ }