wrangler 2.0.26 → 2.0.27

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.0.26",
3
+ "version": "2.0.27",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -1635,6 +1635,7 @@ describe("normalizeAndValidateConfig()", () => {
1635
1635
  id: "KV_ID_2",
1636
1636
  preview_id: 2222,
1637
1637
  },
1638
+ { binding: "VALID", id: "" },
1638
1639
  ],
1639
1640
  } as unknown as RawConfig,
1640
1641
  undefined,
@@ -1654,7 +1655,8 @@ describe("normalizeAndValidateConfig()", () => {
1654
1655
  - \\"kv_namespaces[1]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":\\"VALID\\"}.
1655
1656
  - \\"kv_namespaces[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2000,\\"id\\":2111}.
1656
1657
  - \\"kv_namespaces[2]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":2000,\\"id\\":2111}.
1657
- - \\"kv_namespaces[3]\\" bindings should, optionally, have a string \\"preview_id\\" field but got {\\"binding\\":\\"KV_BINDING_2\\",\\"id\\":\\"KV_ID_2\\",\\"preview_id\\":2222}."
1658
+ - \\"kv_namespaces[3]\\" bindings should, optionally, have a string \\"preview_id\\" field but got {\\"binding\\":\\"KV_BINDING_2\\",\\"id\\":\\"KV_ID_2\\",\\"preview_id\\":2222}.
1659
+ - \\"kv_namespaces[4]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":\\"VALID\\",\\"id\\":\\"\\"}."
1658
1660
  `);
1659
1661
  });
1660
1662
  });
@@ -1740,6 +1742,7 @@ describe("normalizeAndValidateConfig()", () => {
1740
1742
  bucket_name: "R2_BUCKET_2",
1741
1743
  preview_bucket_name: 2555,
1742
1744
  },
1745
+ { binding: "R2_BINDING_1", bucket_name: "" },
1743
1746
  ],
1744
1747
  } as unknown as RawConfig,
1745
1748
  undefined,
@@ -1759,7 +1762,8 @@ describe("normalizeAndValidateConfig()", () => {
1759
1762
  - \\"r2_buckets[1]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_1\\"}.
1760
1763
  - \\"r2_buckets[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2333,\\"bucket_name\\":2444}.
1761
1764
  - \\"r2_buckets[2]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":2333,\\"bucket_name\\":2444}.
1762
- - \\"r2_buckets[3]\\" bindings should, optionally, have a string \\"preview_bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_2\\",\\"bucket_name\\":\\"R2_BUCKET_2\\",\\"preview_bucket_name\\":2555}."
1765
+ - \\"r2_buckets[3]\\" bindings should, optionally, have a string \\"preview_bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_2\\",\\"bucket_name\\":\\"R2_BUCKET_2\\",\\"preview_bucket_name\\":2555}.
1766
+ - \\"r2_buckets[4]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_1\\",\\"bucket_name\\":\\"\\"}."
1763
1767
  `);
1764
1768
  });
1765
1769
  });
@@ -1,6 +1,4 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
2
  import { setup } from "create-cloudflare";
5
3
  import { mockConsoleMethods } from "./helpers/mock-console";
6
4
  import { mockConfirm, clearConfirmMocks } from "./helpers/mock-dialogs";
