@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.
@@ -1,122 +1,189 @@
1
+ import { globalAgent } from "https";
1
2
  import * as fs from "node:fs";
2
- import { expect, test, vi } from "vitest";
3
-
4
- // Mock fs module
5
- vi.mock("node:fs", () => ({
6
- readFileSync: vi.fn(),
7
- }));
8
-
9
- const mockReadFileSync = vi.mocked(fs.readFileSync);
10
-
11
- // We need to access the private getCertificateContent function for testing
12
- // Since it's not exported, we'll test it indirectly through scenarios or mock the module
13
- const getCertificateContent = (input: string): string => {
14
- if (input.startsWith("data:")) {
15
- // Parse data URI: data:[<mediatype>][;base64],<data>
16
- const [header, data] = input.split(",");
17
- if (header.includes("base64")) {
18
- return Buffer.from(data, "base64").toString("utf8");
19
- } else {
20
- return decodeURIComponent(data);
21
- }
22
- } else {
23
- // Assume it's a file path
24
- return fs.readFileSync(input, "utf8");
25
- }
26
- };
27
-
28
- test("getCertificateContent should decode base64 data URI correctly", () => {
29
- const testCert =
30
- "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----";
31
- const base64Data = Buffer.from(testCert, "utf8").toString("base64");
32
- const dataUri = `data:application/x-pem-file;base64,${base64Data}`;
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { afterEach, beforeEach, expect, test } from "vitest";
6
+ import { CertsCache } from "./certs.js";
7
+ import { getAgentOptions } from "./getAgentOptions.js";
33
8
 
34
- const result = getCertificateContent(dataUri);
9
+ // Store original env
10
+ const originalEnv = process.env;
11
+ const originalGlobalAgentOptions = { ...globalAgent.options };
35
12
 
36
- expect(result).toBe(testCert);
37
- });
13
+ // Temporary directory for test certificate files
14
+ let tempDir: string;
38
15
 
39
- test("getCertificateContent should decode URL-encoded data URI correctly", () => {
40
- const testCert =
41
- "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----";
42
- const encodedData = encodeURIComponent(testCert);
43
- const dataUri = `data:text/plain,${encodedData}`;
16
+ beforeEach(() => {
17
+ // Create a temporary directory for test files
18
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "fetch-test-"));
44
19
 
45
- const result = getCertificateContent(dataUri);
20
+ process.env = { ...originalEnv };
46
21
 
47
- expect(result).toBe(testCert);
22
+ // Reset globalAgent for each test
23
+ globalAgent.options = { ...originalGlobalAgentOptions };
48
24
  });
49
25
 
