@continuedev/fetch 1.0.13 → 1.0.14

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/src/util.test.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { afterEach, expect, test, vi } from "vitest";
2
- import { getProxyFromEnv, shouldBypassProxy } from "./util.js";
2
+ import {
3
+ getProxyFromEnv,
4
+ patternMatchesHostname,
5
+ shouldBypassProxy,
6
+ } from "./util.js";
3
7
 
4
8
  // Reset environment variables after each test
5
9
  afterEach(() => {
@@ -52,51 +56,159 @@ test("getProxyFromEnv prefers HTTPS_PROXY over other env vars for https protocol
52
56
  expect(getProxyFromEnv("https:")).toBe("https://preferred.example.com");
53
57
  });
54
58
 
59
+ // Tests for patternMatchesHostname
60
+ test("patternMatchesHostname with exact hostname match", () => {
61
+ expect(patternMatchesHostname("example.com", "example.com")).toBe(true);
62
+ expect(patternMatchesHostname("example.com", "different.com")).toBe(false);
63
+ });
64
+
65
+ test("patternMatchesHostname with wildcard domains", () => {
66
+ expect(patternMatchesHostname("sub.example.com", "*.example.com")).toBe(true);
67
+ expect(patternMatchesHostname("sub.sub.example.com", "*.example.com")).toBe(
68
+ true,
69
+ );
70
+ expect(patternMatchesHostname("example.com", "*.example.com")).toBe(false);
71
+ expect(patternMatchesHostname("sub.different.com", "*.example.com")).toBe(
72
+ false,
73
+ );
74
+ });
75
+
76
+ test("patternMatchesHostname with domain suffix", () => {
77
+ expect(patternMatchesHostname("sub.example.com", ".example.com")).toBe(true);
78
+ expect(patternMatchesHostname("example.com", ".example.com")).toBe(true);
79
+ expect(patternMatchesHostname("different.com", ".example.com")).toBe(false);
80
+ });
81
+
82
+ test("patternMatchesHostname with case insensitivity", () => {
83
+ expect(patternMatchesHostname("EXAMPLE.com", "example.COM")).toBe(true);
84
+ expect(patternMatchesHostname("sub.EXAMPLE.com", "*.example.COM")).toBe(true);
85
+ });
86
+
87
+ // Port handling tests
88
+ test("patternMatchesHostname with exact port match", () => {
89
+ expect(patternMatchesHostname("example.com:8080", "example.com:8080")).toBe(
90
+ true,
91
+ );
92
+ expect(patternMatchesHostname("example.com:8080", "example.com:9090")).toBe(
93
+ false,
94
+ );
95
+ });
96
+
97
+ test("patternMatchesHostname with port in pattern but not in hostname", () => {
98
+ expect(patternMatchesHostname("example.com", "example.com:8080")).toBe(false);
99
+ });
100
+
101
+ test("patternMatchesHostname with port in hostname but not in pattern", () => {
102
+ expect(patternMatchesHostname("example.com:8080", "example.com")).toBe(true);
103
+ });
104
+
105
+ test("patternMatchesHostname with wildcard domains and ports", () => {
106
+ expect(
107
+ patternMatchesHostname("sub.example.com:8080", "*.example.com:8080"),
108
+ ).toBe(true);
109
+ expect(
110
+ patternMatchesHostname("sub.example.com:9090", "*.example.com:8080"),
111
+ ).toBe(false);
112
+ expect(patternMatchesHostname("sub.example.com", "*.example.com:8080")).toBe(
113
+ false,
114
+ );
115
+ });
116
+
117
+ test("patternMatchesHostname with domain suffix and ports", () => {
118
+ expect(
119
+ patternMatchesHostname("sub.example.com:8080", ".example.com:8080"),
120
+ ).toBe(true);
121
+ expect(patternMatchesHostname("example.com:8080", ".example.com:8080")).toBe(
122
+ true,
123
+ );
124
+ expect(
125
+ patternMatchesHostname("sub.example.com:9090", ".example.com:8080"),
126
+ ).toBe(false);
127
+ });
128
+
55
129
  // Tests for shouldBypassProxy
56
130
  test("shouldBypassProxy returns false when NO_PROXY is not set", () => {
57
- expect(shouldBypassProxy("example.com")).toBe(false);
131
+ expect(shouldBypassProxy("example.com", undefined)).toBe(false);
58
132
  });
59
133
 
60
134
  test("shouldBypassProxy returns true for exact hostname match", () => {
61
135
  process.env.NO_PROXY = "example.com,another.com";
62
- expect(shouldBypassProxy("example.com")).toBe(true);
136
+ expect(shouldBypassProxy("example.com", undefined)).toBe(true);
63
137
  });
64
138
 
65
139
  test("shouldBypassProxy returns false when hostname doesn't match any NO_PROXY entry", () => {
66
140
  process.env.NO_PROXY = "example.com,another.com";
67
- expect(shouldBypassProxy("different.com")).toBe(false);
141
+ expect(shouldBypassProxy("different.com", undefined)).toBe(false);
68
142
  });
69
143
 
70
144
  test("shouldBypassProxy handles lowercase no_proxy", () => {
71
145
  process.env.no_proxy = "example.com";
72
- expect(shouldBypassProxy("example.com")).toBe(true);
146
+ expect(shouldBypassProxy("example.com", undefined)).toBe(true);
73
147
  });
74
148
 
75
149
  test("shouldBypassProxy works with wildcard domains", () => {
76
150
  process.env.NO_PROXY = "*.example.com";
77
- expect(shouldBypassProxy("sub.example.com")).toBe(true);
78
- expect(shouldBypassProxy("example.com")).toBe(false);
79
- expect(shouldBypassProxy("different.com")).toBe(false);
151
+ expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
152
+ expect(shouldBypassProxy("example.com", undefined)).toBe(false);
153
+ expect(shouldBypassProxy("different.com", undefined)).toBe(false);
80
154
  });
81
155
 
82
156
  test("shouldBypassProxy works with domain suffix", () => {
83
157
  process.env.NO_PROXY = ".example.com";
84
- expect(shouldBypassProxy("sub.example.com")).toBe(true);
85
- expect(shouldBypassProxy("example.com")).toBe(true);
86
- expect(shouldBypassProxy("different.com")).toBe(false);
158
+ expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
159
+ expect(shouldBypassProxy("example.com", undefined)).toBe(true);
160
+ expect(shouldBypassProxy("different.com", undefined)).toBe(false);
87
161
  });
88
162
 
89
163
  test("shouldBypassProxy handles multiple entries with different patterns", () => {
90
164
  process.env.NO_PROXY = "internal.local,*.example.com,.test.com";
91
- expect(shouldBypassProxy("internal.local")).toBe(true);
92
- expect(shouldBypassProxy("sub.example.com")).toBe(true);
93
- expect(shouldBypassProxy("sub.test.com")).toBe(true);
94
- expect(shouldBypassProxy("test.com")).toBe(true);
95
- expect(shouldBypassProxy("example.org")).toBe(false);
165
+ expect(shouldBypassProxy("internal.local", undefined)).toBe(true);
166
+ expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
167
+ expect(shouldBypassProxy("sub.test.com", undefined)).toBe(true);
168
+ expect(shouldBypassProxy("test.com", undefined)).toBe(true);
169
+ expect(shouldBypassProxy("example.org", undefined)).toBe(false);
96
170
  });
97
171
 
98
172
  test("shouldBypassProxy ignores whitespace in NO_PROXY", () => {
99
173
  process.env.NO_PROXY = " example.com , *.test.org ";
100
- expect(shouldBypassProxy("example.com")).toBe(true);
101
- expect(shouldBypassProxy("subdomain.test.org")).toBe(true);
174
+ expect(shouldBypassProxy("example.com", undefined)).toBe(true);
175
+ expect(shouldBypassProxy("subdomain.test.org", undefined)).toBe(true);
176
+ });
177
+
178
+ test("shouldBypassProxy with ports in NO_PROXY", () => {
179
+ process.env.NO_PROXY = "example.com:8080,*.test.org:443,.internal.net:8443";
180
+ expect(shouldBypassProxy("example.com:8080", undefined)).toBe(true);
181
+ expect(shouldBypassProxy("example.com:9090", undefined)).toBe(false);
182
+ expect(shouldBypassProxy("sub.test.org:443", undefined)).toBe(true);
183
+ expect(shouldBypassProxy("sub.internal.net:8443", undefined)).toBe(true);
184
+ expect(shouldBypassProxy("internal.net:8443", undefined)).toBe(true);
185
+ });
186
+
187
+ test("shouldBypassProxy accepts options with noProxy patterns", () => {
188
+ const options = { noProxy: ["example.com:8080", "*.internal.net"] };
189
+ expect(shouldBypassProxy("example.com:8080", options)).toBe(true);
190
+ expect(shouldBypassProxy("example.com", options)).toBe(false);
191
+ expect(shouldBypassProxy("server.internal.net", options)).toBe(true);
192
+ });
193
+
194
+ test("shouldBypassProxy combines environment and options noProxy patterns", () => {
195
+ process.env.NO_PROXY = "example.org,*.test.com";
196
+ const options = { noProxy: ["example.com:8080", "*.internal.net"] };
197
+ expect(shouldBypassProxy("example.org", options)).toBe(true);
198
+ expect(shouldBypassProxy("sub.test.com", options)).toBe(true);
199
+ expect(shouldBypassProxy("example.com:8080", options)).toBe(true);
200
+ expect(shouldBypassProxy("server.internal.net", options)).toBe(true);
201
+ expect(shouldBypassProxy("other.domain", options)).toBe(false);
202
+ });
203
+
204
+ test("shouldBypassProxy handles empty noProxy array in options", () => {
205
+ process.env.NO_PROXY = "example.org";
206
+ const options = { noProxy: [] };
207
+ expect(shouldBypassProxy("example.org", options)).toBe(true);
208
+ expect(shouldBypassProxy("different.com", options)).toBe(false);
209
+ });
210
+
211
+ test("shouldBypassProxy handles undefined options", () => {
212
+ process.env.NO_PROXY = "example.org";
213
+ expect(shouldBypassProxy("example.org", undefined)).toBe(true);
102
214
  });
package/src/util.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { RequestOptions } from "@continuedev/config-types";
2
+
1
3
  /**
2
4
  * Gets the proxy settings from environment variables
3
5
  * @param protocol The URL protocol (http: or https:)
@@ -16,28 +18,91 @@ export function getProxyFromEnv(protocol: string): string | undefined {
16
18
  }
17
19
  }
18
20
 
19
- /**
20
- * Checks if a hostname should bypass proxy based on NO_PROXY environment variable
21
- * @param hostname The hostname to check
22
- * @returns True if the hostname should bypass proxy
23
- */
24
- export function shouldBypassProxy(hostname: string): boolean {
25
- const noProxy = process.env.NO_PROXY || process.env.no_proxy;
26
- if (!noProxy) return false;
27
-
28
- const noProxyItems = noProxy.split(",").map((item) => item.trim());
21
+ // Note that request options proxy (per model) takes precedence over environment variables
22
+ export function getProxy(
23
+ protocol: string,
24
+ requestOptions?: RequestOptions,
25
+ ): string | undefined {
26
+ if (requestOptions?.proxy) {
27
+ return requestOptions.proxy;
28
+ }
29
+ return getProxyFromEnv(protocol);
30
+ }
29
31
 
30
- return noProxyItems.some((item) => {
31
- // Exact match
32
- if (item === hostname) return true;
32
+ export function getEnvNoProxyPatterns(): string[] {
33
+ const envValue = process.env.NO_PROXY || process.env.no_proxy;
34
+ if (envValue) {
35
+ return envValue
36
+ .split(",")
37
+ .map((item) => item.trim().toLowerCase())
38
+ .filter((i) => !!i);
39
+ } else {
40
+ return [];
41
+ }
42
+ }
33
43
 
34
- // Wildcard domain match (*.example.com)
35
- if (item.startsWith("*.") && hostname.endsWith(item.substring(1)))
36
- return true;
44
+ export function getReqOptionsNoProxyPatterns(
45
+ options: RequestOptions | undefined,
46
+ ): string[] {
47
+ return (
48
+ options?.noProxy?.map((i) => i.trim().toLowerCase()).filter((i) => !!i) ??
49
+ []
50
+ );
51
+ }
37
52
 
38
- // Domain suffix match (.example.com)
39
- if (item.startsWith(".") && hostname.endsWith(item.slice(1))) return true;
53
+ export function patternMatchesHostname(hostname: string, pattern: string) {
54
+ // Split hostname and pattern to separate hostname and port
55
+ const [hostnameWithoutPort, hostnamePort] = hostname.toLowerCase().split(":");
56
+ const [patternWithoutPort, patternPort] = pattern.toLowerCase().split(":");
40
57
 
58
+ // If pattern specifies a port but hostname doesn't match it, no match
59
+ if (patternPort && (!hostnamePort || hostnamePort !== patternPort)) {
41
60
  return false;
42
- });
61
+ }
62
+
63
+ // Now compare just the hostname parts
64
+
65
+ // exact match
66
+ if (patternWithoutPort === hostnameWithoutPort) {
67
+ return true;
68
+ }
69
+ // wildcard domain match (*.example.com)
70
+ if (
71
+ patternWithoutPort.startsWith("*.") &&
72
+ hostnameWithoutPort.endsWith(patternWithoutPort.substring(1))
73
+ ) {
74
+ return true;
75
+ }
76
+ // Domain suffix match (.example.com)
77
+ if (
78
+ patternWithoutPort.startsWith(".") &&
79
+ hostnameWithoutPort.endsWith(patternWithoutPort.slice(1))
80
+ ) {
81
+ return true;
82
+ }
83
+
84
+ // TODO IP address ranges
85
+
86
+ // TODO CIDR notation
87
+
88
+ return false;
89
+ }
90
+
91
+ /**
92
+ * Checks if a hostname should bypass proxy based on NO_PROXY environment variable
93
+ * @param hostname The hostname to check
94
+ * @returns True if the hostname should bypass proxy
95
+ */
96
+ export function shouldBypassProxy(
97
+ hostname: string,
98
+ requestOptions: RequestOptions | undefined,
99
+ ): boolean {
100
+ const ignores = [
101
+ ...getEnvNoProxyPatterns(),
102
+ ...getReqOptionsNoProxyPatterns(requestOptions),
103
+ ];
104
+ const hostLowerCase = hostname.toLowerCase();
105
+ return ignores.some((ignore) =>
106
+ patternMatchesHostname(hostLowerCase, ignore),
107
+ );
43
108
  }
package/src/fetch.test.ts DELETED
@@ -1,173 +0,0 @@
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 { afterEach, beforeEach, expect, test } from "vitest";
6
- import { getAgentOptions } from "./getAgentOptions.js";
7
-
8
- // Store original env
9
- const originalEnv = process.env;
10
- const originalGlobalAgentOptions = { ...globalAgent.options };
11
-
12
- // Temporary directory for test certificate files
13
- let tempDir: string;
14
-
15
- beforeEach(() => {
16
- // Create a temporary directory for test files
17
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "fetch-test-"));
18
-
19
- process.env = { ...originalEnv };
20
-
21
- // Reset globalAgent for each test
22
- globalAgent.options = { ...originalGlobalAgentOptions };
23
- });
24
-
25
- afterEach(() => {
26
- process.env = originalEnv;
27
- globalAgent.options = originalGlobalAgentOptions;
28
-
29
- // Clean up temporary directory
30
- try {
31
- fs.rmSync(tempDir, { recursive: true, force: true });
32
- } catch (error) {
33
- console.error(`Failed to remove temp directory: ${error}`);
34
- }
35
- });
36
-
37
- // Helper function to create test certificate files
38
- function createTestCertFile(filename: string, content: string): string {
39
- const filePath = path.join(tempDir, filename);
40
- fs.writeFileSync(filePath, content, "utf8");
41
- return filePath;
42
- }
43
-
44
- test("getAgentOptions returns basic configuration with default values", () => {
45
- const options = getAgentOptions();
46
-
47
- // Check default timeout (7200 seconds = 2 hours = 7,200,000 ms)
48
- expect(options.timeout).toBe(7200000);
49
- expect(options.sessionTimeout).toBe(7200000);
50
- expect(options.keepAliveMsecs).toBe(7200000);
51
- expect(options.keepAlive).toBe(true);
52
-
53
- // Verify certificates array exists and contains items
54
- expect(options.ca).toBeInstanceOf(Array);
55
- expect(options.ca.length).toBeGreaterThan(0);
56
-
57
- // Verify at least one of the real TLS root certificates is included
58
- // This assumes there's at least one certificate with "CERTIFICATE" in it
59
- expect(options.ca.some((cert: any) => cert.includes("CERTIFICATE"))).toBe(
60
- true,
61
- );
62
- });
63
-
64
- test("getAgentOptions respects custom timeout", () => {
65
- const customTimeout = 300; // 5 minutes
66
- const options = getAgentOptions({ timeout: customTimeout });
67
-
68
- // Check timeout values (300 seconds = 300,000 ms)
69
- expect(options.timeout).toBe(300000);
70
- expect(options.sessionTimeout).toBe(300000);
71
- expect(options.keepAliveMsecs).toBe(300000);
72
- });
73
-
74
- test("getAgentOptions uses verifySsl setting", () => {
75
- // With verifySsl true
76
- let options = getAgentOptions({ verifySsl: true });
77
- expect(options.rejectUnauthorized).toBe(true);
78
-
79
- // With verifySsl false
80
- options = getAgentOptions({ verifySsl: false });
81
- expect(options.rejectUnauthorized).toBe(false);
82
- });
83
-
84
- test("getAgentOptions incorporates custom CA bundle paths", () => {
85
- // Create a test CA bundle file
86
- const caBundleContent =
87
- "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAMcuSp7chAYdMA==\n-----END CERTIFICATE-----";
88
- const caBundlePath = createTestCertFile("ca-bundle.pem", caBundleContent);
89
-
90
- // Single string path
91
- let options = getAgentOptions({ caBundlePath });
92
-
93
- // Verify that our test certificate is included in the CA list
94
- expect(options.ca).toContain(caBundleContent);
95
-
96
- // Create multiple test CA bundle files
97
- const caContent1 =
98
- "-----BEGIN CERTIFICATE-----\nABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n-----END CERTIFICATE-----";
99
- const caContent2 =
100
- "-----BEGIN CERTIFICATE-----\n0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n-----END CERTIFICATE-----";
101
- const caPath1 = createTestCertFile("ca1.pem", caContent1);
102
- const caPath2 = createTestCertFile("ca2.pem", caContent2);
103
-
104
- // Array of paths
105
- options = getAgentOptions({
106
- caBundlePath: [caPath1, caPath2],
107
- });
108
-
109
- // Verify that both test certificates are included in the CA list
110
- expect(options.ca).toContain(caContent1);
111
- expect(options.ca).toContain(caContent2);
112
- });
113
-
114
- test("getAgentOptions includes global certs when running as binary", () => {
115
- // Set up test certs in globalAgent
116
- globalAgent.options.ca = ["global-cert-1", "global-cert-2"];
117
-
118
- // Set IS_BINARY environment variable
119
- process.env.IS_BINARY = "true";
120
-
121
- const options = getAgentOptions();
122
-
123
- // Test for global certs
124
- expect(options.ca).toContain("global-cert-1");
125
- expect(options.ca).toContain("global-cert-2");
126
- });
127
-
128
- test("getAgentOptions handles client certificate configuration", () => {
129
- // Create test certificate files
130
- const clientCertContent =
131
- "-----BEGIN CERTIFICATE-----\nCLIENTCERT\n-----END CERTIFICATE-----";
132
- const clientKeyContent =
133
- "-----BEGIN PRIVATE KEY-----\nCLIENTKEY\n-----END PRIVATE KEY-----";
134
- const certPath = createTestCertFile("client.cert", clientCertContent);
135
- const keyPath = createTestCertFile("client.key", clientKeyContent);
136
-
137
- const clientCertOptions = {
138
- clientCertificate: {
139
- cert: certPath,
140
- key: keyPath,
141
- passphrase: "secret-passphrase",
142
- },
143
- };
144
-
145
- const options = getAgentOptions(clientCertOptions);
146
-
147
- expect(options.cert).toBe(clientCertContent);
148
- expect(options.key).toBe(clientKeyContent);
149
- expect(options.passphrase).toBe("secret-passphrase");
150
- });
151
-
152
- test("getAgentOptions handles client certificate without passphrase", () => {
153
- // Create test certificate files
154
- const clientCertContent =
155
- "-----BEGIN CERTIFICATE-----\nCLIENTCERT2\n-----END CERTIFICATE-----";
156
- const clientKeyContent =
157
- "-----BEGIN PRIVATE KEY-----\nCLIENTKEY2\n-----END PRIVATE KEY-----";
158
- const certPath = createTestCertFile("client2.cert", clientCertContent);
159
- const keyPath = createTestCertFile("client2.key", clientKeyContent);
160
-
161
- const clientCertOptions = {
162
- clientCertificate: {
163
- cert: certPath,
164
- key: keyPath,
165
- },
166
- };
167
-
168
- const options = getAgentOptions(clientCertOptions);
169
-
170
- expect(options.cert).toBe(clientCertContent);
171
- expect(options.key).toBe(clientKeyContent);
172
- expect(options.passphrase).toBeUndefined();
173
- });