@continuedev/fetch 1.0.13 → 1.0.15
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/certs.d.ts +17 -0
- package/dist/certs.js +105 -0
- package/dist/certs.test.d.ts +1 -0
- package/dist/certs.test.js +139 -0
- package/dist/fetch.js +7 -10
- package/dist/fetch.test.js +0 -1
- package/dist/getAgentOptions.d.ts +2 -2
- package/dist/getAgentOptions.js +13 -40
- package/dist/getAgentOptions.test.js +129 -77
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/node-fetch-patch.d.ts +16 -0
- package/dist/node-fetch-patch.js +395 -0
- package/dist/node-fetch-patch.test.d.ts +1 -0
- package/dist/node-fetch-patch.test.js +50 -0
- package/dist/stream.js +27 -4
- package/dist/util.d.ts +6 -1
- package/dist/util.js +57 -17
- package/dist/util.test.js +92 -18
- package/package.json +1 -1
- package/release.config.js +3 -0
- package/src/certs.test.ts +187 -0
- package/src/certs.ts +129 -0
- package/src/fetch.ts +7 -11
- package/src/getAgentOptions.test.ts +158 -91
- package/src/getAgentOptions.ts +23 -44
- package/src/index.ts +3 -0
- package/src/node-fetch-patch.js +518 -0
- package/src/node-fetch-patch.test.js +67 -0
- package/src/node_modules/.vite/vitest/d41d8cd98f00b204e9800998ecf8427e/results.json +1 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/stream.ts +29 -4
- package/src/util.test.ts +130 -18
- package/src/util.ts +84 -19
- package/src/fetch.test.ts +0 -173
package/dist/util.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
-
import { getProxyFromEnv, shouldBypassProxy } from "./util.js";
|
|
2
|
+
import { getProxyFromEnv, patternMatchesHostname, shouldBypassProxy, } from "./util.js";
|
|
3
3
|
// Reset environment variables after each test
|
|
4
4
|
afterEach(() => {
|
|
5
5
|
vi.resetModules();
|
|
@@ -42,44 +42,118 @@ test("getProxyFromEnv prefers HTTPS_PROXY over other env vars for https protocol
|
|
|
42
42
|
process.env.http_proxy = "http://notused3.example.com";
|
|
43
43
|
expect(getProxyFromEnv("https:")).toBe("https://preferred.example.com");
|
|
44
44
|
});
|
|
45
|
+
// Tests for patternMatchesHostname
|
|
46
|
+
test("patternMatchesHostname with exact hostname match", () => {
|
|
47
|
+
expect(patternMatchesHostname("example.com", "example.com")).toBe(true);
|
|
48
|
+
expect(patternMatchesHostname("example.com", "different.com")).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
test("patternMatchesHostname with wildcard domains", () => {
|
|
51
|
+
expect(patternMatchesHostname("sub.example.com", "*.example.com")).toBe(true);
|
|
52
|
+
expect(patternMatchesHostname("sub.sub.example.com", "*.example.com")).toBe(true);
|
|
53
|
+
expect(patternMatchesHostname("example.com", "*.example.com")).toBe(false);
|
|
54
|
+
expect(patternMatchesHostname("sub.different.com", "*.example.com")).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
test("patternMatchesHostname with domain suffix", () => {
|
|
57
|
+
expect(patternMatchesHostname("sub.example.com", ".example.com")).toBe(true);
|
|
58
|
+
expect(patternMatchesHostname("example.com", ".example.com")).toBe(true);
|
|
59
|
+
expect(patternMatchesHostname("different.com", ".example.com")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
test("patternMatchesHostname with case insensitivity", () => {
|
|
62
|
+
expect(patternMatchesHostname("EXAMPLE.com", "example.COM")).toBe(true);
|
|
63
|
+
expect(patternMatchesHostname("sub.EXAMPLE.com", "*.example.COM")).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
// Port handling tests
|
|
66
|
+
test("patternMatchesHostname with exact port match", () => {
|
|
67
|
+
expect(patternMatchesHostname("example.com:8080", "example.com:8080")).toBe(true);
|
|
68
|
+
expect(patternMatchesHostname("example.com:8080", "example.com:9090")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
test("patternMatchesHostname with port in pattern but not in hostname", () => {
|
|
71
|
+
expect(patternMatchesHostname("example.com", "example.com:8080")).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
test("patternMatchesHostname with port in hostname but not in pattern", () => {
|
|
74
|
+
expect(patternMatchesHostname("example.com:8080", "example.com")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
test("patternMatchesHostname with wildcard domains and ports", () => {
|
|
77
|
+
expect(patternMatchesHostname("sub.example.com:8080", "*.example.com:8080")).toBe(true);
|
|
78
|
+
expect(patternMatchesHostname("sub.example.com:9090", "*.example.com:8080")).toBe(false);
|
|
79
|
+
expect(patternMatchesHostname("sub.example.com", "*.example.com:8080")).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
test("patternMatchesHostname with domain suffix and ports", () => {
|
|
82
|
+
expect(patternMatchesHostname("sub.example.com:8080", ".example.com:8080")).toBe(true);
|
|
83
|
+
expect(patternMatchesHostname("example.com:8080", ".example.com:8080")).toBe(true);
|
|
84
|
+
expect(patternMatchesHostname("sub.example.com:9090", ".example.com:8080")).toBe(false);
|
|
85
|
+
});
|
|
45
86
|
// Tests for shouldBypassProxy
|
|
46
87
|
test("shouldBypassProxy returns false when NO_PROXY is not set", () => {
|
|
47
|
-
expect(shouldBypassProxy("example.com")).toBe(false);
|
|
88
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(false);
|
|
48
89
|
});
|
|
49
90
|
test("shouldBypassProxy returns true for exact hostname match", () => {
|
|
50
91
|
process.env.NO_PROXY = "example.com,another.com";
|
|
51
|
-
expect(shouldBypassProxy("example.com")).toBe(true);
|
|
92
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(true);
|
|
52
93
|
});
|
|
53
94
|
test("shouldBypassProxy returns false when hostname doesn't match any NO_PROXY entry", () => {
|
|
54
95
|
process.env.NO_PROXY = "example.com,another.com";
|
|
55
|
-
expect(shouldBypassProxy("different.com")).toBe(false);
|
|
96
|
+
expect(shouldBypassProxy("different.com", undefined)).toBe(false);
|
|
56
97
|
});
|
|
57
98
|
test("shouldBypassProxy handles lowercase no_proxy", () => {
|
|
58
99
|
process.env.no_proxy = "example.com";
|
|
59
|
-
expect(shouldBypassProxy("example.com")).toBe(true);
|
|
100
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(true);
|
|
60
101
|
});
|
|
61
102
|
test("shouldBypassProxy works with wildcard domains", () => {
|
|
62
103
|
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);
|
|
104
|
+
expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
|
|
105
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(false);
|
|
106
|
+
expect(shouldBypassProxy("different.com", undefined)).toBe(false);
|
|
66
107
|
});
|
|
67
108
|
test("shouldBypassProxy works with domain suffix", () => {
|
|
68
109
|
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);
|
|
110
|
+
expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
|
|
111
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(true);
|
|
112
|
+
expect(shouldBypassProxy("different.com", undefined)).toBe(false);
|
|
72
113
|
});
|
|
73
114
|
test("shouldBypassProxy handles multiple entries with different patterns", () => {
|
|
74
115
|
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);
|
|
116
|
+
expect(shouldBypassProxy("internal.local", undefined)).toBe(true);
|
|
117
|
+
expect(shouldBypassProxy("sub.example.com", undefined)).toBe(true);
|
|
118
|
+
expect(shouldBypassProxy("sub.test.com", undefined)).toBe(true);
|
|
119
|
+
expect(shouldBypassProxy("test.com", undefined)).toBe(true);
|
|
120
|
+
expect(shouldBypassProxy("example.org", undefined)).toBe(false);
|
|
80
121
|
});
|
|
81
122
|
test("shouldBypassProxy ignores whitespace in NO_PROXY", () => {
|
|
82
123
|
process.env.NO_PROXY = " example.com , *.test.org ";
|
|
83
|
-
expect(shouldBypassProxy("example.com")).toBe(true);
|
|
84
|
-
expect(shouldBypassProxy("subdomain.test.org")).toBe(true);
|
|
124
|
+
expect(shouldBypassProxy("example.com", undefined)).toBe(true);
|
|
125
|
+
expect(shouldBypassProxy("subdomain.test.org", undefined)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
test("shouldBypassProxy with ports in NO_PROXY", () => {
|
|
128
|
+
process.env.NO_PROXY = "example.com:8080,*.test.org:443,.internal.net:8443";
|
|
129
|
+
expect(shouldBypassProxy("example.com:8080", undefined)).toBe(true);
|
|
130
|
+
expect(shouldBypassProxy("example.com:9090", undefined)).toBe(false);
|
|
131
|
+
expect(shouldBypassProxy("sub.test.org:443", undefined)).toBe(true);
|
|
132
|
+
expect(shouldBypassProxy("sub.internal.net:8443", undefined)).toBe(true);
|
|
133
|
+
expect(shouldBypassProxy("internal.net:8443", undefined)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
test("shouldBypassProxy accepts options with noProxy patterns", () => {
|
|
136
|
+
const options = { noProxy: ["example.com:8080", "*.internal.net"] };
|
|
137
|
+
expect(shouldBypassProxy("example.com:8080", options)).toBe(true);
|
|
138
|
+
expect(shouldBypassProxy("example.com", options)).toBe(false);
|
|
139
|
+
expect(shouldBypassProxy("server.internal.net", options)).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
test("shouldBypassProxy combines environment and options noProxy patterns", () => {
|
|
142
|
+
process.env.NO_PROXY = "example.org,*.test.com";
|
|
143
|
+
const options = { noProxy: ["example.com:8080", "*.internal.net"] };
|
|
144
|
+
expect(shouldBypassProxy("example.org", options)).toBe(true);
|
|
145
|
+
expect(shouldBypassProxy("sub.test.com", options)).toBe(true);
|
|
146
|
+
expect(shouldBypassProxy("example.com:8080", options)).toBe(true);
|
|
147
|
+
expect(shouldBypassProxy("server.internal.net", options)).toBe(true);
|
|
148
|
+
expect(shouldBypassProxy("other.domain", options)).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
test("shouldBypassProxy handles empty noProxy array in options", () => {
|
|
151
|
+
process.env.NO_PROXY = "example.org";
|
|
152
|
+
const options = { noProxy: [] };
|
|
153
|
+
expect(shouldBypassProxy("example.org", options)).toBe(true);
|
|
154
|
+
expect(shouldBypassProxy("different.com", options)).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
test("shouldBypassProxy handles undefined options", () => {
|
|
157
|
+
process.env.NO_PROXY = "example.org";
|
|
158
|
+
expect(shouldBypassProxy("example.org", undefined)).toBe(true);
|
|
85
159
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { beforeEach, expect, test, vi } from "vitest";
|
|
3
|
+
import { CertsCache, getCertificateContent } from "./certs.js";
|
|
4
|
+
|
|
5
|
+
// Mock fs module
|
|
6
|
+
vi.mock("node:fs", () => ({
|
|
7
|
+
readFileSync: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const mockReadFileSync = vi.mocked(fs.readFileSync);
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("getCertificateContent should decode base64 data URI correctly", () => {
|
|
17
|
+
const testCert =
|
|
18
|
+
"-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----";
|
|
19
|
+
const base64Data = Buffer.from(testCert, "utf8").toString("base64");
|
|
20
|
+
const dataUri = `data:application/x-pem-file;base64,${base64Data}`;
|
|
21
|
+
|
|
22
|
+
const result = getCertificateContent(dataUri);
|
|
23
|
+
|
|
24
|
+
expect(result).toBe(testCert);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("getCertificateContent should decode URL-encoded data URI correctly", () => {
|
|
28
|
+
const testCert =
|
|
29
|
+
"-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----";
|
|
30
|
+
const encodedData = encodeURIComponent(testCert);
|
|
31
|
+
const dataUri = `data:text/plain,${encodedData}`;
|
|
32
|
+
|
|
33
|
+
const result = getCertificateContent(dataUri);
|
|
34
|
+
|
|
35
|
+
expect(result).toBe(testCert);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("getCertificateContent should handle plain text data URI correctly", () => {
|
|
39
|
+
const testCert = "simple-cert-content";
|
|
40
|
+
const dataUri = `data:text/plain,${testCert}`;
|
|
41
|
+
|
|
42
|
+
const result = getCertificateContent(dataUri);
|
|
43
|
+
|
|
44
|
+
expect(result).toBe(testCert);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("getCertificateContent should read file when input is a file path", () => {
|
|
48
|
+
const filePath = "/path/to/cert.pem";
|
|
49
|
+
const expectedContent =
|
|
50
|
+
"-----BEGIN CERTIFICATE-----\nfile content\n-----END CERTIFICATE-----";
|
|
51
|
+
|
|
52
|
+
mockReadFileSync.mockReturnValue(expectedContent);
|
|
53
|
+
|
|
54
|
+
const result = getCertificateContent(filePath);
|
|
55
|
+
|
|
56
|
+
expect(mockReadFileSync).toHaveBeenCalledWith(filePath, "utf8");
|
|
57
|
+
expect(result).toBe(expectedContent);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("getCertificateContent should handle data URI with different media types", () => {
|
|
61
|
+
const testData = "certificate-data";
|
|
62
|
+
const base64Data = Buffer.from(testData, "utf8").toString("base64");
|
|
63
|
+
const dataUri = `data:application/x-x509-ca-cert;base64,${base64Data}`;
|
|
64
|
+
|
|
65
|
+
const result = getCertificateContent(dataUri);
|
|
66
|
+
|
|
67
|
+
expect(result).toBe(testData);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("getCertificateContent should handle data URI without media type", () => {
|
|
71
|
+
const testData = "simple-data";
|
|
72
|
+
const base64Data = Buffer.from(testData, "utf8").toString("base64");
|
|
73
|
+
const dataUri = `data:;base64,${base64Data}`;
|
|
74
|
+
|
|
75
|
+
const result = getCertificateContent(dataUri);
|
|
76
|
+
|
|
77
|
+
expect(result).toBe(testData);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("getCertificateContent should handle data URI without media type or encoding", () => {
|
|
81
|
+
const testData = "simple-data";
|
|
82
|
+
const base64Data = Buffer.from(testData, "utf8").toString("base64");
|
|
83
|
+
const dataUri = `data:;base64,${base64Data}`;
|
|
84
|
+
|
|
85
|
+
const result = getCertificateContent(dataUri);
|
|
86
|
+
|
|
87
|
+
expect(result).toBe(testData);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("getCertificateContent should handle relative file paths", () => {
|
|
91
|
+
const filePath = "./certs/ca.pem";
|
|
92
|
+
const expectedContent = "certificate from relative path";
|
|
93
|
+
|
|
94
|
+
mockReadFileSync.mockReturnValue(expectedContent);
|
|
95
|
+
|
|
96
|
+
const result = getCertificateContent(filePath);
|
|
97
|
+
|
|
98
|
+
expect(mockReadFileSync).toHaveBeenCalledWith(filePath, "utf8");
|
|
99
|
+
expect(result).toBe(expectedContent);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("getCertificateContent should handle data URI with special characters in URL encoding", () => {
|
|
103
|
+
const testCert = "cert with spaces and special chars: !@#$%";
|
|
104
|
+
const encodedData = encodeURIComponent(testCert);
|
|
105
|
+
const dataUri = `data:text/plain,${encodedData}`;
|
|
106
|
+
|
|
107
|
+
const result = getCertificateContent(dataUri);
|
|
108
|
+
|
|
109
|
+
expect(result).toBe(testCert);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("CertsCache.getCachedCustomCert should cache and return certificate content", async () => {
|
|
113
|
+
const certsCache = CertsCache.getInstance();
|
|
114
|
+
const filePath = "/path/to/custom/cert.pem";
|
|
115
|
+
const expectedContent = "custom cert content";
|
|
116
|
+
|
|
117
|
+
mockReadFileSync.mockReturnValue(expectedContent);
|
|
118
|
+
|
|
119
|
+
const cert1 = await certsCache.getCachedCustomCert(filePath);
|
|
120
|
+
expect(cert1).toBe(expectedContent);
|
|
121
|
+
expect(mockReadFileSync).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(mockReadFileSync).toHaveBeenCalledWith(filePath, "utf8");
|
|
123
|
+
|
|
124
|
+
// Call again to check if it's cached
|
|
125
|
+
const cert2 = await certsCache.getCachedCustomCert(filePath);
|
|
126
|
+
expect(cert2).toBe(expectedContent);
|
|
127
|
+
// readFileSync should not be called again
|
|
128
|
+
expect(mockReadFileSync).toHaveBeenCalledTimes(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("CertsCache.getAllCachedCustomCerts should return all cached custom certs", async () => {
|
|
132
|
+
const certsCache = CertsCache.getInstance();
|
|
133
|
+
const filePaths = ["/path/to/cert1.pem", "/path/to/cert2.pem"];
|
|
134
|
+
const expectedContent1 = "content of cert1";
|
|
135
|
+
const expectedContent2 = "content of cert2";
|
|
136
|
+
|
|
137
|
+
mockReadFileSync.mockReturnValueOnce(expectedContent1);
|
|
138
|
+
mockReadFileSync.mockReturnValueOnce(expectedContent2);
|
|
139
|
+
|
|
140
|
+
const certs = await certsCache.getAllCachedCustomCerts(filePaths);
|
|
141
|
+
expect(certs).toEqual([expectedContent1, expectedContent2]);
|
|
142
|
+
expect(mockReadFileSync).toHaveBeenCalledTimes(2);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("CertsCache.getCa should return combined CA when caBundlePath is provided", async () => {
|
|
146
|
+
const certsCache = CertsCache.getInstance();
|
|
147
|
+
const fixedCa = ["fixed CA cert"];
|
|
148
|
+
const customCertPath = "/path/to/custom/cert.pem";
|
|
149
|
+
const customCertContent = "custom cert content";
|
|
150
|
+
|
|
151
|
+
// Directly set _fixedCa to avoid initializing it with real data
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
153
|
+
// @ts-ignore
|
|
154
|
+
certsCache._fixedCa = fixedCa;
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
certsCache._initialized = true;
|
|
158
|
+
mockReadFileSync.mockReturnValue(customCertContent);
|
|
159
|
+
|
|
160
|
+
const ca = await certsCache.getCa(customCertPath);
|
|
161
|
+
expect(ca).toEqual([...fixedCa, customCertContent]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("CertsCache.clear should clear custom certs and reset initialized flag", () => {
|
|
165
|
+
const certsCache = CertsCache.getInstance();
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
certsCache._customCerts.set("key", "value");
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
170
|
+
// @ts-ignore
|
|
171
|
+
certsCache._initialized = true;
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
certsCache._fixedCa = ["test"];
|
|
175
|
+
|
|
176
|
+
certsCache.clear();
|
|
177
|
+
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
179
|
+
// @ts-ignore
|
|
180
|
+
expect(certsCache._customCerts.size).toBe(0);
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
expect(certsCache._initialized).toBe(false);
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
185
|
+
// @ts-ignore
|
|
186
|
+
expect(certsCache._fixedCa).toEqual([]);
|
|
187
|
+
});
|
package/src/certs.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { globalAgent } from "https";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import tls from "node:tls";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts content from either a file path or data URI
|
|
7
|
+
*/
|
|
8
|
+
export function getCertificateContent(input: string): string {
|
|
9
|
+
if (input.startsWith("data:")) {
|
|
10
|
+
// Parse data URI: data:[<mediatype>][;base64],<data>
|
|
11
|
+
const [header, data] = input.split(",");
|
|
12
|
+
if (header.includes("base64")) {
|
|
13
|
+
return Buffer.from(data, "base64").toString("utf8");
|
|
14
|
+
} else {
|
|
15
|
+
return decodeURIComponent(data);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
// Assume it's a file path
|
|
19
|
+
return fs.readFileSync(input, "utf8");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class CertsCache {
|
|
24
|
+
private static instance: CertsCache;
|
|
25
|
+
private _fixedCa: string[] = [];
|
|
26
|
+
private _initialized: boolean = false;
|
|
27
|
+
private _customCerts: Map<string, string> = new Map();
|
|
28
|
+
|
|
29
|
+
private constructor() {}
|
|
30
|
+
|
|
31
|
+
public static getInstance(): CertsCache {
|
|
32
|
+
if (!CertsCache.instance) {
|
|
33
|
+
CertsCache.instance = new CertsCache();
|
|
34
|
+
}
|
|
35
|
+
return CertsCache.instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get fixedCa(): string[] {
|
|
39
|
+
if (this._initialized) {
|
|
40
|
+
return this._fixedCa;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const globalCerts: string[] = [];
|
|
44
|
+
if (Boolean(process.env.IS_BINARY)) {
|
|
45
|
+
if (Array.isArray(globalAgent.options.ca)) {
|
|
46
|
+
globalCerts.push(
|
|
47
|
+
...globalAgent.options.ca.map((cert) => cert.toString()),
|
|
48
|
+
);
|
|
49
|
+
} else if (typeof globalAgent.options.ca !== "undefined") {
|
|
50
|
+
globalCerts.push(globalAgent.options.ca.toString());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const extraCerts: string[] = [];
|
|
55
|
+
if (process.env.NODE_EXTRA_CA_CERTS) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(
|
|
58
|
+
process.env.NODE_EXTRA_CA_CERTS,
|
|
59
|
+
"utf8",
|
|
60
|
+
);
|
|
61
|
+
extraCerts.push(content);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (process.env.VERBOSE_FETCH) {
|
|
64
|
+
console.error(
|
|
65
|
+
`Error reading NODE_EXTRA_CA_CERTS file: ${process.env.NODE_EXTRA_CA_CERTS}`,
|
|
66
|
+
error,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this._fixedCa = Array.from(
|
|
73
|
+
new Set([...tls.rootCertificates, ...globalCerts, ...extraCerts]),
|
|
74
|
+
);
|
|
75
|
+
this._initialized = true;
|
|
76
|
+
return this._fixedCa;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getCachedCustomCert(path: string): Promise<string | undefined> {
|
|
80
|
+
if (this._customCerts.has(path)) {
|
|
81
|
+
return this._customCerts.get(path);
|
|
82
|
+
}
|
|
83
|
+
const certContent = getCertificateContent(path);
|
|
84
|
+
this._customCerts.set(path, certContent);
|
|
85
|
+
return certContent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getAllCachedCustomCerts(
|
|
89
|
+
caBundlePath: string[] | string,
|
|
90
|
+
): Promise<string[]> {
|
|
91
|
+
const paths = Array.isArray(caBundlePath) ? caBundlePath : [caBundlePath];
|
|
92
|
+
const certs: string[] = [];
|
|
93
|
+
await Promise.all(
|
|
94
|
+
paths.map(async (path) => {
|
|
95
|
+
try {
|
|
96
|
+
const certContent = await this.getCachedCustomCert(path);
|
|
97
|
+
if (certContent) {
|
|
98
|
+
certs.push(certContent);
|
|
99
|
+
} else if (process.env.VERBOSE_FETCH) {
|
|
100
|
+
console.warn(`Empty certificate found at ${path}`);
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (process.env.VERBOSE_FETCH) {
|
|
104
|
+
console.error(
|
|
105
|
+
`Error loading custom certificate from ${path}:`,
|
|
106
|
+
error,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
return certs;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async getCa(caBundlePath: undefined | string | string[]): Promise<string[]> {
|
|
116
|
+
if (!caBundlePath) {
|
|
117
|
+
return this.fixedCa;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const customCerts = await this.getAllCachedCustomCerts(caBundlePath);
|
|
121
|
+
return [...this.fixedCa, ...customCerts];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async clear(): Promise<void> {
|
|
125
|
+
this._customCerts.clear();
|
|
126
|
+
this._initialized = false;
|
|
127
|
+
this._fixedCa = [];
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/fetch.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { RequestOptions } from "@continuedev/config-types";
|
|
|
2
2
|
import * as followRedirects from "follow-redirects";
|
|
3
3
|
import { HttpProxyAgent } from "http-proxy-agent";
|
|
4
4
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
5
|
-
import
|
|
5
|
+
import { BodyInit, RequestInit, Response } from "node-fetch";
|
|
6
6
|
import { getAgentOptions } from "./getAgentOptions.js";
|
|
7
|
-
import
|
|
7
|
+
import patchedFetch from "./node-fetch-patch.js";
|
|
8
|
+
import { getProxy, shouldBypassProxy } from "./util.js";
|
|
8
9
|
|
|
9
10
|
const { http, https } = (followRedirects as any).default;
|
|
10
11
|
|
|
@@ -88,18 +89,13 @@ export async function fetchwithRequestOptions(
|
|
|
88
89
|
url.host = "127.0.0.1";
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
const agentOptions = getAgentOptions(requestOptions);
|
|
92
|
+
const agentOptions = await getAgentOptions(requestOptions);
|
|
92
93
|
|
|
93
94
|
// Get proxy from options or environment variables
|
|
94
|
-
|
|
95
|
-
if (!proxy) {
|
|
96
|
-
proxy = getProxyFromEnv(url.protocol);
|
|
97
|
-
}
|
|
95
|
+
const proxy = getProxy(url.protocol, requestOptions);
|
|
98
96
|
|
|
99
97
|
// Check if should bypass proxy based on requestOptions or NO_PROXY env var
|
|
100
|
-
const shouldBypass =
|
|
101
|
-
requestOptions?.noProxy?.includes(url.hostname) ||
|
|
102
|
-
shouldBypassProxy(url.hostname);
|
|
98
|
+
const shouldBypass = shouldBypassProxy(url.hostname, requestOptions);
|
|
103
99
|
|
|
104
100
|
// Create agent
|
|
105
101
|
const protocol = url.protocol === "https:" ? https : http;
|
|
@@ -148,7 +144,7 @@ export async function fetchwithRequestOptions(
|
|
|
148
144
|
|
|
149
145
|
// fetch the request with the provided options
|
|
150
146
|
try {
|
|
151
|
-
const resp = await
|
|
147
|
+
const resp = await patchedFetch(url, {
|
|
152
148
|
...init,
|
|
153
149
|
body: finalBody,
|
|
154
150
|
headers: headers,
|