50
- test("getCertificateContent should handle plain text data URI correctly", () => {
51
- const testCert = "simple-cert-content";
52
- const dataUri = `data:text/plain,${testCert}`;
26
+ afterEach(() => {
27
+ process.env = originalEnv;
28
+ globalAgent.options = originalGlobalAgentOptions;
53
29
 
54
- const result = getCertificateContent(dataUri);
30
+ CertsCache.getInstance().clear();
55
31
 
56
- expect(result).toBe(testCert);
32
+ // Clean up temporary directory
33
+ try {
34
+ fs.rmSync(tempDir, { recursive: true, force: true });
35
+ } catch (error) {
36
+ console.error(`Failed to remove temp directory: ${error}`);
37
+ }
57
38
  });
58
39
 
59
- test("getCertificateContent should read file when input is a file path", () => {
60
- const filePath = "/path/to/cert.pem";
61
- const expectedContent =
62
- "-----BEGIN CERTIFICATE-----\nfile content\n-----END CERTIFICATE-----";
63
-
64
- mockReadFileSync.mockReturnValue(expectedContent);
65
-
66
- const result = getCertificateContent(filePath);
67
-
68
- expect(mockReadFileSync).toHaveBeenCalledWith(filePath, "utf8");
69
- expect(result).toBe(expectedContent);
40
+ // Helper function to create test certificate files
41
+ function createTestCertFile(filename: string, content: string): string {
42
+ const filePath = path.join(tempDir, filename);
43
+ fs.writeFileSync(filePath, content, "utf8");
44
+ return filePath;
45
+ }
46
+
47
+ test("getAgentOptions returns basic configuration with default values", async () => {
48
+ const options = await getAgentOptions();
49
+
50
+ // Check default timeout (7200 seconds = 2 hours = 7,200,000 ms)
51
+ expect(options.timeout).toBe(7200000);
52
+ expect(options.sessionTimeout).toBe(7200000);
53
+ expect(options.keepAliveMsecs).toBe(7200000);
54
+ expect(options.keepAlive).toBe(true);
55
+
56
+ // Verify certificates array exists and contains items
57
+ expect(options.ca).toBeInstanceOf(Array);
58
+ expect(options.ca.length).toBeGreaterThan(0);
59
+
60
+ // Verify at least one of the real TLS root certificates is included
61
+ // This assumes there's at least one certificate with "CERTIFICATE" in it
62
+ expect(options.ca.some((cert: any) => cert.includes("CERTIFICATE"))).toBe(
63
+ true,
64
+ );
70
65
  });
71
66
 
72
- test("getCertificateContent should handle data URI with different media types", () => {
73
- const testData = "certificate-data";
74
- const base64Data = Buffer.from(testData, "utf8").toString("base64");
75
- const dataUri = `data:application/x-x509-ca-cert;base64,${base64Data}`;
67
+ test("getAgentOptions respects custom timeout", async () => {
68
+ const customTimeout = 300; // 5 minutes
69
+ const options = await getAgentOptions({ timeout: customTimeout });
76
70
 
77
- const result = getCertificateContent(dataUri);
78
-
79
- expect(result).toBe(testData);
71
+ // Check timeout values (300 seconds = 300,000 ms)
72
+ expect(options.timeout).toBe(300000);
73
+ expect(options.sessionTimeout).toBe(300000);
74
+ expect(options.keepAliveMsecs).toBe(300000);
80
75
  });
81
76
 
82
- test("getCertificateContent should handle data URI without media type", () => {
83
- const testData = "simple-data";
84
- const base64Data = Buffer.from(testData, "utf8").toString("base64");
85
- const dataUri = `data:;base64,${base64Data}`;
86
-
87
- const result = getCertificateContent(dataUri);
77
+ test("getAgentOptions uses verifySsl setting", async () => {
78
+ // With verifySsl true
79
+ let options = await getAgentOptions({ verifySsl: true });
80
+ expect(options.rejectUnauthorized).toBe(true);
88
81
 
89
- expect(result).toBe(testData);
82
+ // With verifySsl false
83
+ options = await getAgentOptions({ verifySsl: false });
84
+ expect(options.rejectUnauthorized).toBe(false);
90
85
  });
91
86
 
92
- test("getCertificateContent should handle data URI without media type or encoding", () => {
93
- const testData = "simple-data";
94
- const base64Data = Buffer.from(testData, "utf8").toString("base64");
95
- const dataUri = `data:;base64,${base64Data}`;
87
+ test("getAgentOptions incorporates custom CA bundle paths", async () => {
88
+ // Create a test CA bundle file
89
+ const caBundleContent =
90
+ "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAMcuSp7chAYdMA==\n-----END CERTIFICATE-----";
91
+ const caBundlePath = createTestCertFile("ca-bundle.pem", caBundleContent);
92
+
93
+ // Single string path
94
+ let options = await getAgentOptions({ caBundlePath });
95
+
96
+ // Verify that our test certificate is included in the CA list
97
+ expect(options.ca).toContain(caBundleContent);
98
+
99
+ // Create multiple test CA bundle files
100
+ const caContent1 =
101
+ "-----BEGIN CERTIFICATE-----\nABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n-----END CERTIFICATE-----";
102
+ const caContent2 =
103
+ "-----BEGIN CERTIFICATE-----\n0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n-----END CERTIFICATE-----";
104
+ const caPath1 = createTestCertFile("ca1.pem", caContent1);
105
+ const caPath2 = createTestCertFile("ca2.pem", caContent2);
106
+
107
+ // Array of paths
108
+ options = await getAgentOptions({
109
+ caBundlePath: [caPath1, caPath2],
110
+ });
111
+
112
+ // Verify that both test certificates are included in the CA list
113
+ expect(options.ca).toContain(caContent1);
114
+ expect(options.ca).toContain(caContent2);
115
+ });
96
116
 
97
- const result = getCertificateContent(dataUri);
117
+ test("getAgentOptions includes global certs when running as binary", async () => {
118
+ // Set up test certs in globalAgent
119
+ globalAgent.options.ca = ["global-cert-1", "global-cert-2"];
98
120
 
99
- expect(result).toBe(testData);
100
- });
121
+ // Set IS_BINARY environment variable
122
+ process.env.IS_BINARY = "true";
101
123
 
102
- test("getCertificateContent should handle relative file paths", () => {
103
- const filePath = "./certs/ca.pem";
104
- const expectedContent = "certificate from relative path";
124
+ const options = await getAgentOptions();
105
125
 
106
- mockReadFileSync.mockReturnValue(expectedContent);
126
+ // Test for global certs
127
+ expect(options.ca).toContain("global-cert-1");
128
+ expect(options.ca).toContain("global-cert-2");
129
+ });
107
130
 
108
- const result = getCertificateContent(filePath);
131
+ test("getAgentOptions handles client certificate configuration", async () => {
132
+ // Create test certificate files
133
+ const clientCertContent =
134
+ "-----BEGIN CERTIFICATE-----\nCLIENTCERT\n-----END CERTIFICATE-----";
135
+ const clientKeyContent =
136
+ "-----BEGIN PRIVATE KEY-----\nCLIENTKEY\n-----END PRIVATE KEY-----";
137
+ const certPath = createTestCertFile("client.cert", clientCertContent);
138
+ const keyPath = createTestCertFile("client.key", clientKeyContent);
139
+
140
+ const clientCertOptions = {
141
+ clientCertificate: {
142
+ cert: certPath,
143
+ key: keyPath,
144
+ passphrase: "secret-passphrase",
145
+ },
146
+ };
147
+
148
+ const options = await getAgentOptions(clientCertOptions);
149
+
150
+ expect(options.cert).toBe(clientCertContent);
151
+ expect(options.key).toBe(clientKeyContent);
152
+ expect(options.passphrase).toBe("secret-passphrase");
153
+ });
109
154
 
110
- expect(mockReadFileSync).toHaveBeenCalledWith(filePath, "utf8");
111
- expect(result).toBe(expectedContent);
155
+ test("getAgentOptions handles client certificate without passphrase", async () => {
156
+ // Create test certificate files
157
+ const clientCertContent =
158
+ "-----BEGIN CERTIFICATE-----\nCLIENTCERT2\n-----END CERTIFICATE-----";
159
+ const clientKeyContent =
160
+ "-----BEGIN PRIVATE KEY-----\nCLIENTKEY2\n-----END PRIVATE KEY-----";
161
+ const certPath = createTestCertFile("client2.cert", clientCertContent);
162
+ const keyPath = createTestCertFile("client2.key", clientKeyContent);
163
+
164
+ const clientCertOptions = {
165
+ clientCertificate: {
166
+ cert: certPath,
167
+ key: keyPath,
168
+ },
169
+ };
170
+
171
+ const options = await getAgentOptions(clientCertOptions);
172
+
173
+ expect(options.cert).toBe(clientCertContent);
174
+ expect(options.key).toBe(clientKeyContent);
175
+ expect(options.passphrase).toBeUndefined();
112
176
  });
113
177
 
114
- test("getCertificateContent should handle data URI with special characters in URL encoding", () => {
115
- const testCert = "cert with spaces and special chars: !@#$%";
116
- const encodedData = encodeURIComponent(testCert);
117
- const dataUri = `data:text/plain,${encodedData}`;
178
+ test("getAgentOptions reads NODE_EXTRA_CA_CERTS", async () => {
179
+ const extraCertContent =
180
+ "-----BEGIN CERTIFICATE-----\nEXTRA_CERT\n-----END CERTIFICATE-----";
181
+ const certPath = createTestCertFile("extra-cert.cert", extraCertContent);
182
+
183
+ expect(CertsCache.getInstance().fixedCa).not.toContain(extraCertContent);
118
184
 
119
- const result = getCertificateContent(dataUri);
185
+ CertsCache.getInstance().clear();
120
186
 
121
- expect(result).toBe(testCert);
187
+ process.env.NODE_EXTRA_CA_CERTS = certPath;
188
+ expect(CertsCache.getInstance().fixedCa).toContain(extraCertContent);
122
189
  });
@@ -1,56 +1,19 @@
1
- import { globalAgent } from "https";
2
- import * as fs from "node:fs";
3
- import tls from "node:tls";
4
-
5
1
  import { RequestOptions } from "@continuedev/config-types";
6
-
7
- /**
8
- * Extracts content from either a file path or data URI
9
- */
10
- function getCertificateContent(input: string): string {
11
- if (input.startsWith("data:")) {
12
- // Parse data URI: data:[<mediatype>][;base64],<data>
13
- const [header, data] = input.split(",");
14
- if (header.includes("base64")) {
15
- return Buffer.from(data, "base64").toString("utf8");
16
- } else {
17
- return decodeURIComponent(data);
18
- }
19
- } else {
20
- // Assume it's a file path
21
- return fs.readFileSync(input, "utf8");
22
- }
23
- }
2
+ import { CertsCache, getCertificateContent } from "./certs.js";
24
3
 
25
4
  /**
26
5
  * Prepares agent options based on request options and certificates
27
6
  */
28
- export function getAgentOptions(requestOptions?: RequestOptions): {
7
+ export async function getAgentOptions(
8
+ requestOptions?: RequestOptions,
9
+ ): Promise<{
29
10
  [key: string]: any;
30
- } {
11
+ }> {
31
12
  const TIMEOUT = 7200; // 7200 seconds = 2 hours
32
13
  const timeout = (requestOptions?.timeout ?? TIMEOUT) * 1000; // measured in ms
33
14
 
34
- // Get root certificates
35
- let globalCerts: string[] = [];
36
- if (process.env.IS_BINARY) {
37
- if (Array.isArray(globalAgent.options.ca)) {
38
- globalCerts = [...globalAgent.options.ca.map((cert) => cert.toString())];
39
- } else if (typeof globalAgent.options.ca !== "undefined") {
40
- globalCerts.push(globalAgent.options.ca.toString());
41
- }
42
- }
43
-
44
- const ca = Array.from(new Set([...tls.rootCertificates, ...globalCerts]));
45
- const customCerts =
46
- typeof requestOptions?.caBundlePath === "string"
47
- ? [requestOptions?.caBundlePath]
48
- : requestOptions?.caBundlePath;
49
- if (customCerts) {
50
- ca.push(
51
- ...customCerts.map((customCert) => getCertificateContent(customCert)),
52
- );
53
- }
15
+ const certsCache = CertsCache.getInstance();
16
+ const ca = await certsCache.getCa(requestOptions?.caBundlePath);
54
17
 
55
18
  const agentOptions: { [key: string]: any } = {
56
19
  ca,
@@ -73,5 +36,21 @@ export function getAgentOptions(requestOptions?: RequestOptions): {
73
36
  }
74
37
  }
75
38
 
39
+ if (process.env.VERBOSE_FETCH) {
40
+ console.log(`Fetch agent options:`);
41
+ console.log(
42
+ `\tTimeout (sessionTimeout/keepAliveMsecs): ${agentOptions.timeout}`,
43
+ );
44
+ console.log(`\tTotal CA certs: ${ca.length}`);
45
+ console.log(`\tGlobal/Root CA certs: ${certsCache.fixedCa.length}`);
46
+ console.log(`\tCustom CA certs: ${ca.length - certsCache.fixedCa.length}`);
47
+ console.log(
48
+ `\tClient certificate: ${requestOptions?.clientCertificate ? "Yes" : "No"}`,
49
+ );
50
+ console.log(
51
+ `\trejectUnauthorized/verifySsl: ${agentOptions.rejectUnauthorized ?? "not set (defaults to true)"}`,
52
+ );
53
+ }
54
+
76
55
  return agentOptions;
77
56
  }
package/src/index.ts CHANGED
@@ -5,10 +5,13 @@ import {
5
5
  toAsyncIterable,
6
6
  } from "./stream.js";
7
7
 
8
+ import patchedFetch from "./node-fetch-patch.js";
9
+
8
10
  import { fetchwithRequestOptions } from "./fetch.js";
9
11
 
10
12
  export {
11
13
  fetchwithRequestOptions,
14
+ patchedFetch,
12
15
  streamJSON,
13
16
  streamResponse,
14
17
  streamSse,