@continuedev/fetch 1.0.6 → 1.0.10

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/dist/fetch.js CHANGED
@@ -1,54 +1,27 @@
1
- import { globalAgent } from "https";
2
- import * as fs from "node:fs";
3
- import tls from "node:tls";
4
1
  import * as followRedirects from "follow-redirects";
5
2
  import { HttpProxyAgent } from "http-proxy-agent";
6
3
  import { HttpsProxyAgent } from "https-proxy-agent";
7
- import fetch from "node-fetch";
4
+ import fetch, { Response } from "node-fetch";
5
+ import { getAgentOptions } from "./getAgentOptions.js";
6
+ import { getProxyFromEnv, shouldBypassProxy } from "./util.js";
8
7
  const { http, https } = followRedirects.default;
9
8
  export async function fetchwithRequestOptions(url_, init, requestOptions) {
10
9
  const url = typeof url_ === "string" ? new URL(url_) : url_;
11
10
  if (url.host === "localhost") {
12
11
  url.host = "127.0.0.1";
13
12
  }
14
- const TIMEOUT = 7200; // 7200 seconds = 2 hours
15
- let globalCerts = [];
16
- if (process.env.IS_BINARY) {
17
- if (Array.isArray(globalAgent.options.ca)) {
18
- globalCerts = [...globalAgent.options.ca.map((cert) => cert.toString())];
19
- }
20
- else if (typeof globalAgent.options.ca !== "undefined") {
21
- globalCerts.push(globalAgent.options.ca.toString());
22
- }
23
- }
24
- const ca = Array.from(new Set([...tls.rootCertificates, ...globalCerts]));
25
- const customCerts = typeof requestOptions?.caBundlePath === "string"
26
- ? [requestOptions?.caBundlePath]
27
- : requestOptions?.caBundlePath;
28
- if (customCerts) {
29
- ca.push(...customCerts.map((customCert) => fs.readFileSync(customCert, "utf8")));
30
- }
31
- const timeout = (requestOptions?.timeout ?? TIMEOUT) * 1000; // measured in ms
32
- const agentOptions = {
33
- ca,
34
- rejectUnauthorized: requestOptions?.verifySsl,
35
- timeout,
36
- sessionTimeout: timeout,
37
- keepAlive: true,
38
- keepAliveMsecs: timeout,
39
- };
40
- // Handle ClientCertificateOptions
41
- if (requestOptions?.clientCertificate) {
42
- agentOptions.cert = fs.readFileSync(requestOptions.clientCertificate.cert, "utf8");
43
- agentOptions.key = fs.readFileSync(requestOptions.clientCertificate.key, "utf8");
44
- if (requestOptions.clientCertificate.passphrase) {
45
- agentOptions.passphrase = requestOptions.clientCertificate.passphrase;
46
- }
13
+ const agentOptions = getAgentOptions(requestOptions);
14
+ // Get proxy from options or environment variables
15
+ let proxy = requestOptions?.proxy;
16
+ if (!proxy) {
17
+ proxy = getProxyFromEnv(url.protocol);
47
18
  }
48
- const proxy = requestOptions?.proxy;
19
+ // Check if should bypass proxy based on requestOptions or NO_PROXY env var
20
+ const shouldBypass = requestOptions?.noProxy?.includes(url.hostname) ||
21
+ shouldBypassProxy(url.hostname);
49
22
  // Create agent
50
23
  const protocol = url.protocol === "https:" ? https : http;
51
- const agent = proxy && !requestOptions?.noProxy?.includes(url.hostname)
24
+ const agent = proxy && !shouldBypass
52
25
  ? protocol === https
53
26
  ? new HttpsProxyAgent(proxy, agentOptions)
54
27
  : new HttpProxyAgent(proxy, agentOptions)
@@ -80,17 +53,29 @@ export async function fetchwithRequestOptions(url_, init, requestOptions) {
80
53
  console.log("Unable to parse HTTP request body: ", e);
81
54
  }
82
55
  // fetch the request with the provided options
83
- const resp = await fetch(url, {
84
- ...init,
85
- body: updatedBody ?? init?.body,
86
- headers: headers,
87
- agent: agent,
88
- });
89
- if (!resp.ok) {
90
- const requestId = resp.headers.get("x-request-id");
91
- if (requestId) {
92
- console.log(`Request ID: ${requestId}, Status: ${resp.status}`);
56
+ try {
57
+ const resp = await fetch(url, {
58
+ ...init,
59
+ body: updatedBody ?? init?.body,
60
+ headers: headers,
61
+ agent: agent,
62
+ });
63
+ if (!resp.ok) {
64
+ const requestId = resp.headers.get("x-request-id");
65
+ if (requestId) {
66
+ console.log(`Request ID: ${requestId}, Status: ${resp.status}`);
67
+ }
68
+ }
69
+ return resp;
70
+ }
71
+ catch (error) {
72
+ if (error instanceof Error && error.name === "AbortError") {
73
+ // Return a Response object that streamResponse etc can handle
74
+ return new Response(null, {
75
+ status: 499, // Client Closed Request
76
+ statusText: "Client Closed Request",
77
+ });
93
78
  }
79
+ throw error;
94
80
  }
95
- return resp;
96
81
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,130 @@
1
+ import { globalAgent } from "https";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { getAgentOptions } from "./getAgentOptions.js";
6
+ // Store original env
7
+ const originalEnv = process.env;
8
+ const originalGlobalAgentOptions = { ...globalAgent.options };
9
+ // Temporary directory for test certificate files
10
+ let tempDir;
11
+ beforeEach(() => {
12
+ // Create a temporary directory for test files
13
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "fetch-test-"));
14
+ process.env = { ...originalEnv };
15
+ // Reset globalAgent for each test
16
+ globalAgent.options = { ...originalGlobalAgentOptions };
17
+ });
18
+ afterEach(() => {
19
+ process.env = originalEnv;
20
+ globalAgent.options = originalGlobalAgentOptions;
21
+ // Clean up temporary directory
22
+ try {
23
+ fs.rmSync(tempDir, { recursive: true, force: true });
24
+ }
25
+ catch (error) {
26
+ console.error(`Failed to remove temp directory: ${error}`);
27
+ }
28
+ });
29
+ // Helper function to create test certificate files
30
+ function createTestCertFile(filename, content) {
31
+ const filePath = path.join(tempDir, filename);
32
+ fs.writeFileSync(filePath, content, "utf8");
33
+ return filePath;
34
+ }
35
+ test("getAgentOptions returns basic configuration with default values", () => {
36
+ const options = getAgentOptions();
37
+ // Check default timeout (7200 seconds = 2 hours = 7,200,000 ms)
38
+ expect(options.timeout).toBe(7200000);
39
+ expect(options.sessionTimeout).toBe(7200000);
40
+ expect(options.keepAliveMsecs).toBe(7200000);
41
+ expect(options.keepAlive).toBe(true);
42
+ // Verify certificates array exists and contains items
43
+ expect(options.ca).toBeInstanceOf(Array);
44
+ expect(options.ca.length).toBeGreaterThan(0);
45
+ // Verify at least one of the real TLS root certificates is included
46
+ // This assumes there's at least one certificate with "CERTIFICATE" in it
47
+ expect(options.ca.some((cert) => cert.includes("CERTIFICATE"))).toBe(true);
48
+ });
49
+ test("getAgentOptions respects custom timeout", () => {
50
+ const customTimeout = 300; // 5 minutes
51
+ const options = getAgentOptions({ timeout: customTimeout });
52
+ // Check timeout values (300 seconds = 300,000 ms)
53
+ expect(options.timeout).toBe(300000);
54
+ expect(options.sessionTimeout).toBe(300000);
55
+ expect(options.keepAliveMsecs).toBe(300000);
56
+ });
57
+ test("getAgentOptions uses verifySsl setting", () => {
58
+ // With verifySsl true
59
+ let options = getAgentOptions({ verifySsl: true });
60
+ expect(options.rejectUnauthorized).toBe(true);
61
+ // With verifySsl false
62
+ options = getAgentOptions({ verifySsl: false });
63
+ expect(options.rejectUnauthorized).toBe(false);
64
+ });
65
+ test("getAgentOptions incorporates custom CA bundle paths", () => {
66
+ // Create a test CA bundle file
67
+ const caBundleContent = "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAMcuSp7chAYdMA==\n-----END CERTIFICATE-----";
68
+ const caBundlePath = createTestCertFile("ca-bundle.pem", caBundleContent);
69
+ // Single string path
70
+ let options = getAgentOptions({ caBundlePath });
71
+ // Verify that our test certificate is included in the CA list
72
+ expect(options.ca).toContain(caBundleContent);
73
+ // Create multiple test CA bundle files
74
+ const caContent1 = "-----BEGIN CERTIFICATE-----\nABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n-----END CERTIFICATE-----";
75
+ const caContent2 = "-----BEGIN CERTIFICATE-----\n0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n-----END CERTIFICATE-----";
76
+ const caPath1 = createTestCertFile("ca1.pem", caContent1);
77
+ const caPath2 = createTestCertFile("ca2.pem", caContent2);
78
+ // Array of paths
79
+ options = getAgentOptions({
80
+ caBundlePath: [caPath1, caPath2],
81
+ });
82
+ // Verify that both test certificates are included in the CA list
83
+ expect(options.ca).toContain(caContent1);
84
+ expect(options.ca).toContain(caContent2);
85
+ });
86
+ test("getAgentOptions includes global certs when running as binary", () => {
87
+ // Set up test certs in globalAgent
88
+ globalAgent.options.ca = ["global-cert-1", "global-cert-2"];
89
+ // Set IS_BINARY environment variable
90
+ process.env.IS_BINARY = "true";
91
+ const options = getAgentOptions();
92
+ // Test for global certs
93
+ expect(options.ca).toContain("global-cert-1");
94
+ expect(options.ca).toContain("global-cert-2");
95
+ });
96
+ test("getAgentOptions handles client certificate configuration", () => {
97
+ // Create test certificate files
98
+ const clientCertContent = "-----BEGIN CERTIFICATE-----\nCLIENTCERT\n-----END CERTIFICATE-----";
99
+ const clientKeyContent = "-----BEGIN PRIVATE KEY-----\nCLIENTKEY\n-----END PRIVATE KEY-----";
100
+ const certPath = createTestCertFile("client.cert", clientCertContent);
101
+ const keyPath = createTestCertFile("client.key", clientKeyContent);
102
+ const clientCertOptions = {
103
+ clientCertificate: {
104
+ cert: certPath,
105
+ key: keyPath,
106
+ passphrase: "secret-passphrase",
107
+ },
108
+ };
109
+ const options = getAgentOptions(clientCertOptions);
110
+ expect(options.cert).toBe(clientCertContent);
111
+ expect(options.key).toBe(clientKeyContent);
112
+ expect(options.passphrase).toBe("secret-passphrase");
113
+ });
114
+ test("getAgentOptions handles client certificate without passphrase", () => {
115
+ // Create test certificate files
116
+ const clientCertContent = "-----BEGIN CERTIFICATE-----\nCLIENTCERT2\n-----END CERTIFICATE-----";
117
+ const clientKeyContent = "-----BEGIN PRIVATE KEY-----\nCLIENTKEY2\n-----END PRIVATE KEY-----";
118
+ const certPath = createTestCertFile("client2.cert", clientCertContent);
119
+ const keyPath = createTestCertFile("client2.key", clientKeyContent);
120
+ const clientCertOptions = {
121
+ clientCertificate: {
122
+ cert: certPath,
123
+ key: keyPath,
124
+ },
125
+ };
126
+ const options = getAgentOptions(clientCertOptions);
127
+ expect(options.cert).toBe(clientCertContent);
128
+ expect(options.key).toBe(clientKeyContent);
129
+ expect(options.passphrase).toBeUndefined();
130
+ });
@@ -0,0 +1,7 @@
1
+ import { RequestOptions } from "@continuedev/config-types";
2
+ /**
3
+ * Prepares agent options based on request options and certificates
4
+ */
5
+ export declare function getAgentOptions(requestOptions?: RequestOptions): {
6
+ [key: string]: any;
7
+ };
@@ -0,0 +1,44 @@
1
+ import { globalAgent } from "https";
2
+ import * as fs from "node:fs";
3
+ import tls from "node:tls";
4
+ /**
5
+ * Prepares agent options based on request options and certificates
6
+ */
7
+ export function getAgentOptions(requestOptions) {
8
+ const TIMEOUT = 7200; // 7200 seconds = 2 hours
9
+ const timeout = (requestOptions?.timeout ?? TIMEOUT) * 1000; // measured in ms
10
+ // Get root certificates
11
+ let globalCerts = [];
12
+ if (process.env.IS_BINARY) {
13
+ if (Array.isArray(globalAgent.options.ca)) {
14
+ globalCerts = [...globalAgent.options.ca.map((cert) => cert.toString())];
15
+ }
16
+ else if (typeof globalAgent.options.ca !== "undefined") {
17
+ globalCerts.push(globalAgent.options.ca.toString());
18
+ }
19
+ }
20
+ const ca = Array.from(new Set([...tls.rootCertificates, ...globalCerts]));
21
+ const customCerts = typeof requestOptions?.caBundlePath === "string"
22
+ ? [requestOptions?.caBundlePath]
23
+ : requestOptions?.caBundlePath;
24
+ if (customCerts) {
25
+ ca.push(...customCerts.map((customCert) => fs.readFileSync(customCert, "utf8")));
26
+ }
27
+ const agentOptions = {
28
+ ca,
29
+ rejectUnauthorized: requestOptions?.verifySsl,
30
+ timeout,
31
+ sessionTimeout: timeout,
32
+ keepAlive: true,
33
+ keepAliveMsecs: timeout,
34
+ };
35
+ // Handle ClientCertificateOptions
36
+ if (requestOptions?.clientCertificate) {
37
+ agentOptions.cert = fs.readFileSync(requestOptions.clientCertificate.cert, "utf8");
38
+ agentOptions.key = fs.readFileSync(requestOptions.clientCertificate.key, "utf8");
39
+ if (requestOptions.clientCertificate.passphrase) {
40
+ agentOptions.passphrase = requestOptions.clientCertificate.passphrase;
41
+ }
42
+ }
43
+ return agentOptions;
44
+ }
package/dist/stream.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare function toAsyncIterable(nodeReadable: NodeJS.ReadableStream): AsyncGenerator<Uint8Array>;
2
2
  export declare function streamResponse(response: Response): AsyncGenerator<string>;
3
+ export declare function parseDataLine(line: string): any;
3
4
  export declare function streamSse(response: Response): AsyncGenerator<any>;
4
5
  export declare function streamJSON(response: Response): AsyncGenerator<any>;
package/dist/stream.js CHANGED
@@ -1,10 +1,13 @@
1
1
  export async function* toAsyncIterable(nodeReadable) {
2
2
  for await (const chunk of nodeReadable) {
3
- // @ts-expect-error
3
+ // @ts-ignore
4
4
  yield chunk;
5
5
  }
6
6
  }
7
7
  export async function* streamResponse(response) {
8
+ if (response.status === 499) {
9
+ return; // In case of client-side cancellation, just return
10
+ }
8
11
  if (response.status !== 200) {
9
12
  throw new Error(await response.text());
10
13
  }
@@ -13,24 +16,33 @@ export async function* streamResponse(response) {
13
16
  }
14
17
  // Get the major version of Node.js
15
18
  const nodeMajorVersion = parseInt(process.versions.node.split(".")[0], 10);
16
- if (nodeMajorVersion >= 20) {
17
- // Use the new API for Node 20 and above
18
- const stream = ReadableStream.from(response.body);
19
- for await (const chunk of stream.pipeThrough(new TextDecoderStream("utf-8"))) {
20
- yield chunk;
19
+ try {
20
+ if (nodeMajorVersion >= 20) {
21
+ // Use the new API for Node 20 and above
22
+ const stream = ReadableStream.from(response.body);
23
+ for await (const chunk of stream.pipeThrough(new TextDecoderStream("utf-8"))) {
24
+ yield chunk;
25
+ }
26
+ }
27
+ else {
28
+ // Fallback for Node versions below 20
29
+ // Streaming with this method doesn't work as version 20+ does
30
+ const decoder = new TextDecoder("utf-8");
31
+ const nodeStream = response.body;
32
+ for await (const chunk of toAsyncIterable(nodeStream)) {
33
+ yield decoder.decode(chunk, { stream: true });
34
+ }
21
35
  }
22
36
  }
23
- else {
24
- // Fallback for Node versions below 20
25
- // Streaming with this method doesn't work as version 20+ does
26
- const decoder = new TextDecoder("utf-8");
27
- const nodeStream = response.body;
28
- for await (const chunk of toAsyncIterable(nodeStream)) {
29
- yield decoder.decode(chunk, { stream: true });
37
+ catch (e) {
38
+ if (e instanceof Error && e.name.startsWith("AbortError")) {
39
+ return; // In case of client-side cancellation, just return
30
40
  }
41
+ throw e;
31
42
  }
32
43
  }
33
- function parseDataLine(line) {
44
+ // Export for testing purposes
45
+ export function parseDataLine(line) {
34
46
  const json = line.startsWith("data: ")
35
47
  ? line.slice("data: ".length)
36
48
  : line.slice("data:".length);
@@ -42,11 +54,17 @@ function parseDataLine(line) {
42
54
  return data;
43
55
  }
44
56
  catch (e) {
57
+ // If the error was thrown by our error check, rethrow it
58
+ if (e instanceof Error &&
59
+ e.message.startsWith("Error streaming response:")) {
60
+ throw e;
61
+ }
62
+ // Otherwise it's a JSON parsing error
45
63
  throw new Error(`Malformed JSON sent from server: ${json}`);
46
64
  }
47
65
  }
48
66
  function parseSseLine(line) {
49
- if (line.startsWith("data: [DONE]")) {
67
+ if (line.startsWith("data:[DONE]") || line.startsWith("data: [DONE]")) {
50
68
  return { done: true, data: undefined };
51
69
  }
52
70
  if (line.startsWith("data:")) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { Readable } from "stream";
2
+ import { parseDataLine, streamSse } from "./stream.js";
3
+ function createMockResponse(sseLines) {
4
+ // Create a Readable stream that emits the SSE lines
5
+ const stream = new Readable({
6
+ read() {
7
+ for (const line of sseLines) {
8
+ this.push(line + "\n\n");
9
+ }
10
+ this.push(null); // End of stream
11
+ },
12
+ });
13
+ // Minimal Response mock
14
+ return {
15
+ status: 200,
16
+ body: stream,
17
+ text: async () => "",
18
+ };
19
+ }
20
+ describe("streamSse", () => {
21
+ it("yields parsed SSE data objects that ends with `data:[DONE]`", async () => {
22
+ const sseLines = [
23
+ 'data: {"foo": "bar"}',
24
+ 'data: {"baz": 42}',
25
+ "data:[DONE]",
26
+ ];
27
+ const response = createMockResponse(sseLines);
28
+ const results = [];
29
+ for await (const data of streamSse(response)) {
30
+ results.push(data);
31
+ }
32
+ expect(results).toEqual([{ foo: "bar" }, { baz: 42 }]);
33
+ });
34
+ it("yields parsed SSE data objects that ends with `data: [DONE]` (with a space before [DONE]", async () => {
35
+ const sseLines = [
36
+ 'data: {"foo": "bar"}',
37
+ 'data: {"baz": 42}',
38
+ "data: [DONE]",
39
+ ];
40
+ const response = createMockResponse(sseLines);
41
+ const results = [];
42
+ for await (const data of streamSse(response)) {
43
+ results.push(data);
44
+ }
45
+ expect(results).toEqual([{ foo: "bar" }, { baz: 42 }]);
46
+ });
47
+ it("throws on malformed JSON", async () => {
48
+ const sseLines = ['data: {"foo": "bar"', "data:[DONE]"];
49
+ const response = createMockResponse(sseLines);
50
+ const iterator = streamSse(response)[Symbol.asyncIterator]();
51
+ await expect(iterator.next()).rejects.toThrow(/Malformed JSON/);
52
+ });
53
+ });
54
+ describe("parseDataLine", () => {
55
+ test("parseDataLine should parse valid JSON data with 'data: ' prefix", () => {
56
+ const line = 'data: {"message":"hello","status":"ok"}';
57
+ const result = parseDataLine(line);
58
+ expect(result).toEqual({ message: "hello", status: "ok" });
59
+ });
60
+ test("parseDataLine should parse valid JSON data with 'data:' prefix (no space)", () => {
61
+ const line = 'data:{"message":"hello","status":"ok"}';
62
+ const result = parseDataLine(line);
63
+ expect(result).toEqual({ message: "hello", status: "ok" });
64
+ });
65
+ test("parseDataLine should throw error for malformed JSON", () => {
66
+ const line = "data: {invalid json}";
67
+ expect(() => parseDataLine(line)).toThrow("Malformed JSON sent from server");
68
+ });
69
+ test("parseDataLine should throw error when data contains error field", () => {
70
+ const line = 'data: {"error":"something went wrong"}';
71
+ expect(() => parseDataLine(line)).toThrow("Error streaming response: something went wrong");
72
+ });
73
+ test("parseDataLine should handle empty objects", () => {
74
+ const line = "data: {}";
75
+ const result = parseDataLine(line);
76
+ expect(result).toEqual({});
77
+ });
78
+ test("parseDataLine should handle arrays", () => {
79
+ const line = "data: [1,2,3]";
80
+ const result = parseDataLine(line);
81
+ expect(result).toEqual([1, 2, 3]);
82
+ });
83
+ test("parseDataLine should handle nested objects", () => {
84
+ const line = 'data: {"user":{"name":"John","age":30}}';
85
+ const result = parseDataLine(line);
86
+ expect(result).toEqual({ user: { name: "John", age: 30 } });
87
+ });
88
+ });
package/dist/util.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Gets the proxy settings from environment variables
3
+ * @param protocol The URL protocol (http: or https:)
4
+ * @returns The proxy URL if available, otherwise undefined
5
+ */
6
+ export declare function getProxyFromEnv(protocol: string): string | undefined;
7
+ /**
8
+ * Checks if a hostname should bypass proxy based on NO_PROXY environment variable
9
+ * @param hostname The hostname to check
10
+ * @returns True if the hostname should bypass proxy
11
+ */
12
+ export declare function shouldBypassProxy(hostname: string): boolean;
package/dist/util.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Gets the proxy settings from environment variables
3
+ * @param protocol The URL protocol (http: or https:)
4
+ * @returns The proxy URL if available, otherwise undefined
5
+ */
6
+ export function getProxyFromEnv(protocol) {
7
+ if (protocol === "https:") {
8
+ return (process.env.HTTPS_PROXY ||
9
+ process.env.https_proxy ||
10
+ process.env.HTTP_PROXY ||
11
+ process.env.http_proxy);
12
+ }
13
+ else {
14
+ return process.env.HTTP_PROXY || process.env.http_proxy;
15
+ }
16
+ }
17
+ /**
18
+ * Checks if a hostname should bypass proxy based on NO_PROXY environment variable
19
+ * @param hostname The hostname to check
20
+ * @returns True if the hostname should bypass proxy
21
+ */
22
+ export function shouldBypassProxy(hostname) {
23
+ const noProxy = process.env.NO_PROXY || process.env.no_proxy;
24
+ if (!noProxy)
25
+ return false;
26
+ const noProxyItems = noProxy.split(",").map((item) => item.trim());
27
+ return noProxyItems.some((item) => {
28
+ // Exact match
29
+ if (item === hostname)
30
+ return true;
31
+ // Wildcard domain match (*.example.com)
32
+ if (item.startsWith("*.") && hostname.endsWith(item.substring(1)))
33
+ return true;
34
+ // Domain suffix match (.example.com)
35
+ if (item.startsWith(".") && hostname.endsWith(item.slice(1)))
36
+ return true;
37
+ return false;
38
+ });
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ import { jest } from "@jest/globals";
2
+ import { getProxyFromEnv, shouldBypassProxy } from "./util.js";
3
+ // Reset environment variables after each test
4
+ afterEach(() => {
5
+ jest.resetModules();
6
+ process.env = {};
7
+ });
8
+ // Tests for getProxyFromEnv
9
+ test("getProxyFromEnv returns undefined when no proxy is set", () => {
10
+ expect(getProxyFromEnv("http:")).toBeUndefined();
11
+ expect(getProxyFromEnv("https:")).toBeUndefined();
12
+ });
13
+ test("getProxyFromEnv returns HTTP_PROXY for http protocol", () => {
14
+ process.env.HTTP_PROXY = "http://proxy.example.com";
15
+ expect(getProxyFromEnv("http:")).toBe("http://proxy.example.com");
16
+ });
17
+ test("getProxyFromEnv returns lowercase http_proxy for http protocol", () => {
18
+ process.env.http_proxy = "http://proxy.example.com";
19
+ expect(getProxyFromEnv("http:")).toBe("http://proxy.example.com");
20
+ });
21
+ test("getProxyFromEnv prefers HTTP_PROXY over http_proxy for http protocol", () => {
22
+ process.env.HTTP_PROXY = "http://upper.example.com";
23
+ process.env.http_proxy = "http://lower.example.com";
24
+ expect(getProxyFromEnv("http:")).toBe("http://upper.example.com");
25
+ });
26
+ test("getProxyFromEnv returns HTTPS_PROXY for https protocol", () => {
27
+ process.env.HTTPS_PROXY = "https://secure.example.com";
28
+ expect(getProxyFromEnv("https:")).toBe("https://secure.example.com");
29
+ });
30
+ test("getProxyFromEnv returns lowercase https_proxy for https protocol", () => {
31
+ process.env.https_proxy = "https://secure.example.com";
32
+ expect(getProxyFromEnv("https:")).toBe("https://secure.example.com");
33
+ });
34
+ test("getProxyFromEnv falls back to HTTP_PROXY for https protocol when HTTPS_PROXY is not set", () => {
35
+ process.env.HTTP_PROXY = "http://fallback.example.com";
36
+ expect(getProxyFromEnv("https:")).toBe("http://fallback.example.com");
37
+ });
38
+ test("getProxyFromEnv prefers HTTPS_PROXY over other env vars for https protocol", () => {
39
+ process.env.HTTPS_PROXY = "https://preferred.example.com";
40
+ process.env.https_proxy = "https://notused1.example.com";
41
+ process.env.HTTP_PROXY = "http://notused2.example.com";
42
+ process.env.http_proxy = "http://notused3.example.com";
43
+ expect(getProxyFromEnv("https:")).toBe("https://preferred.example.com");
44
+ });
45
+ // Tests for shouldBypassProxy
46
+ test("shouldBypassProxy returns false when NO_PROXY is not set", () => {
47
+ expect(shouldBypassProxy("example.com")).toBe(false);
48
+ });
49
+ test("shouldBypassProxy returns true for exact hostname match", () => {
50
+ process.env.NO_PROXY = "example.com,another.com";
51
+ expect(shouldBypassProxy("example.com")).toBe(true);
52
+ });
53
+ test("shouldBypassProxy returns false when hostname doesn't match any NO_PROXY entry", () => {
54
+ process.env.NO_PROXY = "example.com,another.com";
55
+ expect(shouldBypassProxy("different.com")).toBe(false);
56
+ });
57
+ test("shouldBypassProxy handles lowercase no_proxy", () => {
58
+ process.env.no_proxy = "example.com";
59
+ expect(shouldBypassProxy("example.com")).toBe(true);
60
+ });
61
+ test("shouldBypassProxy works with wildcard domains", () => {
62
+ process.env.NO_PROXY = "*.example.com";
63
+ expect(shouldBypassProxy("sub.example.com")).toBe(true);
64
+ expect(shouldBypassProxy("example.com")).toBe(false);
65
+ expect(shouldBypassProxy("different.com")).toBe(false);
66
+ });
67
+ test("shouldBypassProxy works with domain suffix", () => {
68
+ process.env.NO_PROXY = ".example.com";
69
+ expect(shouldBypassProxy("sub.example.com")).toBe(true);
70
+ expect(shouldBypassProxy("example.com")).toBe(true);
71
+ expect(shouldBypassProxy("different.com")).toBe(false);
72
+ });
73
+ test("shouldBypassProxy handles multiple entries with different patterns", () => {
74
+ process.env.NO_PROXY = "internal.local,*.example.com,.test.com";
75
+ expect(shouldBypassProxy("internal.local")).toBe(true);
76
+ expect(shouldBypassProxy("sub.example.com")).toBe(true);
77
+ expect(shouldBypassProxy("sub.test.com")).toBe(true);
78
+ expect(shouldBypassProxy("test.com")).toBe(true);
79
+ expect(shouldBypassProxy("example.org")).toBe(false);
80
+ });
81
+ test("shouldBypassProxy ignores whitespace in NO_PROXY", () => {
82
+ process.env.NO_PROXY = " example.com , *.test.org ";
83
+ expect(shouldBypassProxy("example.com")).toBe(true);
84
+ expect(shouldBypassProxy("subdomain.test.org")).toBe(true);
85
+ });
@@ -0,0 +1,20 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+
4
+ export default {
5
+ transform: {
6
+ "\\.[jt]sx?$": ["ts-jest", { useESM: true }],
7
+ },
8
+
9
+ moduleNameMapper: {
10
+ "(.+)\\.js": "$1",
11
+ },
12
+ extensionsToTreatAsEsm: [".ts"],
13
+ preset: "ts-jest/presets/default-esm",
14
+ testTimeout: 10000,
15
+ testEnvironment: "node",
16
+ globals: {
17
+ __dirname: path.dirname(fileURLToPath(import.meta.url)),
18
+ __filename: path.resolve(fileURLToPath(import.meta.url)),
19
+ },
20
+ };
@@ -0,0 +1,14 @@
1
+ // This file declares the Jest globals for TypeScript
2
+ import '@jest/globals';
3
+
4
+ declare global {
5
+ const describe: typeof import('@jest/globals').describe;
6
+ const expect: typeof import('@jest/globals').expect;
7
+ const it: typeof import('@jest/globals').it;
8
+ const test: typeof import('@jest/globals').test;
9
+ const beforeAll: typeof import('@jest/globals').beforeAll;
10
+ const afterAll: typeof import('@jest/globals').afterAll;
11
+ const beforeEach: typeof import('@jest/globals').beforeEach;
12
+ const afterEach: typeof import('@jest/globals').afterEach;
13
+ const jest: typeof import('@jest/globals').jest;
14
+ }