wrangler 0.0.0-e043479 → 0.0.0-e0b9366

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 (51) hide show
  1. package/package.json +10 -4
  2. package/pages/functions/filepath-routing.test.ts +125 -25
  3. package/pages/functions/filepath-routing.ts +6 -3
  4. package/src/__tests__/configuration.test.ts +479 -114
  5. package/src/__tests__/dev.test.tsx +162 -8
  6. package/src/__tests__/dev2.test.tsx +152 -0
  7. package/src/__tests__/guess-worker-format.test.ts +9 -17
  8. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  9. package/src/__tests__/helpers/mock-cfetch.ts +3 -3
  10. package/src/__tests__/helpers/mock-kv.ts +9 -16
  11. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  12. package/src/__tests__/https-options.test.ts +127 -0
  13. package/src/__tests__/index.test.ts +125 -0
  14. package/src/__tests__/jest.setup.ts +1 -1
  15. package/src/__tests__/kv.test.ts +27 -53
  16. package/src/__tests__/logout.test.ts +15 -13
  17. package/src/__tests__/pages.test.ts +44 -0
  18. package/src/__tests__/publish.test.ts +504 -32
  19. package/src/__tests__/route.test.ts +45 -0
  20. package/src/__tests__/whoami.test.tsx +21 -7
  21. package/src/bundle.ts +20 -11
  22. package/src/cfetch/internal.ts +11 -5
  23. package/src/config/config.ts +53 -44
  24. package/src/config/validation-helpers.ts +110 -12
  25. package/src/config/validation.ts +59 -41
  26. package/src/{api/preview.ts → create-worker-preview.ts} +23 -4
  27. package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
  28. package/src/dev/dev.tsx +357 -0
  29. package/src/dev/local.tsx +241 -0
  30. package/src/dev/remote.tsx +201 -0
  31. package/src/dev/use-esbuild.ts +100 -0
  32. package/src/entry.ts +111 -9
  33. package/src/https-options.ts +122 -0
  34. package/src/index.tsx +171 -109
  35. package/src/inspect.ts +9 -1
  36. package/src/{kv.tsx → kv.ts} +0 -0
  37. package/src/module-collection.ts +85 -2
  38. package/src/open-in-browser.ts +14 -2
  39. package/src/pages.tsx +1 -1
  40. package/src/proxy.ts +102 -40
  41. package/src/publish.ts +41 -50
  42. package/src/reporting.ts +25 -9
  43. package/src/selfsigned.d.ts +29 -0
  44. package/src/sites.tsx +51 -16
  45. package/src/user.tsx +132 -77
  46. package/src/{api/worker.ts → worker.ts} +0 -23
  47. package/wrangler-dist/cli.js +5012 -3107
  48. package/wrangler-dist/cli.js.map +3 -3
  49. package/src/__tests__/helpers/mock-user.ts +0 -27
  50. package/src/dev.tsx +0 -960
  51. package/src/guess-worker-format.ts +0 -68
