From fb3469534b78e12e62cede44507a0aac8f0b5945 Mon Sep 17 00:00:00 2001 From: iray-tno Date: Tue, 5 May 2026 11:40:16 +0900 Subject: [PATCH 1/3] feat: add HTTP proxy support via environment variables Co-Authored-By: Claude Sonnet 4.6 --- packages/backlog-utils/src/http-logger.test.ts | 9 +++++---- packages/backlog-utils/src/http-logger.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/backlog-utils/src/http-logger.test.ts b/packages/backlog-utils/src/http-logger.test.ts index 17091ef..132e0cf 100644 --- a/packages/backlog-utils/src/http-logger.test.ts +++ b/packages/backlog-utils/src/http-logger.test.ts @@ -10,6 +10,7 @@ vi.mock("undici", () => { }); return { Agent: MockAgent, + EnvHttpProxyAgent: MockAgent, setGlobalDispatcher: vi.fn(), }; }); @@ -138,14 +139,14 @@ describe("installHttpLogger", () => { vi.clearAllMocks(); }); - it("calls setGlobalDispatcher with a composed agent", async () => { - const { Agent, setGlobalDispatcher } = await import("undici"); + it("calls setGlobalDispatcher with a composed EnvHttpProxyAgent", async () => { + const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici"); const { installHttpLogger } = await import("./http-logger"); installHttpLogger(); - expect(Agent).toHaveBeenCalled(); - expect(vi.mocked(Agent).prototype.compose).toHaveBeenCalled(); + expect(EnvHttpProxyAgent).toHaveBeenCalled(); + expect(vi.mocked(EnvHttpProxyAgent).prototype.compose).toHaveBeenCalled(); expect(setGlobalDispatcher).toHaveBeenCalled(); }); }); diff --git a/packages/backlog-utils/src/http-logger.ts b/packages/backlog-utils/src/http-logger.ts index 0f89085..bde9e4f 100644 --- a/packages/backlog-utils/src/http-logger.ts +++ b/packages/backlog-utils/src/http-logger.ts @@ -1,5 +1,5 @@ import consola from "consola"; -import { Agent, type Dispatcher, setGlobalDispatcher } from "undici"; +import { EnvHttpProxyAgent, type Dispatcher, setGlobalDispatcher } from "undici"; type Interceptor = (dispatch: Dispatcher["dispatch"]) => Dispatcher["dispatch"]; @@ -65,10 +65,14 @@ const createLoggingInterceptor = (): Interceptor => { }; }; -/** Installs the logging interceptor as the global fetch dispatcher. */ +/** Installs the logging interceptor as the global fetch dispatcher. + * + * Respects the standard HTTP proxy environment variables: + * HTTPS_PROXY / https_proxy, HTTP_PROXY / http_proxy, + * NO_PROXY / no_proxy (comma-separated list of hosts to bypass). + */ const installHttpLogger = (): void => { - const agent = new Agent().compose(createLoggingInterceptor()); - setGlobalDispatcher(agent); + setGlobalDispatcher(new EnvHttpProxyAgent().compose(createLoggingInterceptor())); }; export { createLoggingInterceptor, installHttpLogger }; From 4d7edfd172f72698c1a4eea0aed3beaf7232486b Mon Sep 17 00:00:00 2001 From: iray-tno Date: Sun, 10 May 2026 18:40:34 +0900 Subject: [PATCH 2/3] refactor: split http-logger and http-dispatcher into separate modules Keeps createLoggingInterceptor in http-logger (logging only) and introduces http-dispatcher solely for installHttpDispatcher, which wires EnvHttpProxyAgent with the logging interceptor. Each module now has a single responsibility. Co-Authored-By: Claude Sonnet 4.6 --- apps/cli/src/index.ts | 4 +-- .../backlog-utils/src/http-dispatcher.test.ts | 31 +++++++++++++++++++ packages/backlog-utils/src/http-dispatcher.ts | 15 +++++++++ .../backlog-utils/src/http-logger.test.ts | 29 ----------------- packages/backlog-utils/src/http-logger.ts | 14 ++------- packages/backlog-utils/src/index.ts | 2 +- 6 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 packages/backlog-utils/src/http-dispatcher.test.ts create mode 100644 packages/backlog-utils/src/http-dispatcher.ts diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 49f980c..0ff2215 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -1,11 +1,11 @@ import consola from "consola"; -import { installHttpLogger } from "@repo/backlog-utils"; +import { installHttpDispatcher } from "@repo/backlog-utils"; import { BeeCommand } from "./lib/bee-command"; import { handleError } from "./lib/error"; import pkg from "../package.json" with { type: "json" }; consola.options.formatOptions.date = false; -installHttpLogger(); +installHttpDispatcher(); const program = new BeeCommand("bee").version(pkg.version).description(pkg.description ?? ""); diff --git a/packages/backlog-utils/src/http-dispatcher.test.ts b/packages/backlog-utils/src/http-dispatcher.test.ts new file mode 100644 index 0000000..3d6c953 --- /dev/null +++ b/packages/backlog-utils/src/http-dispatcher.test.ts @@ -0,0 +1,31 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("consola", () => import("@repo/test-utils/mock-consola")); + +vi.mock("undici", () => { + const MockAgent = vi.fn(); + MockAgent.prototype.compose = vi.fn(function (this: unknown) { + return this; + }); + return { + EnvHttpProxyAgent: MockAgent, + setGlobalDispatcher: vi.fn(), + }; +}); + +describe("installHttpDispatcher", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("calls setGlobalDispatcher with a composed EnvHttpProxyAgent", async () => { + const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici"); + const { installHttpDispatcher } = await import("./http-dispatcher"); + + installHttpDispatcher(); + + expect(EnvHttpProxyAgent).toHaveBeenCalled(); + expect(vi.mocked(EnvHttpProxyAgent).prototype.compose).toHaveBeenCalled(); + expect(setGlobalDispatcher).toHaveBeenCalled(); + }); +}); diff --git a/packages/backlog-utils/src/http-dispatcher.ts b/packages/backlog-utils/src/http-dispatcher.ts new file mode 100644 index 0000000..b107b78 --- /dev/null +++ b/packages/backlog-utils/src/http-dispatcher.ts @@ -0,0 +1,15 @@ +import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici"; +import { createLoggingInterceptor } from "./http-logger"; + +/** Installs the global fetch dispatcher with logging and proxy support. + * + * Respects the standard HTTP proxy environment variables: + * HTTPS_PROXY / https_proxy, HTTP_PROXY / http_proxy, + * NO_PROXY / no_proxy (comma-separated list of hosts to bypass). + */ +const installHttpDispatcher = (): void => { + const agent = new EnvHttpProxyAgent().compose(createLoggingInterceptor()); + setGlobalDispatcher(agent); +}; + +export { installHttpDispatcher }; diff --git a/packages/backlog-utils/src/http-logger.test.ts b/packages/backlog-utils/src/http-logger.test.ts index 132e0cf..462e67d 100644 --- a/packages/backlog-utils/src/http-logger.test.ts +++ b/packages/backlog-utils/src/http-logger.test.ts @@ -3,18 +3,6 @@ import consola from "consola"; vi.mock("consola", () => import("@repo/test-utils/mock-consola")); -vi.mock("undici", () => { - const MockAgent = vi.fn(); - MockAgent.prototype.compose = vi.fn(function (this: unknown) { - return this; - }); - return { - Agent: MockAgent, - EnvHttpProxyAgent: MockAgent, - setGlobalDispatcher: vi.fn(), - }; -}); - const newHandler = () => ({ onRequestStart: vi.fn(), onResponseStart: vi.fn(), @@ -133,20 +121,3 @@ describe("createLoggingInterceptor", () => { expect(consola.debug).toHaveBeenCalledWith("→ GET /api/v2/issues?apiKey=***&projectId[]=1"); }); }); - -describe("installHttpLogger", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("calls setGlobalDispatcher with a composed EnvHttpProxyAgent", async () => { - const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici"); - const { installHttpLogger } = await import("./http-logger"); - - installHttpLogger(); - - expect(EnvHttpProxyAgent).toHaveBeenCalled(); - expect(vi.mocked(EnvHttpProxyAgent).prototype.compose).toHaveBeenCalled(); - expect(setGlobalDispatcher).toHaveBeenCalled(); - }); -}); diff --git a/packages/backlog-utils/src/http-logger.ts b/packages/backlog-utils/src/http-logger.ts index bde9e4f..e5db8b8 100644 --- a/packages/backlog-utils/src/http-logger.ts +++ b/packages/backlog-utils/src/http-logger.ts @@ -1,5 +1,5 @@ import consola from "consola"; -import { EnvHttpProxyAgent, type Dispatcher, setGlobalDispatcher } from "undici"; +import { type Dispatcher } from "undici"; type Interceptor = (dispatch: Dispatcher["dispatch"]) => Dispatcher["dispatch"]; @@ -65,14 +65,4 @@ const createLoggingInterceptor = (): Interceptor => { }; }; -/** Installs the logging interceptor as the global fetch dispatcher. - * - * Respects the standard HTTP proxy environment variables: - * HTTPS_PROXY / https_proxy, HTTP_PROXY / http_proxy, - * NO_PROXY / no_proxy (comma-separated list of hosts to bypass). - */ -const installHttpLogger = (): void => { - setGlobalDispatcher(new EnvHttpProxyAgent().compose(createLoggingInterceptor())); -}; - -export { createLoggingInterceptor, installHttpLogger }; +export { createLoggingInterceptor }; diff --git a/packages/backlog-utils/src/index.ts b/packages/backlog-utils/src/index.ts index 2849a33..cf3e75f 100644 --- a/packages/backlog-utils/src/index.ts +++ b/packages/backlog-utils/src/index.ts @@ -46,4 +46,4 @@ export { getRepoRelativePath, parseBacklogRemoteUrl, } from "./git-context"; -export { installHttpLogger } from "./http-logger"; +export { installHttpDispatcher } from "./http-dispatcher"; From 6c9d4219405417f03674750cd73c08bd371e894d Mon Sep 17 00:00:00 2001 From: iray-tno Date: Sun, 10 May 2026 18:40:37 +0900 Subject: [PATCH 3/3] docs: document HTTP proxy environment variables Co-Authored-By: Claude Sonnet 4.6 --- apps/docs/src/content/docs/guides/environment-variables.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/docs/src/content/docs/guides/environment-variables.mdx b/apps/docs/src/content/docs/guides/environment-variables.mdx index baffb6d..2e08244 100644 --- a/apps/docs/src/content/docs/guides/environment-variables.mdx +++ b/apps/docs/src/content/docs/guides/environment-variables.mdx @@ -33,6 +33,12 @@ description: |- `BACKLOG_OAUTH_PORT` : OAuth 認証で使用するコールバックサーバーのポート番号。デフォルトは `5033`。別のアプリケーションとポートが競合する場合に変更できます。 +`HTTPS_PROXY` / `HTTP_PROXY` +: アウトバウンドの HTTPS / HTTP リクエストを指定したプロキシ経由でルーティングします(例: `http://proxy.corp.example.com:8080`)。社内プロキシ環境や制限されたネットワークでの利用に必要です。 + +`NO_PROXY` +: プロキシをバイパスするホストをカンマ区切りで指定します(例: `internal.example.com,localhost`)。サブドメイン(`.example.com`)、ポート指定(`example.com:8080`)、ワイルドカード(`*`)にも対応しています。 + ## よく使うパターン ### プロジェクトの固定