@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 +18 -1
- package/dist/proxy.js +297 -0
- package/dist/request.js +19 -1
- package/package.json +5 -2
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,
|
|
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,
|
|
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.
|
|
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
|
-
"
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"pac-proxy-agent": "^5.0.0"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "d4fa0ae4e58a2f8fd2e389492ee6f2ef0e6c7fd0"
|
|
55
58
|
}
|