@@ -0,0 +1,45 @@
1
+ import { mockConsoleMethods } from "./helpers/mock-console";
2
+ import { runInTempDir } from "./helpers/run-in-tmp";
3
+ import { runWrangler } from "./helpers/run-wrangler";
4
+
5
+ describe("wrangler route", () => {
6
+ mockConsoleMethods();
7
+ runInTempDir();
8
+
9
+ it("shows a deprecation notice when `wrangler route` is run", async () => {
10
+ await expect(runWrangler("route")).rejects
11
+ .toThrowErrorMatchingInlineSnapshot(`
12
+ "DEPRECATION WARNING:
13
+ \`wrangler route\` has been deprecated.
14
+ Please use wrangler.toml and/or \`wrangler publish --routes\` to modify routes"
15
+ `);
16
+ });
17
+
18
+ it("shows a deprecation notice when `wrangler route delete` is run", async () => {
19
+ await expect(runWrangler("route delete")).rejects
20
+ .toThrowErrorMatchingInlineSnapshot(`
21
+ "DEPRECATION WARNING:
22
+ \`wrangler route delete\` has been deprecated.
23
+ Remove the unwanted route(s) from wrangler.toml and run \`wrangler publish\` to remove your worker from those routes."
24
+ `);
25
+ });
26
+
27
+ it("shows a deprecation notice when `wrangler route delete <id>` is run", async () => {
28
+ await expect(runWrangler("route delete some-zone-id")).rejects
29
+ .toThrowErrorMatchingInlineSnapshot(`
30
+ "DEPRECATION WARNING:
31
+ \`wrangler route delete\` has been deprecated.
32
+ Remove the unwanted route(s) from wrangler.toml and run \`wrangler publish\` to remove your worker from those routes."
33
+ `);
34
+ });
35
+
36
+ it("shows a deprecation notice when `wrangler route list` is run", async () => {
37
+ await expect(runWrangler("route list")).rejects
38
+ .toThrowErrorMatchingInlineSnapshot(`
39
+ "DEPRECATION WARNING:
40
+ \`wrangler route list\` has been deprecated.
41
+ Refer to wrangler.toml for a list of routes the worker will be deployed to upon publishing.
42
+ Refer to the Cloudflare Dashboard to see the routes this worker is currently running on."
43
+ `);
44
+ });
45
+ });
@@ -1,30 +1,34 @@
1
1
  import { render } from "ink-testing-library";
2
2
  import React from "react";
3
- import { initialise } from "../user";
3
+ import { reinitialiseAuthTokens, writeAuthConfigFile } from "../user";
4
4
  import { getUserInfo, WhoAmI } from "../whoami";
5
5
  import { setMockResponse } from "./helpers/mock-cfetch";
6
- import { writeUserConfig } from "./helpers/mock-user";
6
+ import { mockConsoleMethods } from "./helpers/mock-console";
7
7
  import { runInTempDir } from "./helpers/run-in-tmp";
8
8
  import type { UserInfo } from "../whoami";
9
9
 
10
10
  describe("getUserInfo()", () => {
11
11
  runInTempDir({ homedir: "./home" });
12
+ const std = mockConsoleMethods();
12
13
 
13
14
  it("should return undefined if there is no config file", async () => {
14
- await initialise();
15
15
  const userInfo = await getUserInfo();
16
16
  expect(userInfo).toBeUndefined();
17
17
  });
18
18
 
19
19
  it("should return undefined if there is an empty config file", async () => {
20
- writeUserConfig();
21
- await initialise();
20
+ writeAuthConfigFile({});
21
+ // Now that we have changed the auth tokens in the wrangler.toml we must reinitialize the user auth state.
22
+ reinitialiseAuthTokens();
22
23
  const userInfo = await getUserInfo();
23
24
  expect(userInfo).toBeUndefined();
24
25
  });
25
26
 
26
27
  it("should return the user's email and accounts if authenticated via config token", async () => {
27
- writeUserConfig("some-oauth-token");
28
+ writeAuthConfigFile({ oauth_token: "some-oauth-token" });
29
+ // Now that we have changed the auth tokens in the wrangler.toml we must reinitialize the user auth state.
30
+ reinitialiseAuthTokens();
31
+
28
32
  setMockResponse("/user", () => {
29
33
  return { email: "user@example.com" };
30
34
  });
@@ -36,7 +40,6 @@ describe("getUserInfo()", () => {
36
40
  ];
37
41
  });
38
42
 
39
- await initialise();
40
43
  const userInfo = await getUserInfo();
41
44
 
42
45
  expect(userInfo).toEqual({
@@ -50,6 +53,17 @@ describe("getUserInfo()", () => {
50
53
  ],
51
54
  });
52
55
  });
56
+
57
+ it("should display a warning message if the config file contains a legacy api_token field", async () => {
58
+ writeAuthConfigFile({ api_token: "API_TOKEN" });
59
+ await reinitialiseAuthTokens();
60
+ await getUserInfo();
61
+ expect(std.warn).toMatchInlineSnapshot(`
62
+ "It looks like you have used Wrangler 1's \`config\` command to login with an API token.
63
+ This is no longer supported in the current version of Wrangler.
64
+ If you wish to authenticate via an API token then please set the \`CLOUDFLARE_API_TOKEN\` environment variable."
65
+ `);
66
+ });
53
67
  });