@@ -73,7 +71,7 @@ describe("generate", () => {
73
71
  ).resolves.toBeUndefined();
74
72
 
75
73
  expect(createCloudflareMock).lastCalledWith(
76
- path.resolve(process.cwd(), "my-worker-1"),
74
+ "my-worker-1",
77
75
  "some-template",
78
76
  { debug: false, force: false, init: true }
79
77
  );
@@ -85,7 +83,7 @@ describe("generate", () => {
85
83
  ).resolves.toBeUndefined();
86
84
 
87
85
  expect(createCloudflareMock).lastCalledWith(
88
- path.resolve(process.cwd(), "my-worker-2"),
86
+ "my-worker-2",
89
87
  "some-template",
90
88
  { debug: false, force: false, init: true }
91
89
  );
@@ -109,6 +109,10 @@ jest.mock("../user/generate-auth-url", () => {
109
109
  };
110
110
  });
111
111
 
112
+ jest.mock("../is-ci", () => {
113
+ return { CI: { isCI: jest.fn().mockImplementation(() => false) } };
114
+ });
115
+
112
116
  jest.mock("../user/generate-random-state", () => {
113
117
  return {
114
118
  generateRandomState: jest.fn().mockImplementation(() => "MOCK_STATE_PARAM"),
@@ -2,9 +2,9 @@ import { mkdirSync } from "node:fs";
2
2
  import fetchMock from "jest-fetch-mock";
3
3
  import { version as wranglerVersion } from "../../package.json";
4
4
  import { purgeConfigCaches, saveToConfigCache } from "../config-cache";
5
+ import { CI } from "../is-ci";
5
6
  import { logger } from "../logger";
6
7
  import { getMetricsDispatcher, getMetricsConfig } from "../metrics";
7
- import { CI } from "../metrics/is-ci";
8
8
  import {
9
9
  CURRENT_METRICS_DATE,
10
10
  readMetricsConfig,
@@ -82,10 +82,11 @@ describe("wrangler", () => {
82
82
  function mockCreateRequest(expectedBucketName: string) {
83
83
  const requests = { count: 0 };
84
84
  setMockResponse(
85
- "/accounts/:accountId/r2/buckets/:bucketName",
86
- "PUT",
87
- ([_url, accountId, bucketName]) => {
85
+ "/accounts/:accountId/r2/buckets",
86
+ "POST",
87
+ ([_url, accountId], { body }) => {
88
88
  expect(accountId).toEqual("some-account-id");
89
+ const bucketName = JSON.parse(body as string).name;
89
90
  expect(bucketName).toEqual(expectedBucketName);
90
91
  requests.count += 1;
91
92
  }
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import fetchMock from "jest-fetch-mock";
4
4
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
5
+ import { CI } from "../is-ci";
5
6
  import {
6
7
  loginOrRefreshIfRequired,
7
8
  readAuthConfigFile,
@@ -18,6 +19,7 @@ import type { Config } from "../config";
18
19
  import type { UserAuthConfig } from "../user";
19
20
 
20
21
  describe("User", () => {
22
+ let isCISpy: jest.SpyInstance;
21
23
  runInTempDir();
22
24
  const std = mockConsoleMethods();
23
25
  const {
@@ -30,6 +32,10 @@ describe("User", () => {
30
32
 
31
33
  const { setIsTTY } = useMockIsTTY();
32
34
 
35
+ beforeEach(() => {
36
+ isCISpy = jest.spyOn(CI, "isCI").mockReturnValue(false);
37
+ });
38
+
33
39
  describe("login", () => {
34
40
  it("should login a user when `wrangler login` is run", async () => {
35
41
  mockOAuthServerCallback();
@@ -132,6 +138,11 @@ describe("User", () => {
132
138
  expect(std.err).toContain("");
133
139
  });
134
140
 
141
+ it("should revert to non-interactive mode if in CI", async () => {
142
+ isCISpy.mockReturnValue(true);
143
+ await expect(loginOrRefreshIfRequired()).resolves.toEqual(false);
144
+ });
145
+
135
146
  it("should revert to non-interactive mode if isTTY throws an error", async () => {
136
147
  setIsTTY({
137
148
  stdin() {
@@ -2,6 +2,7 @@ import assert from "node:assert";
2
2
  import { fetch, Headers } from "undici";
3
3
  import { version as wranglerVersion } from "../../package.json";
4
4
  import { getEnvironmentVariableFactory } from "../environment-variables";
5
+ import { logger } from "../logger";
5
6
  import { ParseError, parseJSON } from "../parse";
6
7
  import { loginOrRefreshIfRequired, requireApiToken } from "../user";
7
8
  import type { ApiCredentials } from "../user";
@@ -44,6 +45,13 @@ export async function fetchInternal<ResponseType>(
44
45
 
45
46
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
46
47
  const method = init.method ?? "GET";
48
+
49
+ logger.debug(
50
+ `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}`
51
+ );
52
+ logger.debug("HEADERS:", JSON.stringify(headers, null, 2));
53
+ logger.debug("INIT:", JSON.stringify(init, null, 2));
54
+ logger.debug("-- END CF API REQUEST");
47
55
  const response = await fetch(
48
56
  `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
49
57
  {
@@ -54,6 +62,15 @@ export async function fetchInternal<ResponseType>(
54
62
  }
55
63
  );
56
64
  const jsonText = await response.text();
65
+ logger.debug(
66
+ "-- START CF API RESPONSE:",
67
+ response.statusText,
68
+ response.status
69
+ );
70
+ logger.debug("HEADERS:", JSON.stringify(response.headers, null, 2));
71
+ logger.debug("RESPONSE:", jsonText);
72
+ logger.debug("-- END CF API RESPONSE");
73
+
57
74
  try {
58
75
  return parseJSON<ResponseType>(jsonText);
59
76
  } catch (err) {
@@ -383,7 +383,7 @@ export const validateRequiredProperty = (
383
383
  container: string,
384
384
  key: string,
385
385
  value: unknown,
386
- type: string,
386
+ type: TypeofType,
387
387
  choices?: unknown[]
388
388
  ): boolean => {
389
389
  if (container) {
@@ -417,7 +417,7 @@ export const validateOptionalProperty = (
417
417
  container: string,
418
418
  key: string,
419
419
  value: unknown,
420
- type: string,
420
+ type: TypeofType,
421
421
  choices?: unknown[]
422
422
  ): boolean => {
423
423
  if (value !== undefined) {
@@ -440,7 +440,7 @@ export const validateTypedArray = (
440
440
  diagnostics: Diagnostics,
441
441
  container: string,
442
442
  value: unknown,
443
- type: string
443
+ type: TypeofType
444
444
  ): boolean => {
445
445
  let isValid = true;
446
446
  if (!Array.isArray(value)) {
@@ -472,7 +472,7 @@ export const validateOptionalTypedArray = (
472
472
  diagnostics: Diagnostics,
473
473
  container: string,
474
474
  value: unknown,
475
- type: string
475
+ type: TypeofType
476
476
  ) => {
477
477
  if (value !== undefined) {
478
478
  return validateTypedArray(diagnostics, container, value, type);
@@ -486,7 +486,7 @@ export const validateOptionalTypedArray = (
486
486
  export const isRequiredProperty = <T extends object>(
487
487
  obj: object,
488
488
  prop: keyof T,
489
- type: string,
489
+ type: TypeofType,
490
490
  choices?: unknown[]
491
491
  ): obj is T =>
492
492
  hasProperty<T>(obj, prop) &&
@@ -499,7 +499,7 @@ export const isRequiredProperty = <T extends object>(
499
499
  export const isOptionalProperty = <T extends object>(
500
500
  obj: object,
501
501
  prop: keyof T,
502
- type: string
502
+ type: TypeofType
503
503
  ): obj is T => !hasProperty<T>(obj, prop) || typeof obj[prop] === type;
504
504
 
505
505
  /**
@@ -582,3 +582,16 @@ const isRecord = (
582
582
  value: unknown
583
583
  ): value is Record<string | number | symbol, unknown> =>
584
584
  typeof value === "object" && value !== null && !Array.isArray(value);
585
+
586
+ /**
587
+ * JavaScript `typeof` operator return values.
588
+ */
589
+ type TypeofType =
590
+ | "string"
591
+ | "number"
592
+ | "bigint"
593
+ | "boolean"
594
+ | "symbol"
595
+ | "undefined"
596
+ | "object"
597
+ | "function";
@@ -1630,7 +1630,10 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1630
1630
  );
1631
1631
  isValid = false;
1632
1632
  }
1633
- if (!isRequiredProperty(value, "id", "string")) {
1633
+ if (
1634
+ !isRequiredProperty(value, "id", "string") ||
1635
+ (value as { id: string }).id.length === 0
1636
+ ) {
1634
1637
  diagnostics.errors.push(
1635
1638
  `"${field}" bindings should have a string "id" field but got ${JSON.stringify(
1636
1639
  value
@@ -1668,7 +1671,10 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1668
1671
  );
1669
1672
  isValid = false;
1670
1673
  }
1671
- if (!isRequiredProperty(value, "bucket_name", "string")) {
1674
+ if (
1675
+ !isRequiredProperty(value, "bucket_name", "string") ||
1676
+ (value as { bucket_name: string }).bucket_name.length === 0
1677
+ ) {
1672
1678
  diagnostics.errors.push(
1673
1679
  `"${field}" bindings should have a string "bucket_name" field but got ${JSON.stringify(
1674
1680
  value
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import * as path from "path";
3
3
  import { findUpSync } from "find-up";
4
+ import { CI } from "./is-ci";
4
5
  import isInteractive from "./is-interactive";
5
6
  import { logger } from "./logger";
6
7
 
@@ -29,7 +30,7 @@ const arrayFormatter = new Intl.ListFormat("en", {
29
30
  });
30
31
 
31
32
  function showCacheMessage(fields: string[], folder: string) {
32
- if (!cacheMessageShown && isInteractive()) {
33
+ if (!cacheMessageShown && isInteractive() && !CI.isCI()) {
33
34
  if (fields.length > 0) {
34
35
  logger.log(
35
36
  `Retrieving cached values for ${arrayFormatter.format(
package/src/generate.ts CHANGED
@@ -88,7 +88,7 @@ export async function generateHandler({
88
88
  `Creating a worker in ${path.basename(creationDirectory)} from ${template}`
89
89
  );
90
90
 
91
- await createCloudflare(creationDirectory, template, {
91
+ await createCloudflare(path.basename(creationDirectory), template, {
92
92
  init: true, // initialize a git repository
93
93
  debug: logger.loggerLevel === "debug",
94
94
  force: false, // do not overwrite an existing directory
File without changes
@@ -6,10 +6,10 @@ import { fetchResult } from "../cfetch";
6
6
  import { getConfigCache, saveToConfigCache } from "../config-cache";
7
7
  import { confirm } from "../dialogs";
8
8
  import { getEnvironmentVariableFactory } from "../environment-variables";
9
+ import { CI } from "../is-ci";
9
10
  import isInteractive from "../is-interactive";
10
11
  import { logger } from "../logger";
11
12
  import { getAPIToken } from "../user";
12
- import { CI } from "./is-ci";
13
13
 
14
14
  /**
15
15
  * The date that the metrics being gathered was last updated in a way that would require
package/src/pages/dev.tsx CHANGED
@@ -367,24 +367,16 @@ export const Handler = async ({
367
367
  );
368
368
  await metrics.sendMetricsEvent("run pages dev");
369
369
 
370
+ CLEANUP_CALLBACKS.push(stop);
371
+
370
372
  waitUntilExit().then(() => {
371
373
  CLEANUP();
372
- stop();
373
374
  process.exit(0);
374
375
  });
375
376
 
376
- process.on("exit", () => {
377
- CLEANUP();
378
- stop();
379
- });
380
- process.on("SIGINT", () => {
381
- CLEANUP();
382
- stop();
383
- });
384
- process.on("SIGTERM", () => {
385
- CLEANUP();
386
- stop();
387
- });
377
+ process.on("exit", CLEANUP);
378
+ process.on("SIGINT", CLEANUP);
379
+ process.on("SIGTERM", CLEANUP);
388
380
  };
389
381
 
390
382
  function isWindows() {
@@ -491,6 +483,8 @@ async function spawnProxyProcess({
491
483
 
492
484
  proxy.on("close", (code) => {
493
485
  logger.error(`Proxy exited with status ${code}.`);
486
+ CLEANUP();
487
+ process.exitCode = code ?? 0;
494
488
  });
495
489
 
496
490
  // Wait for proxy process to start...
package/src/proxy.ts CHANGED
@@ -177,6 +177,7 @@ export function usePreviewServer({
177
177
  const cleanupListeners: (() => void)[] = [];
178
178
 
179
179
  // create a ClientHttp2Session
180
+ logger.debug("PREVIEW URL:", `https://${previewToken.host}`);
180
181
  const remote = connect(`https://${previewToken.host}`);
181
182
  cleanupListeners.push(() => remote.destroy());
182
183
 
@@ -221,6 +222,15 @@ export function usePreviewServer({
221
222
  }
222
223
  }
223
224
  const request = message.pipe(remote.request(headers));
225
+ logger.debug(
226
+ "WORKER REQUEST",
227
+ new Date().toLocaleTimeString(),
228
+ method,
229
+ url
230
+ );
231
+ logger.debug("HEADERS", JSON.stringify(headers, null, 2));
232
+ logger.debug("PREVIEW TOKEN", previewToken);
233
+
224
234
  request.on("response", (responseHeaders) => {
225
235
  const status = responseHeaders[":status"] ?? 500;
226
236
 
package/src/r2.ts CHANGED
@@ -33,10 +33,10 @@ export async function createR2Bucket(
33
33
  accountId: string,
34
34
  bucketName: string
35
35
  ): Promise<void> {
36
- return await fetchResult<void>(
37
- `/accounts/${accountId}/r2/buckets/${bucketName}`,
38
- { method: "PUT" }
39
- );
36
+ return await fetchResult<void>(`/accounts/${accountId}/r2/buckets`, {
37
+ method: "POST",
38
+ body: JSON.stringify({ name: bucketName }),
39
+ });
40
40
  }
41
41
 
42
42
  /**
package/src/user/user.tsx CHANGED
@@ -224,6 +224,7 @@ import {
224
224
  saveToConfigCache,
225
225
  } from "../config-cache";
226
226
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
227
+ import { CI } from "../is-ci";
227
228
  import isInteractive from "../is-interactive";
228
229
  import { logger } from "../logger";
229
230
  import openInBrowser from "../open-in-browser";
@@ -878,10 +879,11 @@ type LoginProps = {
878
879
  export async function loginOrRefreshIfRequired(): Promise<boolean> {
879
880
  // TODO: if there already is a token, then try refreshing
880
881
  // TODO: ask permission before opening browser
882
+ const { isCI } = CI;
881
883
  if (!getAPIToken()) {
882
884
  // Not logged in.
883
885
  // If we are not interactive, we cannot ask the user to login
884
- return isInteractive() && (await login());
886
+ return isInteractive() && !isCI() && (await login());
885
887
  } else if (isAccessTokenExpired()) {
886
888
  // We're logged in, but the refresh token seems to have expired,
887
889
  // so let's try to refresh it
@@ -891,7 +893,7 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
891
893
  return true;
892
894
  } else {
893
895
  // If the refresh token isn't valid, then we ask the user to login again
894
- return isInteractive() && (await login());
896
+ return isInteractive() && !isCI() && (await login());
895
897
  }
896
898
  } else {
897
899
  return true;
@@ -1088,7 +1090,7 @@ export async function getAccountId(): Promise<string | undefined> {
1088
1090
  return accounts[0].id;
1089
1091
  }
1090
1092
 
1091
- if (isInteractive()) {
1093
+ if (isInteractive() && !CI.isCI()) {
1092
1094
  const account = await new Promise<{ id: string; name: string }>(
1093
1095
  (resolve, reject) => {
1094
1096
  const { unmount } = render(
@@ -1128,7 +1130,7 @@ export async function requireAuth(config: {
1128
1130
  }): Promise<string> {
1129
1131
  const loggedIn = await loginOrRefreshIfRequired();
1130
1132
  if (!loggedIn) {
1131
- if (!isInteractive()) {
1133
+ if (!isInteractive() || CI.isCI()) {
1132
1134
  throw new Error(
1133
1135
  "In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."
1134
1136
  );
package/src/whoami.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Text, render } from "ink";
2
2
  import Table from "ink-table";
3
- import React from "react";
3
+ import React, { Fragment } from "react";
4
4
  import { fetchListResult, fetchResult } from "./cfetch";
5
5
  import { logger } from "./logger";
6
6
  import { getAPIToken, getAuthFromEnv, getScopes } from "./user";
@@ -64,12 +64,12 @@ function Permissions(props: {
64
64
  and re-login.
65
65
  </Text>
66
66
  <Text>Scope (Access)</Text>
67
- {permissions.map(([type, name]) => (
68
- <>
67
+ {permissions.map(([scope, access], index) => (
68
+ <Fragment key={`${scope}${index}`}>
69
69
  <Text>
70
- - {type} {name && `(${name})`}
70
+ - {scope} {access && `(${access})`}
71
71
  </Text>
72
- </>
72
+ </Fragment>
73
73
  ))}
74
74
  </>
75
75
  ) : null