@percy/sdk-utils 1.31.3-beta.2 → 1.31.3-beta.4

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/bundle.js CHANGED
@@ -116,8 +116,25 @@
116
116
  let {
117
117
  default: http
118
118
  } = protocol === 'https:' ? await ({}) : await ({});
119
+ const requestOptions = {
120
+ ...options
121
+ };
122
+ try {
123
+ const {
124
+ proxyAgentFor
125
+ } = await ({});
126
+ const agent = proxyAgentFor(url);
127
+ if (agent) {
128
+ requestOptions.agent = agent;
129
+ }
130
+ } catch (error) {
131
+ // Failed to load proxy module or create proxy agent (e.g., missing proxy.js, invalid proxy config)
132
+ // Continue without proxy support - requests will go directly without proxy
133
+ /* istanbul ignore next */
134
+ logger('sdk-utils:request').debug(`Proxy agent unavailable: ${error.message}`);
135
+ }
119
136
  return new Promise((resolve, reject) => {
120
- http.request(url, options).on('response', response => {
137
+ http.request(url, requestOptions).on('response', response => {
121
138
  let body = '';
122
139
  response.on('data', chunk => body += chunk.toString());
123
140
  response.on('end', () => resolve({
package/dist/proxy.js ADDED
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ProxyHttpsAgent = exports.ProxyHttpAgent = void 0;
7
+ exports.createPacAgent = createPacAgent;
8
+ exports.getProxy = getProxy;
9
+ exports.hostnameMatches = hostnameMatches;
10
+ exports.href = href;
11
+ exports.port = port;
12
+ exports.proxyAgentFor = proxyAgentFor;
13
+ var _net = _interopRequireDefault(require("net"));
14
+ var _tls = _interopRequireDefault(require("tls"));
15
+ var _http = _interopRequireDefault(require("http"));
16
+ var _https = _interopRequireDefault(require("https"));
17
+ var _logger = _interopRequireDefault(require("./logger.js"));
18
+ var _pacProxyAgent = require("pac-proxy-agent");
19
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
+ const CRLF = '\r\n';
21
+ const STATUS_REG = /^HTTP\/1.[01] (\d*)/;
22
+
23
+ /**
24
+ * Local proxy implementation for sdk-utils package
25
+ *
26
+ * WHY THIS EXISTS:
27
+ * ================
28
+ * This is a self-contained proxy implementation copied and simplified from
29
+ * @percy/client/src/proxy.js. We cannot directly import from @percy/client/utils
30
+ * due to several module compatibility and build system issues:
31
+ *
32
+ * 1. MODULE TYPE MISMATCH:
33
+ * - @percy/client is built as an ES module ("type": "module")
34
+ * - @percy/sdk-utils gets built as CommonJS by Rollup/Babel
35
+ * - Runtime import() calls get transformed to require() calls by the build system
36
+ * - This causes "require() of ES Module not supported" errors at runtime
37
+ *
38
+ * 2. BUILD SYSTEM TRANSFORMATION:
39
+ * - Rollup/Babel transforms dynamic imports even when marked as external
40
+ * - Attempts to use eval() or Function constructor to bypass transformation
41
+ * either fail with "experimental-vm-modules" requirements or still get transformed
42
+ *
43
+ * 3. DEPENDENCY RESOLUTION:
44
+ * - Cross-package imports create complex dependency resolution issues
45
+ * - The sdk-utils package needs to be self-contained for broader compatibility
46
+ *
47
+ * 4. RUNTIME ENVIRONMENT DIFFERENCES:
48
+ * - sdk-utils may run in different environments than the main Percy CLI
49
+ * - A local implementation ensures consistent behavior regardless of environment
50
+ *
51
+ * WHAT'S DIFFERENT FROM CLIENT VERSION:
52
+ * ====================================
53
+ * - Removed PAC proxy support (pac-proxy-agent dependency)
54
+ * - Removed @percy/env dependency (inlined stripQuotesAndSpaces function)
55
+ * - Uses local logger instead of @percy/logger
56
+ * - Simplified error handling and logging
57
+ * - Removed some advanced features to keep it lightweight
58
+ *
59
+ * This approach ensures proxy functionality works reliably without external
60
+ * import complications while maintaining the same core HTTP/HTTPS proxy features.
61
+ */
62
+
63
+ // function to create PAC proxy agent
64
+ function createPacAgent(pacUrl, options = {}) {
65
+ pacUrl = stripQuotesAndSpaces(pacUrl);
66
+ try {
67
+ const agent = new _pacProxyAgent.PacProxyAgent(pacUrl, {
68
+ keepAlive: true,
69
+ ...options
70
+ });
71
+ (0, _logger.default)('sdk-utils:proxy').info(`Successfully loaded PAC file from: ${pacUrl}`);
72
+ return agent;
73
+ } catch (error) {
74
+ (0, _logger.default)('sdk-utils:proxy').error(`Failed to load PAC file, error message: ${error.message}, stack: ${error.stack}`);
75
+ throw new Error(`Failed to initialize PAC proxy: ${error.message}`);
76
+ }
77
+ }
78
+
79
+ // Returns true if the URL hostname matches any patterns
80
+ function hostnameMatches(patterns, url) {
81
+ let subject = new URL(url);
82
+ patterns = typeof patterns === 'string' ? patterns.split(/[\s,]+/) : [].concat(patterns);
83
+ for (let pattern of patterns) {
84
+ if (pattern === '*') return true;
85
+ if (!pattern) continue;
86
+
87
+ // parse pattern
88
+ let {
89
+ groups: rule
90
+ } = pattern.match(/^(?<hostname>.+?)(?::(?<port>\d+))?$/);
91
+
92
+ // missing a hostname or ports do not match
93
+ if (!rule.hostname || rule.port && rule.port !== subject.port) {
94
+ continue;
95
+ }
96
+
97
+ // wildcards are treated the same as leading dots
98
+ rule.hostname = rule.hostname.replace(/^\*/, '');
99
+
100
+ // hostnames are equal or end with a wildcard rule
101
+ if (rule.hostname === subject.hostname || rule.hostname.startsWith('.') && subject.hostname.endsWith(rule.hostname)) {
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+
108
+ // Returns the port number of a URL object. Defaults to port 443 for https
109
+ // protocols or port 80 otherwise.
110
+ function port(options) {
111
+ if (options.port) return options.port;
112
+ return options.protocol === 'https:' ? 443 : 80;
113
+ }
114
+
115
+ // Returns a string representation of a URL-like object
116
+ function href(options) {
117
+ let {
118
+ protocol,
119
+ hostname,
120
+ path,
121
+ pathname,
122
+ search,
123
+ hash
124
+ } = options;
125
+ return `${protocol}//${hostname}:${port(options)}` + (path || `${pathname || ''}${search || ''}${hash || ''}`);
126
+ }
127
+
128
+ // Strip quotes and spaces from environment variables
129
+ function stripQuotesAndSpaces(str) {
130
+ if (typeof str !== 'string') return str;
131
+ return str.replace(/^["'\s]+|["'\s]+$/g, '');
132
+ }
133
+
134
+ // Returns the proxy URL for a set of request options
135
+ function getProxy(options) {
136
+ let proxyUrl = options.protocol === 'https:' && (process.env.https_proxy || process.env.HTTPS_PROXY) || process.env.http_proxy || process.env.HTTP_PROXY;
137
+ let shouldProxy = !!proxyUrl && !hostnameMatches(stripQuotesAndSpaces(process.env.no_proxy || process.env.NO_PROXY), href(options));
138
+ if (proxyUrl && typeof proxyUrl === 'string') {
139
+ proxyUrl = stripQuotesAndSpaces(proxyUrl);
140
+ }
141
+ if (shouldProxy) {
142
+ proxyUrl = new URL(proxyUrl);
143
+ let isHttps = proxyUrl.protocol === 'https:';
144
+ if (!isHttps && proxyUrl.protocol !== 'http:') {
145
+ throw new Error(`Unsupported proxy protocol: ${proxyUrl.protocol}`);
146
+ }
147
+ let proxy = {
148
+ isHttps
149
+ };
150
+ proxy.auth = !!proxyUrl.username && 'Basic ' + (proxyUrl.password ? Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`) : Buffer.from(proxyUrl.username)).toString('base64');
151
+ proxy.host = proxyUrl.hostname;
152
+ proxy.port = port(proxyUrl);
153
+ proxy.connect = () => (isHttps ? _tls.default : _net.default).connect({
154
+ rejectUnauthorized: options.rejectUnauthorized,
155
+ host: proxy.host,
156
+ port: proxy.port
157
+ });
158
+ return proxy;
159
+ }
160
+ }
161
+
162
+ // Proxified http agent
163
+ class ProxyHttpAgent extends _http.default.Agent {
164
+ // needed for https proxies
165
+ httpsAgent = new _https.default.Agent({
166
+ keepAlive: true
167
+ });
168
+ addRequest(request, options) {
169
+ var _request$outputData;
170
+ let proxy = getProxy(options);
171
+ if (!proxy) return super.addRequest(request, options);
172
+ (0, _logger.default)('sdk-utils:proxy').debug(`Proxying request: ${href(options)}`);
173
+
174
+ // modify the request for proxying
175
+ request.path = href(options);
176
+ if (proxy.auth) {
177
+ request.setHeader('Proxy-Authorization', proxy.auth);
178
+ }
179
+
180
+ // regenerate headers since we just changed things
181
+ delete request._header;
182
+ request._implicitHeader();
183
+ if (((_request$outputData = request.outputData) === null || _request$outputData === void 0 ? void 0 : _request$outputData.length) > 0) {
184
+ let first = request.outputData[0].data;
185
+ let endOfHeaders = first.indexOf(CRLF.repeat(2)) + 4;
186
+ request.outputData[0].data = request._header + first.substring(endOfHeaders);
187
+ }
188
+
189
+ // coerce the connection to the proxy
190
+ options.port = proxy.port;
191
+ options.host = proxy.host;
192
+ delete options.path;
193
+ if (proxy.isHttps) {
194
+ // use the underlying https agent to complete the connection
195
+ request.agent = this.httpsAgent;
196
+ return this.httpsAgent.addRequest(request, options);
197
+ } else {
198
+ return super.addRequest(request, options);
199
+ }
200
+ }
201
+ }
202
+
203
+ // Proxified https agent
204
+ exports.ProxyHttpAgent = ProxyHttpAgent;
205
+ class ProxyHttpsAgent extends _https.default.Agent {
206
+ constructor(options) {
207
+ // default keep-alive
208
+ super({
209
+ keepAlive: true,
210
+ ...options
211
+ });
212
+ }
213
+ createConnection(options, callback) {
214
+ let proxy = getProxy(options);
215
+ if (!proxy) return super.createConnection(options, callback);
216
+ (0, _logger.default)('sdk-utils:proxy').debug(`Proxying request: ${href(options)}`);
217
+
218
+ // generate proxy connect message
219
+ let host = `${options.hostname}:${port(options)}`;
220
+ let connectMessage = [`CONNECT ${host} HTTP/1.1`, `Host: ${host}`];
221
+ if (proxy.auth) {
222
+ connectMessage.push(`Proxy-Authorization: ${proxy.auth}`);
223
+ }
224
+ connectMessage = connectMessage.join(CRLF);
225
+ connectMessage += CRLF.repeat(2);
226
+
227
+ // start the proxy connection and setup listeners
228
+ let socket = proxy.connect();
229
+ let handleError = err => {
230
+ var _err$message, _err$message2;
231
+ socket.destroy(err);
232
+ (0, _logger.default)('sdk-utils:proxy').error(`Proxying request ${href(options)} failed: ${err}`);
233
+
234
+ // We don't get statusCode here, relying on checking error message only
235
+ if (!!err.message && ((_err$message = err.message) !== null && _err$message !== void 0 && _err$message.includes('ECONNREFUSED') || (_err$message2 = err.message) !== null && _err$message2 !== void 0 && _err$message2.includes('EHOSTUNREACH'))) {
236
+ (0, _logger.default)('sdk-utils:proxy').warn('If needed, please verify that your proxy credentials are correct.');
237
+ (0, _logger.default)('sdk-utils:proxy').warn('Please check that your proxy is configured correctly and reachable.');
238
+ }
239
+ (0, _logger.default)('sdk-utils:proxy').warn('Please ensure that the following domains are whitelisted: github.com, percy.io, storage.googleapis.com. If you are an enterprise customer, also whitelist "percy-enterprise.browserstack.com".');
240
+ callback(err);
241
+ };
242
+ let handleClose = () => handleError(new Error('Connection closed while sending request to upstream proxy'));
243
+ let buffer = '';
244
+ let handleData = data => {
245
+ var _buffer$match;
246
+ buffer += data.toString();
247
+ // haven't received end of headers yet, keep buffering
248
+ if (!buffer.includes(CRLF.repeat(2))) return;
249
+ // stop listening after end of headers
250
+ socket.off('data', handleData);
251
+ if (((_buffer$match = buffer.match(STATUS_REG)) === null || _buffer$match === void 0 ? void 0 : _buffer$match[1]) !== '200') {
252
+ return handleError(new Error('Error establishing proxy connection. ' + `Response from server was: ${buffer}`));
253
+ }
254
+ options.socket = socket;
255
+ options.servername = options.hostname;
256
+ // callback not passed in so not to be added as a listener
257
+ callback(null, super.createConnection(options));
258
+ };
259
+
260
+ // send and handle the connect message
261
+ socket.on('error', handleError).on('close', handleClose).on('data', handleData).write(connectMessage);
262
+ }
263
+ }
264
+ exports.ProxyHttpsAgent = ProxyHttpsAgent;
265
+ function proxyAgentFor(url, options) {
266
+ let cache = proxyAgentFor.cache || (proxyAgentFor.cache = new Map());
267
+ let {
268
+ protocol,
269
+ hostname
270
+ } = new URL(url);
271
+ let cachekey = `${protocol}//${hostname}`;
272
+
273
+ // If we already have a cached agent, return it
274
+ if (cache.has(cachekey)) {
275
+ return cache.get(cachekey);
276
+ }
277
+ try {
278
+ let agent;
279
+ const pacUrl = process.env.PERCY_PAC_FILE_URL;
280
+
281
+ // If PAC URL is provided, use PAC proxy
282
+ if (pacUrl) {
283
+ (0, _logger.default)('sdk-utils:proxy').info(`Using PAC file from: ${pacUrl}`);
284
+ agent = createPacAgent(pacUrl, options);
285
+ } else {
286
+ // Fall back to other proxy configuration
287
+ agent = protocol === 'https:' ? new ProxyHttpsAgent(options) : new ProxyHttpAgent(options);
288
+ }
289
+
290
+ // Cache the created agent
291
+ cache.set(cachekey, agent);
292
+ return agent;
293
+ } catch (error) {
294
+ (0, _logger.default)('sdk-utils:proxy').error(`Failed to create proxy agent: ${error.message}`);
295
+ throw error;
296
+ }
297
+ }
package/dist/request.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  exports.request = request;
8
8
  var _percyInfo = _interopRequireDefault(require("./percy-info.js"));
9
+ var _logger = _interopRequireDefault(require("./logger.js"));
9
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
11
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
11
12
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -62,8 +63,25 @@ if (process.env.__PERCY_BROWSERIFIED__) {
62
63
  let {
63
64
  default: http
64
65
  } = protocol === 'https:' ? await Promise.resolve().then(() => _interopRequireWildcard(require('https'))) : await Promise.resolve().then(() => _interopRequireWildcard(require('http')));
66
+ const requestOptions = {
67
+ ...options
68
+ };
69
+ try {
70
+ const {
71
+ proxyAgentFor
72
+ } = await Promise.resolve().then(() => _interopRequireWildcard(require('./proxy.js')));
73
+ const agent = proxyAgentFor(url);
74
+ if (agent) {
75
+ requestOptions.agent = agent;
76
+ }
77
+ } catch (error) {
78
+ // Failed to load proxy module or create proxy agent (e.g., missing proxy.js, invalid proxy config)
79
+ // Continue without proxy support - requests will go directly without proxy
80
+ /* istanbul ignore next */
81
+ (0, _logger.default)('sdk-utils:request').debug(`Proxy agent unavailable: ${error.message}`);
82
+ }
65
83
  return new Promise((resolve, reject) => {
66
- http.request(url, options).on('response', response => {
84
+ http.request(url, requestOptions).on('response', response => {
67
85
  let body = '';
68
86
  response.on('data', chunk => body += chunk.toString());
69
87
  response.on('end', () => resolve({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/sdk-utils",
3
- "version": "1.31.3-beta.2",
3
+ "version": "1.31.3-beta.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,5 +51,8 @@
51
51
  ]
52
52
  }
53
53
  },
54
- "gitHead": "88c987a1544fc803397b5298b88f7a254c729a75"
54
+ "dependencies": {
55
+ "pac-proxy-agent": "^5.0.0"
56
+ },
57
+ "gitHead": "d4fa0ae4e58a2f8fd2e389492ee6f2ef0e6c7fd0"
55
58
  }