54
68
 
55
69
  describe("WhoAmI component", () => {
package/src/bundle.ts CHANGED
@@ -2,10 +2,10 @@ import assert from "node:assert";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import * as esbuild from "esbuild";
5
- import makeModuleCollector from "./module-collection";
6
- import type { CfModule, CfScriptFormat } from "./api/worker";
5
+ import createModuleCollector from "./module-collection";
7
6
  import type { Config } from "./config";
8
7
  import type { Entry } from "./entry";
8
+ import type { CfModule } from "./worker";
9
9
 
10
10
  type BundleResult = {
11
11
  modules: CfModule[];
@@ -24,20 +24,29 @@ export async function bundleWorker(
24
24
  serveAssetsFromWorker: boolean;
25
25
  jsxFactory: string | undefined;
26
26
  jsxFragment: string | undefined;
27
- format: CfScriptFormat;
28
27
  rules: Config["rules"];
29
28
  watch?: esbuild.WatchMode;
30
29
  }
31
30
  ): Promise<BundleResult> {
32
- const {
33
- serveAssetsFromWorker,
34
- jsxFactory,
35
- jsxFragment,
36
- format,
31
+ const { serveAssetsFromWorker, jsxFactory, jsxFragment, rules, watch } =
32
+ options;
33
+ const entryDirectory = path.dirname(entry.file);
34
+ const moduleCollector = createModuleCollector({
35
+ wrangler1xlegacyModuleReferences: {
36
+ rootDirectory: entryDirectory,
37
+ fileNames: new Set(
38
+ fs
39
+ .readdirSync(entryDirectory, { withFileTypes: true })
40
+ .filter(
41
+ (dirEntry) =>
42
+ dirEntry.isFile() && dirEntry.name !== path.basename(entry.file)
43
+ )
44
+ .map((dirEnt) => dirEnt.name)
45
+ ),
46
+ },
47
+ format: entry.format,
37
48
  rules,
38
- watch,
39
- } = options;
40
- const moduleCollector = makeModuleCollector({ format, rules });
49
+ });
41
50
  const result = await esbuild.build({
42
51
  ...getEntryPoint(entry.file, serveAssetsFromWorker),
43
52
  bundle: true,
@@ -1,6 +1,7 @@
1
- import { fetch } from "undici";
1
+ import { fetch, Headers } from "undici";
2
2
  import { getEnvironmentVariableFactory } from "../environment-variables";
3
3
  import { ParseError, parseJSON } from "../parse";
4
+ import { reportError } from "../reporting";
4
5
  import { getAPIToken, loginOrRefreshIfRequired } from "../user";
5
6
  import type { URLSearchParams } from "node:url";
6
7
  import type { RequestInit, HeadersInit } from "undici";
@@ -47,6 +48,7 @@ export async function fetchInternal<ResponseType>(
47
48
  try {
48
49
  return parseJSON(jsonText) as ResponseType;
49
50
  } catch (err) {
51
+ await reportError(err as Error);
50
52
  throw new ParseError({
51
53
  text: "Received a malformed response from the API",
52
54
  notes: [
@@ -72,7 +74,11 @@ function truncate(text: string, maxLength: number): string {
72
74
  function cloneHeaders(
73
75
  headers: HeadersInit | undefined
74
76
  ): Record<string, string> {
75
- return { ...headers };
77
+ return headers instanceof Headers
78
+ ? Object.fromEntries(headers.entries())
79
+ : Array.isArray(headers)
80
+ ? Object.fromEntries(headers)
81
+ : { ...headers };
76
82
  }
77
83
 
78
84
  async function requireLoggedIn(): Promise<void> {
@@ -83,11 +89,11 @@ async function requireLoggedIn(): Promise<void> {
83
89
  }
84
90
 
85
91
  function requireApiToken(): string {
86
- const apiToken = getAPIToken();
87
- if (!apiToken) {
92
+ const authToken = getAPIToken();
93
+ if (!authToken) {
88
94
  throw new Error("No API token found.");
89
95
  }
90
- return apiToken;
96
+ return authToken;
91
97
  }
92
98
 
93
99
  function addAuthorizationHeader(
@@ -20,13 +20,16 @@ import type { Environment, RawEnvironment } from "./environment";
20
20
  * - `@breaking`: the deprecation/optionality is a breaking change from wrangler 1.
21
21
  * - `@todo`: there's more work to be done (with details attached).
22
22
  */
23
- export type Config = ConfigFields<Environment> & Environment;
23
+ export type Config = ConfigFields<Environment, DevConfig> & Environment;
24
24
 
25
- export type RawConfig = Partial<ConfigFields<RawEnvironment>> &
25
+ export type RawConfig = Partial<ConfigFields<RawEnvironment, RawDevConfig>> &
26
26
  RawEnvironment &
27
27
  DeprecatedConfigFields;
28
28
 
29
- export interface ConfigFields<Env extends RawEnvironment> {
29
+ export interface ConfigFields<
30
+ Env extends RawEnvironment,
31
+ Dev extends RawDevConfig
32
+ > {
30
33
  configPath: string | undefined;
31
34
 
32
35
  /**
@@ -60,46 +63,8 @@ export interface ConfigFields<Env extends RawEnvironment> {
60
63
 
61
64
  /**
62
65
  * Options to configure the development server that your worker will use.
63
- *
64
- * @default `{}`
65
66
  */
66
- dev: {
67
- /**
68
- * IP address for the local dev server to listen on,
69
- *
70
- * @default `127.0.0.1`
71
- * @todo this needs to be implemented
72
- */
73
- ip?: string;
74
-
75
- /**
76
- * Port for the local dev server to listen on
77
- *
78
- * @default `8787`
79
- */
80
- port?: number;
81
-
82
- /**
83
- * Protocol that local wrangler dev server listens to requests on.
84
- *
85
- * @default `http`
86
- * @todo this needs to be implemented
87
- */
88
- local_protocol?: string;
89
-
90
- /**
91
- * Protocol that wrangler dev forwards requests on
92
- *
93
- * @default `https`
94
- * @todo this needs to be implemented
95
- */
96
- upstream_protocol?: string;
97
-
98
- /**
99
- * Host to forward requests to, defaults to the zone of project
100
- */
101
- host?: string;
102
- };
67
+ dev: Dev;
103
68
 
104
69
  /**
105
70
  * A list of migrations that should be uploaded with your Worker.
@@ -209,11 +174,56 @@ export interface ConfigFields<Env extends RawEnvironment> {
209
174
  cwd?: string;
210
175
  /** The directory to watch for changes while using wrangler dev, defaults to the current working directory */
211
176
  watch_dir?: string;
212
- /** Deprecated field previously used to configure the build and upload of the script. */
177
+ /**
178
+ * Deprecated field previously used to configure the build and upload of the script.
179
+ * @deprecated
180
+ */
213
181
  upload?: DeprecatedUpload;
214
182
  };
215
183
  }
216
184
 
185
+ export interface DevConfig {
186
+ /**
187
+ * IP address for the local dev server to listen on,
188
+ *
189
+ * @default `127.0.0.1`
190
+ * @todo this needs to be implemented
191
+ */
192
+ ip: string;
193
+
194
+ /**
195
+ * Port for the local dev server to listen on
196
+ *
197
+ * @default `8787`
198
+ */
199
+ port: number;
200
+
201
+ /**
202
+ * Protocol that local wrangler dev server listens to requests on.
203
+ *
204
+ * @default `http`
205
+ * @todo this needs to be implemented
206
+ */
207
+ local_protocol: "http" | "https";
208
+
209
+ /**
210
+ * Protocol that wrangler dev forwards requests on
211
+ *
212
+ * Setting this to `http` is not currently implemented.
213
+ * See https://github.com/cloudflare/wrangler2/issues/583
214
+ *
215
+ * @default `https`
216
+ */
217
+ upstream_protocol: "https" | "http";
218
+
219
+ /**
220
+ * Host to forward requests to, defaults to the zone of project
221
+ */
222
+ host: string | undefined;
223
+ }
224
+
225
+ export type RawDevConfig = Partial<DevConfig>;
226
+
217
227
  export interface DeprecatedConfigFields {
218
228
  /**
219
229
  * The project "type". A holdover from wrangler 1.x.
@@ -252,7 +262,6 @@ export interface DeprecatedUpload {
252
262
  * Defaults to the directory containing the wrangler.toml file.
253
263
  *
254
264
  * @deprecated
255
- * @breaking In wrangler 1, this defaults to ./dist, whereas in wrangler 2 it defaults to ./
256
265
  */
257
266
  dir?: string;
258
267
 
@@ -5,7 +5,7 @@ import type { Environment, RawEnvironment } from "./environment";
5
5
  /**
6
6
  * Mark a field as deprecated.
7
7
  *
8
- * This function will add a diagnostics warning if the deprecated field is found in the `rawEnv`.
8
+ * This function will add a diagnostics warning if the deprecated field is found in the `rawEnv` (or an error if it's also a breaking deprecation)
9
9
  * The `fieldPath` is a dot separated property path, e.g. `"build.upload.format"`.
10
10
  */
11
11
  export function deprecated<T extends object>(
@@ -13,11 +13,14 @@ export function deprecated<T extends object>(
13
13
  config: T,
14
14
  fieldPath: DeepKeyOf<T>,
15
15
  message: string,
16
- remove: boolean
16
+ remove: boolean,
17
+ breaking = false
17
18
  ): void {
18
19
  const result = unwindPropertyPath(config, fieldPath);
19
20
  if (result !== undefined && result.field in result.container) {
20
- diagnostics.warnings.push(`DEPRECATION: "${fieldPath}":\n${message}`);
21
+ (breaking ? diagnostics.errors : diagnostics.warnings).push(
22
+ `DEPRECATION: "${fieldPath}":\n${message}`
23
+ );
21
24
  if (remove) {
22
25
  delete (result.container as Record<string, unknown>)[result.field];
23
26
  }
@@ -61,6 +64,41 @@ export function inheritable<K extends keyof Environment>(
61
64
  return (rawEnv[field] as Environment[K]) ?? config?.[field] ?? defaultValue;
62
65
  }
63
66
 
67
+ /**
68
+ * Get an inheritable environment field, but only if we are in legacy environments
69
+ */
70
+ export function inheritableInLegacyEnvironments<K extends keyof Environment>(
71
+ diagnostics: Diagnostics,
72
+ config: Config | undefined,
73
+ rawEnv: RawEnvironment,
74
+ field: K,
75
+ validate: ValidatorFn,
76
+ defaultValue: Environment[K]
77
+ ): Environment[K] {
78
+ return config === undefined || config.legacy_env
79
+ ? inheritable(diagnostics, config, rawEnv, field, validate, defaultValue)
80
+ : notAllowedInNamedServiceEnvironment(diagnostics, config, rawEnv, field);
81
+ }
82
+
83
+ /**
84
+ * Log an error if this named environment is trying to override the value in the top-level
85
+ * environment, which is not allow for this field.
86
+ */
87
+ function notAllowedInNamedServiceEnvironment<K extends keyof Environment>(
88
+ diagnostics: Diagnostics,
89
+ config: Config,
90
+ rawEnv: RawEnvironment,
91
+ field: K
92
+ ): Environment[K] {
93
+ if (field in rawEnv) {
94
+ diagnostics.errors.push(
95
+ `The "${field}" field is not allowed in named service environments.\n` +
96
+ `Please remove the field from this environment.`
97
+ );
98
+ }
99
+ return config[field];
100
+ }
101
+
64
102
  /**
65
103
  * Get a not inheritable environment field, after computing and validating its value.
66
104
  *
@@ -202,15 +240,60 @@ export const isOneOf =
202
240
  (diagnostics, field, value) => {
203
241
  if (value !== undefined && !choices.some((choice) => value === choice)) {
204
242
  diagnostics.errors.push(
205
- `Expected "${field}" field to be on of ${choices} but got ${JSON.stringify(
206
- value
207
- )}.`
243
+ `Expected "${field}" field to be one of ${JSON.stringify(
244
+ choices
245
+ )} but got ${JSON.stringify(value)}.`
208
246
  );
209
247
  return false;
210
248
  }
211
249
  return true;
212
250
  };
213
251
 
252
+ /**
253
+ * Aggregate multiple validator functions
254
+ */
255
+ export const all = (...validations: ValidatorFn[]): ValidatorFn => {
256
+ return (diagnostics, field, value, config) => {
257
+ let passedValidations = true;
258
+
259
+ for (const validate of validations) {
260
+ if (!validate(diagnostics, field, value, config)) {
261
+ passedValidations = false;
262
+ }
263
+ }
264
+
265
+ return passedValidations;
266
+ };
267
+ };
268
+
269
+ /**
270
+ * Check that the field is mutually exclusive with a list of other fields.
271
+ *
272
+ * @param container the container of the fields to check against.
273
+ * @param fields the names of the fields to check against.
274
+ */
275
+ export const isMutuallyExclusiveWith = <T extends RawEnvironment | RawConfig>(
276
+ container: T,
277
+ ...fields: (keyof T)[]
278
+ ): ValidatorFn => {
279
+ return (diagnostics, field, value) => {
280
+ if (value === undefined) {
281
+ return true;
282
+ }
283
+
284
+ for (const exclusiveWith of fields) {
285
+ if (container[exclusiveWith] !== undefined) {
286
+ diagnostics.errors.push(
287
+ `Expected exactly one of ${JSON.stringify([field, ...fields])}.`
288
+ );
289
+ return false;
290
+ }
291
+ }
292
+
293
+ return true;
294
+ };
295
+ };
296
+
214
297
  /**
215
298
  * Validate that the field is a boolean.
216
299
  */
@@ -234,21 +317,28 @@ export const validateRequiredProperty = (
234
317
  container: string,
235
318
  key: string,
236
319
  value: unknown,
237
- type: string
320
+ type: string,
321
+ choices?: unknown[]
238
322
  ): boolean => {
239
323
  if (container) {
240
324
  container += ".";
241
325
  }
242
326
  if (value === undefined) {
243
- diagnostics.errors.push(`ERROR: "${container}${key}" is a required field.`);
327
+ diagnostics.errors.push(`"${container}${key}" is a required field.`);
244
328
  return false;
245
329
  } else if (typeof value !== type) {
246
330
  diagnostics.errors.push(
247
- `ERROR: "${container}${key}" should be of type ${type} but got ${JSON.stringify(
331
+ `Expected "${container}${key}" to be of type ${type} but got ${JSON.stringify(
248
332
  value
249
333
  )}.`
250
334
  );
251
335
  return false;
336
+ } else if (choices) {
337
+ if (
338
+ !isOneOf(...choices)(diagnostics, `${container}${key}`, value, undefined)
339
+ ) {
340
+ return false;
341
+ }
252
342
  }
253
343
  return true;
254
344
  };
@@ -261,10 +351,18 @@ export const validateOptionalProperty = (
261
351
  container: string,
262
352
  key: string,
263
353
  value: unknown,
264
- type: string
354
+ type: string,
355
+ choices?: unknown[]
265
356
  ): boolean => {
266
357
  if (value !== undefined) {
267
- return validateRequiredProperty(diagnostics, container, key, value, type);
358
+ return validateRequiredProperty(
359
+ diagnostics,
360
+ container,
361
+ key,
362
+ value,
363
+ type,
364
+ choices
365
+ );
268
366
  }
269
367
  return true;
270
368
  };
@@ -281,7 +379,7 @@ export const validateTypedArray = (
281
379
  let isValid = true;
282
380
  if (!Array.isArray(value)) {
283
381
  diagnostics.errors.push(
284
- `"${container}" should be an array of ${type}s but got ${JSON.stringify(
382
+ `Expected "${container}" to be an array of ${type}s but got ${JSON.stringify(
285
383
  value
286
384
  )}`
287
385
  );