@percy/core 1.29.1-alpha.0 → 1.29.1-beta.0

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/config.js CHANGED
@@ -7,6 +7,10 @@ export const configSchema = {
7
7
  deferUploads: {
8
8
  type: 'boolean'
9
9
  },
10
+ useSystemProxy: {
11
+ type: 'boolean',
12
+ default: false
13
+ },
10
14
  token: {
11
15
  type: 'string'
12
16
  },
@@ -584,6 +588,9 @@ export const snapshotSchema = {
584
588
  type: 'string'
585
589
  }
586
590
  },
591
+ cookies: {
592
+ type: 'string'
593
+ },
587
594
  resources: {
588
595
  type: 'array',
589
596
  items: {
package/dist/discovery.js CHANGED
@@ -64,10 +64,11 @@ function debugSnapshotOptions(snapshot) {
64
64
  function waitForDiscoveryNetworkIdle(page, options) {
65
65
  let {
66
66
  allowedHostnames,
67
- networkIdleTimeout
67
+ networkIdleTimeout,
68
+ captureResponsiveAssetsEnabled
68
69
  } = options;
69
70
  let filter = r => hostnameMatches(allowedHostnames, r.url);
70
- return page.network.idle(filter, networkIdleTimeout);
71
+ return page.network.idle(filter, networkIdleTimeout, captureResponsiveAssetsEnabled);
71
72
  }
72
73
 
73
74
  // Creates an initial resource map for a snapshot containing serialized DOM
@@ -174,18 +175,55 @@ async function* captureSnapshotResources(page, snapshot, options) {
174
175
  mobile,
175
176
  captureForDevices
176
177
  } = options;
178
+ let cookies;
179
+ if (process.env.PERCY_DO_NOT_USE_CAPTURED_COOKIES !== 'true') {
180
+ var _snapshot$domSnapshot;
181
+ cookies = snapshot === null || snapshot === void 0 ? void 0 : (_snapshot$domSnapshot = snapshot.domSnapshot) === null || _snapshot$domSnapshot === void 0 ? void 0 : _snapshot$domSnapshot.cookies;
182
+ }
183
+ if (typeof cookies === 'string' && cookies !== '') {
184
+ cookies = cookies.split('; ').map(c => c.split('='));
185
+ cookies = cookies.map(([key, value]) => {
186
+ return {
187
+ name: key,
188
+ value: value
189
+ };
190
+ });
191
+ }
192
+
193
+ // iterate over device to trigger reqeusts and capture other dpr width
194
+ async function* captureResponsiveAssets() {
195
+ for (const device of captureForDevices) {
196
+ discovery = {
197
+ ...discovery,
198
+ captureResponsiveAssetsEnabled: true
199
+ };
200
+
201
+ // We are not adding these widths and pixels ratios in loop below because we want to explicitly reload the page after resize which we dont do below
202
+ yield* captureSnapshotResources(page, {
203
+ ...snapshot,
204
+ discovery,
205
+ widths: [device.width]
206
+ }, {
207
+ deviceScaleFactor: device.deviceScaleFactor,
208
+ mobile: true
209
+ });
210
+ yield waitForDiscoveryNetworkIdle(page, discovery);
211
+ }
212
+ }
177
213
 
178
214
  // used to take snapshots and remove any discovered root resource
179
- let takeSnapshot = async (options, width) => {
215
+ async function* takeSnapshot(options, width) {
180
216
  if (captureWidths) options = {
181
217
  ...options,
182
218
  width
183
219
  };
184
220
  let captured = await page.snapshot(options);
221
+ yield* captureResponsiveAssets();
185
222
  captured.resources.delete(normalizeURL(captured.url));
186
223
  capture(processSnapshotResources(captured));
187
224
  return captured;
188
- };
225
+ }
226
+ ;
189
227
 
190
228
  // used to resize the using capture options
191
229
  let resizePage = width => page.resize({
@@ -197,7 +235,9 @@ async function* captureSnapshotResources(page, snapshot, options) {
197
235
 
198
236
  // navigate to the url
199
237
  yield resizePage(snapshot.widths[0]);
200
- yield page.goto(snapshot.url);
238
+ yield page.goto(snapshot.url, {
239
+ cookies
240
+ });
201
241
  if (snapshot.execute) {
202
242
  // when any execute options are provided, inject snapshot options
203
243
  /* istanbul ignore next: cannot detect coverage of injected code */
@@ -226,25 +266,10 @@ async function* captureSnapshotResources(page, snapshot, options) {
226
266
  } = snap;
227
267
  let [width] = widths;
228
268
 
229
- // iterate over device to trigger reqeusts and capture other dpr width
230
- if (captureForDevices) {
231
- for (const device of captureForDevices) {
232
- yield waitForDiscoveryNetworkIdle(page, discovery);
233
- // We are not adding these widths and pixels ratios in loop below because we want to explicitly reload the page after resize which we dont do below
234
- yield* captureSnapshotResources(page, {
235
- ...snapshot,
236
- widths: [device.width]
237
- }, {
238
- deviceScaleFactor: device.deviceScaleFactor,
239
- mobile: true
240
- });
241
- }
242
- }
243
-
244
269
  // iterate over widths to trigger reqeusts and capture other widths
245
270
  if (isBaseSnapshot || captureWidths) {
246
271
  for (let i = 0; i < widths.length - 1; i++) {
247
- if (captureWidths) yield takeSnapshot(snap, width);
272
+ if (captureWidths) yield* takeSnapshot(snap, width);
248
273
  yield page.evaluate(execute === null || execute === void 0 ? void 0 : execute.beforeResize);
249
274
  yield waitForDiscoveryNetworkIdle(page, discovery);
250
275
  yield resizePage(width = widths[i + 1]);
@@ -253,7 +278,7 @@ async function* captureSnapshotResources(page, snapshot, options) {
253
278
  }
254
279
  if (capture && !snapshot.domSnapshot) {
255
280
  // capture this snapshot and update the base snapshot after capture
256
- let captured = yield takeSnapshot(snap, width);
281
+ let captured = yield* takeSnapshot(snap, width);
257
282
  if (isBaseSnapshot) baseSnapshot = captured;
258
283
 
259
284
  // resize back to the initial width when capturing additional snapshot widths
@@ -272,6 +297,7 @@ async function* captureSnapshotResources(page, snapshot, options) {
272
297
  // wait for final network idle when not capturing DOM
273
298
  if (capture && snapshot.domSnapshot) {
274
299
  yield waitForDiscoveryNetworkIdle(page, discovery);
300
+ yield* captureResponsiveAssets();
275
301
  capture(processSnapshotResources(snapshot));
276
302
  }
277
303
  }
package/dist/network.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { request as makeRequest } from '@percy/client/utils';
2
2
  import logger from '@percy/logger';
3
3
  import mime from 'mime-types';
4
- import { DefaultMap, createResource, hostnameMatches, normalizeURL, waitFor } from './utils.js';
4
+ import { DefaultMap, createResource, decodeAndEncodeURLWithLogging, hostnameMatches, normalizeURL, waitFor } from './utils.js';
5
5
  const MAX_RESOURCE_SIZE = 25 * 1024 ** 2; // 25MB
6
6
  const ALLOWED_STATUSES = [200, 201, 301, 302, 304, 307, 308];
7
7
  const ALLOWED_RESOURCES = ['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Other'];
@@ -71,7 +71,7 @@ export class Network {
71
71
  }
72
72
 
73
73
  // Resolves after the timeout when there are no more in-flight requests.
74
- async idle(filter = () => true, timeout = this.timeout) {
74
+ async idle(filter = () => true, timeout = this.timeout, captureResponsiveAssetsEnabled = false) {
75
75
  let requests = [];
76
76
  this.log.debug(`Wait for ${timeout}ms idle`, this.meta);
77
77
  await waitFor(() => {
@@ -91,11 +91,25 @@ export class Network {
91
91
  idle: timeout
92
92
  }).catch(error => {
93
93
  if (error.message.startsWith('Timeout')) {
94
- this._throwTimeoutError('Timed out waiting for network requests to idle.', filter);
94
+ let message = 'Timed out waiting for network requests to idle.';
95
+ if (captureResponsiveAssetsEnabled) message += '\nWhile capturing responsive assets try setting PERCY_DO_NOT_CAPTURE_RESPONSIVE_ASSETS to true.';
96
+ this._throwTimeoutError(message, filter);
95
97
  } else {
96
98
  throw error;
97
99
  }
98
100
  });
101
+
102
+ // After waiting for network to idle check if there are still some request
103
+ const activeRequests = this.getActiveRequests(filter);
104
+ /* istanbul ignore if: race condition, very hard to mock this */
105
+ if (activeRequests.length > 0) {
106
+ this.log.debug(`There are ${activeRequests.length} active requests pending during asset discovery. Try increasing the networkIdleTimeout to resolve this issue. \n ${activeRequests}`);
107
+ }
108
+ }
109
+ getActiveRequests(filter) {
110
+ let requests = Array.from(this.#requests.values()).filter(filter);
111
+ requests = requests.filter(req => !this.#finishedUrls.has(req.url));
112
+ return requests;
99
113
  }
100
114
 
101
115
  // Validates that requestId is still valid as sometimes request gets cancelled and we have already executed
@@ -199,6 +213,17 @@ export class Network {
199
213
 
200
214
  // do not handle data urls
201
215
  if (request.url.startsWith('data:')) return;
216
+
217
+ // Browsers handle URL encoding leniently, but invalid characters can break tools like Jackproxy.
218
+ // This code checks for issues such as `%` and leading spaces and warns the user accordingly.
219
+ decodeAndEncodeURLWithLogging(request.url, this.log, {
220
+ meta: {
221
+ ...this.meta,
222
+ url: request.url
223
+ },
224
+ shouldLogWarning: true,
225
+ warningMessage: `An invalid URL was detected for url: ${request.url} - the snapshot may fail on Percy. Please verify that your asset URL is valid.`
226
+ });
202
227
  if (this.intercept) {
203
228
  this.#pending.set(requestId, event);
204
229
  if (this.captureMockedServiceWorker) {
package/dist/page.js CHANGED
@@ -41,19 +41,38 @@ export class Page {
41
41
  width
42
42
  });
43
43
  }
44
+ mergeCookies(userPassedCookie, autoCapturedCookie) {
45
+ if (!autoCapturedCookie) return userPassedCookie;
46
+ if (userPassedCookie.length === 0) return autoCapturedCookie;
47
+
48
+ // User passed cookie will be prioritized over auto captured cookie
49
+ const mergedCookies = [...userPassedCookie, ...autoCapturedCookie];
50
+ const uniqueCookies = [];
51
+ const names = new Set();
52
+ for (const cookie of mergedCookies) {
53
+ if (!names.has(cookie.name)) {
54
+ uniqueCookies.push(cookie);
55
+ names.add(cookie.name);
56
+ }
57
+ }
58
+ return uniqueCookies;
59
+ }
44
60
 
45
61
  // Go to a URL and wait for navigation to occur
46
62
  async goto(url, {
47
- waitUntil = 'load'
63
+ waitUntil = 'load',
64
+ cookies
48
65
  } = {}) {
49
66
  this.log.debug(`Navigate to: ${url}`, this.meta);
50
67
  let navigate = async () => {
68
+ const userPassedCookie = this.session.browser.cookies;
51
69
  // set cookies before navigation so we can default the domain to this hostname
52
- if (this.session.browser.cookies.length) {
70
+ if (userPassedCookie.length || cookies) {
53
71
  let defaultDomain = hostname(url);
72
+ cookies = this.mergeCookies(userPassedCookie, cookies);
54
73
  await this.session.send('Network.setCookies', {
55
74
  // spread is used to make a shallow copy of the cookie
56
- cookies: this.session.browser.cookies.map(({
75
+ cookies: cookies.map(({
57
76
  ...cookie
58
77
  }) => {
59
78
  if (!cookie.url) cookie.domain || (cookie.domain = defaultDomain);
package/dist/percy.js CHANGED
@@ -4,16 +4,20 @@ function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("C
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5
5
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
6
6
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; }
8
+ function _classPrivateFieldGet2(e, t) { var r = _classPrivateFieldGet(t, e); return _classApplyDescriptorGet(e, r); }
9
+ function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; }
10
+ function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet(t, e); return _classApplyDescriptorSet(e, s, r), r; }
7
11
  function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
8
- function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
9
12
  function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
13
+ function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } }
10
14
  import PercyClient from '@percy/client';
11
15
  import PercyConfig from '@percy/config';
12
16
  import logger from '@percy/logger';
13
17
  import { getProxy } from '@percy/client/utils';
14
18
  import Browser from './browser.js';
15
19
  import Pako from 'pako';
16
- import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets } from './utils.js';
20
+ import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets, detectSystemProxyAndLog } from './utils.js';
17
21
  import { createPercyServer, createStaticServer } from './api.js';
18
22
  import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js';
19
23
  import { discoverSnapshotResources, createDiscoveryQueue } from './discovery.js';
@@ -24,7 +28,8 @@ import { WaitForJob } from './wait-for-job.js';
24
28
  // concurrently and the build is not finalized until all snapshots have been handled.
25
29
  var _discovery = /*#__PURE__*/new WeakMap();
26
30
  var _snapshots = /*#__PURE__*/new WeakMap();
27
- var _Percy_brand = /*#__PURE__*/new WeakSet();
31
+ var _displaySuggestionLogs = /*#__PURE__*/new WeakSet();
32
+ var _proxyEnabled = /*#__PURE__*/new WeakSet();
28
33
  export class Percy {
29
34
  // Static shortcut to create and start an instance in one call
30
35
  static async start(options) {
@@ -63,11 +68,18 @@ export class Percy {
63
68
  ..._options
64
69
  } = {}) {
65
70
  var _config$percy, _config$percy2;
66
- _classPrivateMethodInitSpec(this, _Percy_brand);
71
+ _classPrivateMethodInitSpec(this, _proxyEnabled);
72
+ _classPrivateMethodInitSpec(this, _displaySuggestionLogs);
67
73
  _defineProperty(this, "log", logger('core'));
68
74
  _defineProperty(this, "readyState", null);
69
- _classPrivateFieldInitSpec(this, _discovery, null);
70
- _classPrivateFieldInitSpec(this, _snapshots, null);
75
+ _classPrivateFieldInitSpec(this, _discovery, {
76
+ writable: true,
77
+ value: null
78
+ });
79
+ _classPrivateFieldInitSpec(this, _snapshots, {
80
+ writable: true,
81
+ value: null
82
+ });
71
83
  let config = PercyConfig.load({
72
84
  overrides: _options,
73
85
  path: configFile
@@ -95,8 +107,8 @@ export class Percy {
95
107
  });
96
108
  if (server) this.server = createPercyServer(this, port);
97
109
  this.browser = new Browser(this);
98
- _classPrivateFieldSet(_discovery, this, createDiscoveryQueue(this));
99
- _classPrivateFieldSet(_snapshots, this, createSnapshotsQueue(this));
110
+ _classPrivateFieldSet(this, _discovery, createDiscoveryQueue(this));
111
+ _classPrivateFieldSet(this, _snapshots, createSnapshotsQueue(this));
100
112
 
101
113
  // generator methods are wrapped to autorun and return promises
102
114
  for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload']) {
@@ -149,10 +161,10 @@ export class Percy {
149
161
  let {
150
162
  concurrency
151
163
  } = this.config.discovery;
152
- _classPrivateFieldGet(_discovery, this).set({
164
+ _classPrivateFieldGet2(this, _discovery).set({
153
165
  concurrency
154
166
  });
155
- _classPrivateFieldGet(_snapshots, this).set({
167
+ _classPrivateFieldGet2(this, _snapshots).set({
156
168
  concurrency
157
169
  });
158
170
  return this.config;
@@ -167,10 +179,13 @@ export class Percy {
167
179
  if (process.env.PERCY_CLIENT_ERROR_LOGS !== 'false') {
168
180
  this.log.warn('Notice: Percy collects CI logs for service improvement, stored for 30 days. Opt-out anytime with export PERCY_CLIENT_ERROR_LOGS=false');
169
181
  }
182
+ // Not awaiting proxy check as this can be asyncronous when not enabled
183
+ const detectProxy = detectSystemProxyAndLog(this.config.percy.useSystemProxy);
184
+ if (this.config.percy.useSystemProxy) await detectProxy;
170
185
  // start the snapshots queue immediately when not delayed or deferred
171
- if (!this.delayUploads && !this.deferUploads) yield _classPrivateFieldGet(_snapshots, this).start();
186
+ if (!this.delayUploads && !this.deferUploads) yield _classPrivateFieldGet2(this, _snapshots).start();
172
187
  // do not start the discovery queue when not needed
173
- if (!this.skipDiscovery) yield _classPrivateFieldGet(_discovery, this).start();
188
+ if (!this.skipDiscovery) yield _classPrivateFieldGet2(this, _discovery).start();
174
189
  // start a local API server for SDK communication
175
190
  if (this.server) yield this.server.listen();
176
191
  if (this.projectType === 'web') {
@@ -188,8 +203,8 @@ export class Percy {
188
203
  var _this$server2;
189
204
  // on error, close any running server and end queues
190
205
  await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close());
191
- await _classPrivateFieldGet(_discovery, this).end();
192
- await _classPrivateFieldGet(_snapshots, this).end();
206
+ await _classPrivateFieldGet2(this, _discovery).end();
207
+ await _classPrivateFieldGet2(this, _snapshots).end();
193
208
 
194
209
  // mark this instance as closed unless aborting
195
210
  this.readyState = error.name !== 'AbortError' ? 3 : null;
@@ -208,8 +223,8 @@ export class Percy {
208
223
 
209
224
  // Resolves once snapshot and upload queues are idle
210
225
  async *idle() {
211
- yield* _classPrivateFieldGet(_discovery, this).idle();
212
- yield* _classPrivateFieldGet(_snapshots, this).idle();
226
+ yield* _classPrivateFieldGet2(this, _discovery).idle();
227
+ yield* _classPrivateFieldGet2(this, _snapshots).idle();
213
228
  }
214
229
 
215
230
  // Wait for currently queued snapshots then run and wait for resulting uploads
@@ -222,13 +237,13 @@ export class Percy {
222
237
  yield new Promise(r => setImmediate(r));
223
238
 
224
239
  // flush and log progress for discovery before snapshots
225
- if (!this.skipDiscovery && _classPrivateFieldGet(_discovery, this).size) {
226
- if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet(_discovery, this).process(o)));else yield* _classPrivateFieldGet(_discovery, this).flush(size => callback === null || callback === void 0 ? void 0 : callback('Processing', size));
240
+ if (!this.skipDiscovery && _classPrivateFieldGet2(this, _discovery).size) {
241
+ if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet2(this, _discovery).process(o)));else yield* _classPrivateFieldGet2(this, _discovery).flush(size => callback === null || callback === void 0 ? void 0 : callback('Processing', size));
227
242
  }
228
243
 
229
244
  // flush and log progress for snapshot uploads
230
- if (!this.skipUploads && _classPrivateFieldGet(_snapshots, this).size) {
231
- if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet(_snapshots, this).process(o)));else yield* _classPrivateFieldGet(_snapshots, this).flush(size => callback === null || callback === void 0 ? void 0 : callback('Uploading', size));
245
+ if (!this.skipUploads && _classPrivateFieldGet2(this, _snapshots).size) {
246
+ if (options) yield* yieldAll(options.map(o => _classPrivateFieldGet2(this, _snapshots).process(o)));else yield* _classPrivateFieldGet2(this, _snapshots).flush(size => callback === null || callback === void 0 ? void 0 : callback('Uploading', size));
232
247
  }
233
248
  }
234
249
 
@@ -247,8 +262,8 @@ export class Percy {
247
262
 
248
263
  // close queues asap
249
264
  if (force) {
250
- _classPrivateFieldGet(_discovery, this).close(true);
251
- _classPrivateFieldGet(_snapshots, this).close(true);
265
+ _classPrivateFieldGet2(this, _discovery).close(true);
266
+ _classPrivateFieldGet2(this, _snapshots).close(true);
252
267
  }
253
268
 
254
269
  // already stopping
@@ -273,14 +288,14 @@ export class Percy {
273
288
  }
274
289
 
275
290
  // if dry-running, log the total number of snapshots
276
- if (this.dryRun && _classPrivateFieldGet(_snapshots, this).size) {
277
- this.log.info(info('Found', _classPrivateFieldGet(_snapshots, this).size));
291
+ if (this.dryRun && _classPrivateFieldGet2(this, _snapshots).size) {
292
+ this.log.info(info('Found', _classPrivateFieldGet2(this, _snapshots).size));
278
293
  }
279
294
 
280
295
  // close server and end queues
281
296
  await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
282
- await _classPrivateFieldGet(_discovery, this).end();
283
- await _classPrivateFieldGet(_snapshots, this).end();
297
+ await _classPrivateFieldGet2(this, _discovery).end();
298
+ await _classPrivateFieldGet2(this, _snapshots).end();
284
299
 
285
300
  // mark instance as stopped
286
301
  this.readyState = 3;
@@ -343,7 +358,7 @@ export class Percy {
343
358
  }
344
359
 
345
360
  // gather snapshots and discover snapshot resources
346
- yield* discoverSnapshotResources(_classPrivateFieldGet(_discovery, this), {
361
+ yield* discoverSnapshotResources(_classPrivateFieldGet2(this, _discovery), {
347
362
  skipDiscovery: this.skipDiscovery,
348
363
  dryRun: this.dryRun,
349
364
  snapshots: yield* gatherSnapshots(options, {
@@ -363,7 +378,7 @@ export class Percy {
363
378
  });
364
379
  }
365
380
  // push each finished snapshot to the snapshots queue
366
- _classPrivateFieldGet(_snapshots, this).push(snapshot);
381
+ _classPrivateFieldGet2(this, _snapshots).push(snapshot);
367
382
  });
368
383
  } finally {
369
384
  var _server;
@@ -423,9 +438,9 @@ export class Percy {
423
438
  // return an async generator to allow cancelation
424
439
  return async function* () {
425
440
  try {
426
- return yield* yieldTo(_classPrivateFieldGet(_snapshots, this).push(options));
441
+ return yield* yieldTo(_classPrivateFieldGet2(this, _snapshots).push(options));
427
442
  } catch (error) {
428
- _classPrivateFieldGet(_snapshots, this).cancel(options);
443
+ _classPrivateFieldGet2(this, _snapshots).cancel(options);
429
444
  // Detecting and suggesting fix for errors;
430
445
  await this.suggestionsForFix(error.message);
431
446
  throw error;
@@ -475,7 +490,7 @@ export class Percy {
475
490
  });
476
491
  if (isPercyStarted && !containsSnapshotTaken) {
477
492
  // This is the case for No snapshot command called
478
- _assertClassBrand(_Percy_brand, this, _displaySuggestionLogs).call(this, [{
493
+ _classPrivateMethodGet(this, _displaySuggestionLogs, _displaySuggestionLogs2).call(this, [{
479
494
  failure_reason: 'Snapshot command was not called',
480
495
  reason_message: 'Snapshot Command was not called. please check your CI for errors',
481
496
  suggestion: 'Try using percy snapshot command to take snapshots',
@@ -486,14 +501,14 @@ export class Percy {
486
501
  async suggestionsForFix(errors, options = {}) {
487
502
  try {
488
503
  const suggestionResponse = await this.client.getErrorAnalysis(errors);
489
- _assertClassBrand(_Percy_brand, this, _displaySuggestionLogs).call(this, suggestionResponse, options);
504
+ _classPrivateMethodGet(this, _displaySuggestionLogs, _displaySuggestionLogs2).call(this, suggestionResponse, options);
490
505
  } catch (e) {
491
506
  // Common error code for Proxy issues
492
507
  const PROXY_CODES = ['ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH'];
493
508
  if (!!e.code && PROXY_CODES.includes(e.code)) {
494
509
  // This can be due to proxy issue
495
510
  this.log.error('percy.io might not be reachable, check network connection, proxy and ensure that percy.io is whitelisted.');
496
- if (!_assertClassBrand(_Percy_brand, this, _proxyEnabled).call(this)) {
511
+ if (!_classPrivateMethodGet(this, _proxyEnabled, _proxyEnabled2).call(this)) {
497
512
  this.log.error('If inside a proxied envirnment, please configure the following environment variables: HTTP_PROXY, [ and optionally HTTPS_PROXY if you need it ]. Refer to our documentation for more details');
498
513
  }
499
514
  }
@@ -532,7 +547,7 @@ export class Percy {
532
547
  }
533
548
  }
534
549
  }
535
- function _displaySuggestionLogs(suggestions, options = {}) {
550
+ function _displaySuggestionLogs2(suggestions, options = {}) {
536
551
  if (!(suggestions !== null && suggestions !== void 0 && suggestions.length)) return;
537
552
  suggestions.forEach(item => {
538
553
  const failure = item === null || item === void 0 ? void 0 : item.failure_reason;
@@ -555,7 +570,7 @@ function _displaySuggestionLogs(suggestions, options = {}) {
555
570
  }
556
571
  });
557
572
  }
558
- function _proxyEnabled() {
573
+ function _proxyEnabled2() {
559
574
  return !!(getProxy({
560
575
  protocol: 'https:'
561
576
  }) || getProxy({}));
package/dist/queue.js CHANGED
@@ -4,9 +4,13 @@ function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("C
4
4
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5
5
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
6
6
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
- function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
8
- function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
7
+ function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet2(t, e); return _classApplyDescriptorSet(e, s, r), r; }
8
+ function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } }
9
+ function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; }
10
+ function _classPrivateFieldGet(e, t) { var r = _classPrivateFieldGet2(t, e); return _classApplyDescriptorGet(e, r); }
11
+ function _classPrivateFieldGet2(s, a) { return s.get(_assertClassBrand(s, a)); }
9
12
  function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
13
+ function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; }
10
14
  import { yieldFor, generatePromise, AbortController } from './utils.js';
11
15
  import logger from '@percy/logger';
12
16
 
@@ -39,25 +43,42 @@ class QueueClosedError extends Error {
39
43
  var _handlers = /*#__PURE__*/new WeakMap();
40
44
  var _queued = /*#__PURE__*/new WeakMap();
41
45
  var _pending = /*#__PURE__*/new WeakMap();
42
- var _Queue_brand = /*#__PURE__*/new WeakSet();
46
+ var _dequeue = /*#__PURE__*/new WeakSet();
47
+ var _find = /*#__PURE__*/new WeakSet();
43
48
  var _start = /*#__PURE__*/new WeakMap();
44
49
  var _end = /*#__PURE__*/new WeakMap();
50
+ var _process = /*#__PURE__*/new WeakSet();
51
+ var _until = /*#__PURE__*/new WeakSet();
45
52
  export class Queue {
53
+ // item concurrency
54
+
46
55
  constructor(name) {
47
- // Maybe processes the next queued item task.
48
- _classPrivateMethodInitSpec(this, _Queue_brand);
49
- // item concurrency
56
+ _classPrivateMethodInitSpec(this, _until);
57
+ _classPrivateMethodInitSpec(this, _process);
58
+ _classPrivateMethodInitSpec(this, _find);
59
+ _classPrivateMethodInitSpec(this, _dequeue);
50
60
  _defineProperty(this, "concurrency", 10);
51
61
  _defineProperty(this, "log", logger('core:queue'));
52
- // Configure queue handlers
53
- _classPrivateFieldInitSpec(this, _handlers, {});
54
- // internal queues
55
- _classPrivateFieldInitSpec(this, _queued, new Set());
56
- _classPrivateFieldInitSpec(this, _pending, new Set());
57
- // keep track of start and end tasks
58
- _classPrivateFieldInitSpec(this, _start, null);
59
- _classPrivateFieldInitSpec(this, _end, null);
60
- // represents various queue states such as ready, running, or closed
62
+ _classPrivateFieldInitSpec(this, _handlers, {
63
+ writable: true,
64
+ value: {}
65
+ });
66
+ _classPrivateFieldInitSpec(this, _queued, {
67
+ writable: true,
68
+ value: new Set()
69
+ });
70
+ _classPrivateFieldInitSpec(this, _pending, {
71
+ writable: true,
72
+ value: new Set()
73
+ });
74
+ _classPrivateFieldInitSpec(this, _start, {
75
+ writable: true,
76
+ value: null
77
+ });
78
+ _classPrivateFieldInitSpec(this, _end, {
79
+ writable: true,
80
+ value: null
81
+ });
61
82
  _defineProperty(this, "readyState", 0);
62
83
  this.name = name;
63
84
  }
@@ -69,13 +90,19 @@ export class Queue {
69
90
  if (concurrency) this.concurrency = concurrency;
70
91
  return this;
71
92
  }
93
+
94
+ // Configure queue handlers
95
+
72
96
  handle(event, handler) {
73
- _classPrivateFieldGet(_handlers, this)[event] = handler;
97
+ _classPrivateFieldGet(this, _handlers)[event] = handler;
74
98
  return this;
75
99
  }
100
+
101
+ // internal queues
102
+
76
103
  // Queue size is total queued and pending items
77
104
  get size() {
78
- return _classPrivateFieldGet(_queued, this).size + _classPrivateFieldGet(_pending, this).size;
105
+ return _classPrivateFieldGet(this, _queued).size + _classPrivateFieldGet(this, _pending).size;
79
106
  }
80
107
 
81
108
  // Pushes an item into the queue, additional args are passed to any configured task handler.
@@ -86,8 +113,8 @@ export class Queue {
86
113
 
87
114
  // attach any configured error handler
88
115
  task.deferred = task.deferred.catch(e => {
89
- if (!_classPrivateFieldGet(_handlers, this).error) throw e;
90
- return _classPrivateFieldGet(_handlers, this).error(item, e);
116
+ if (!_classPrivateFieldGet(this, _handlers).error) throw e;
117
+ return _classPrivateFieldGet(this, _handlers).error(item, e);
91
118
  });
92
119
 
93
120
  // when closed, reject with a queue closed error
@@ -105,22 +132,25 @@ export class Queue {
105
132
  if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
106
133
  item._ctrl = task.ctrl;
107
134
  }
108
- task.item = item = _classPrivateFieldGet(_handlers, this).push ? _classPrivateFieldGet(_handlers, this).push(item, exists) : item;
109
- task.handler = () => _classPrivateFieldGet(_handlers, this).task ? _classPrivateFieldGet(_handlers, this).task(item, ...args) : item;
135
+ task.item = item = _classPrivateFieldGet(this, _handlers).push ? _classPrivateFieldGet(this, _handlers).push(item, exists) : item;
136
+ task.handler = () => _classPrivateFieldGet(this, _handlers).task ? _classPrivateFieldGet(this, _handlers).task(item, ...args) : item;
110
137
 
111
138
  // queue this task & maybe dequeue the next task
112
- _classPrivateFieldGet(_queued, this).add(task);
113
- _assertClassBrand(_Queue_brand, this, _dequeue).call(this);
139
+ _classPrivateFieldGet(this, _queued).add(task);
140
+ _classPrivateMethodGet(this, _dequeue, _dequeue2).call(this);
114
141
 
115
142
  // return the deferred task promise
116
143
  return task.deferred;
117
144
  }
145
+
146
+ // Maybe processes the next queued item task.
147
+
118
148
  // Cancels and aborts a specific item task.
119
149
  cancel(item) {
120
- let task = _assertClassBrand(_Queue_brand, this, _find).call(this, item);
150
+ let task = _classPrivateMethodGet(this, _find, _find2).call(this, item);
121
151
  task === null || task === void 0 ? void 0 : task.ctrl.abort();
122
- let queued = _classPrivateFieldGet(_queued, this).delete(task);
123
- let pending = _classPrivateFieldGet(_pending, this).delete(task);
152
+ let queued = _classPrivateFieldGet(this, _queued).delete(task);
153
+ let pending = _classPrivateFieldGet(this, _pending).delete(task);
124
154
 
125
155
  // reject queued tasks that are not pending
126
156
  if (task && queued && !pending) {
@@ -133,36 +163,39 @@ export class Queue {
133
163
 
134
164
  // Returns an item task matching the provided subject.
135
165
 
166
+ // keep track of start and end tasks
167
+
136
168
  // Initialize a starting task or return an existing one.
137
169
  start() {
138
- var _classPrivateFieldGet2;
139
- _classPrivateFieldGet(_start, this) ?? _classPrivateFieldSet(_start, this, deferred({
170
+ var _classPrivateFieldGet3;
171
+ _classPrivateFieldGet(this, _start) ?? _classPrivateFieldSet(this, _start, deferred({
140
172
  readyState: 1
141
173
  }));
142
- (_classPrivateFieldGet2 = _classPrivateFieldGet(_start, this)).handler ?? (_classPrivateFieldGet2.handler = _classPrivateFieldGet(_end, this)
143
- // wait for any ending task to complete first
144
- ? () => _classPrivateFieldGet(_end, this).promise.then(_classPrivateFieldGet(_handlers, this).start) : _classPrivateFieldGet(_handlers, this).start);
145
- return _assertClassBrand(_Queue_brand, this, _process).call(this, _classPrivateFieldGet(_start, this)).deferred;
174
+ (_classPrivateFieldGet3 = _classPrivateFieldGet(this, _start)).handler ?? (_classPrivateFieldGet3.handler = _classPrivateFieldGet(this, _end) // wait for any ending task to complete first
175
+ ? () => _classPrivateFieldGet(this, _end).promise.then(_classPrivateFieldGet(this, _handlers).start) : _classPrivateFieldGet(this, _handlers).start);
176
+ return _classPrivateMethodGet(this, _process, _process2).call(this, _classPrivateFieldGet(this, _start)).deferred;
146
177
  }
147
178
 
148
179
  // intialize an ending task or return an existing one
149
180
  end() {
150
- var _classPrivateFieldGet3;
151
- _classPrivateFieldGet(_end, this) ?? _classPrivateFieldSet(_end, this, deferred({
181
+ var _classPrivateFieldGet4;
182
+ _classPrivateFieldGet(this, _end) ?? _classPrivateFieldSet(this, _end, deferred({
152
183
  readyState: 0
153
184
  }));
154
- (_classPrivateFieldGet3 = _classPrivateFieldGet(_end, this)).handler ?? (_classPrivateFieldGet3.handler = _classPrivateFieldGet(_start, this)
155
- // wait for any starting task to complete first
156
- ? () => _classPrivateFieldGet(_start, this).promise.then(_classPrivateFieldGet(_handlers, this).end) : _classPrivateFieldGet(_handlers, this).end);
157
- return _assertClassBrand(_Queue_brand, this, _process).call(this, _classPrivateFieldGet(_end, this)).deferred;
185
+ (_classPrivateFieldGet4 = _classPrivateFieldGet(this, _end)).handler ?? (_classPrivateFieldGet4.handler = _classPrivateFieldGet(this, _start) // wait for any starting task to complete first
186
+ ? () => _classPrivateFieldGet(this, _start).promise.then(_classPrivateFieldGet(this, _handlers).end) : _classPrivateFieldGet(this, _handlers).end);
187
+ return _classPrivateMethodGet(this, _process, _process2).call(this, _classPrivateFieldGet(this, _end)).deferred;
158
188
  }
189
+
190
+ // represents various queue states such as ready, running, or closed
191
+
159
192
  // run the queue, starting it if necessary, and start dequeuing tasks
160
193
  run() {
161
- if (!_classPrivateFieldGet(_start, this)) this.start();
194
+ if (!_classPrivateFieldGet(this, _start)) this.start();
162
195
  // when starting, state is updated afterwards
163
- if (this.readyState === 0) _classPrivateFieldGet(_start, this).readyState = 2;
196
+ if (this.readyState === 0) _classPrivateFieldGet(this, _start).readyState = 2;
164
197
  if (this.readyState === 1) this.readyState = 2;
165
- while (_assertClassBrand(_Queue_brand, this, _dequeue).call(this)) _assertClassBrand(_Queue_brand, this, _dequeue).call(this);
198
+ while (_classPrivateMethodGet(this, _dequeue, _dequeue2).call(this)) _classPrivateMethodGet(this, _dequeue, _dequeue2).call(this);
166
199
  return this;
167
200
  }
168
201
 
@@ -174,9 +207,9 @@ export class Queue {
174
207
 
175
208
  // close a running queue, optionally aborting it
176
209
  close(abort) {
177
- var _classPrivateFieldGet4;
210
+ var _classPrivateFieldGet5;
178
211
  // when starting, state is updated afterwards
179
- if ((_classPrivateFieldGet4 = _classPrivateFieldGet(_start, this)) !== null && _classPrivateFieldGet4 !== void 0 && _classPrivateFieldGet4.pending) _classPrivateFieldGet(_start, this).readyState = 3;
212
+ if ((_classPrivateFieldGet5 = _classPrivateFieldGet(this, _start)) !== null && _classPrivateFieldGet5 !== void 0 && _classPrivateFieldGet5.pending) _classPrivateFieldGet(this, _start).readyState = 3;
180
213
  if (this.readyState < 3) this.readyState = 3;
181
214
  if (abort) this.clear();
182
215
  return this;
@@ -184,9 +217,9 @@ export class Queue {
184
217
 
185
218
  // clear and abort any queued tasks
186
219
  clear() {
187
- let tasks = [..._classPrivateFieldGet(_queued, this)];
188
- this.log.debug(`Clearing ${this.name} queue, queued state: ${_classPrivateFieldGet(_queued, this).size}, pending state: ${_classPrivateFieldGet(_pending, this).size}`);
189
- _classPrivateFieldGet(_queued, this).clear();
220
+ let tasks = [..._classPrivateFieldGet(this, _queued)];
221
+ this.log.debug(`Clearing ${this.name} queue, queued state: ${_classPrivateFieldGet(this, _queued).size}, pending state: ${_classPrivateFieldGet(this, _pending).size}`);
222
+ _classPrivateFieldGet(this, _queued).clear();
190
223
  for (let task of tasks) {
191
224
  task.ctrl.abort();
192
225
  task.reject(task.ctrl.signal.reason);
@@ -195,10 +228,10 @@ export class Queue {
195
228
 
196
229
  // process a single item task when started
197
230
  process(item) {
198
- var _classPrivateFieldGet5;
199
- let task = _assertClassBrand(_Queue_brand, this, _find).call(this, item);
200
- if (task && !_classPrivateFieldGet(_start, this)) this.start();
201
- (_classPrivateFieldGet5 = _classPrivateFieldGet(_start, this)) === null || _classPrivateFieldGet5 === void 0 ? void 0 : _classPrivateFieldGet5.promise.then(() => _assertClassBrand(_Queue_brand, this, _process).call(this, task));
231
+ var _classPrivateFieldGet6;
232
+ let task = _classPrivateMethodGet(this, _find, _find2).call(this, item);
233
+ if (task && !_classPrivateFieldGet(this, _start)) this.start();
234
+ (_classPrivateFieldGet6 = _classPrivateFieldGet(this, _start)) === null || _classPrivateFieldGet6 === void 0 ? void 0 : _classPrivateFieldGet6.promise.then(() => _classPrivateMethodGet(this, _process, _process2).call(this, task));
202
235
  return task === null || task === void 0 ? void 0 : task.deferred;
203
236
  }
204
237
 
@@ -208,10 +241,10 @@ export class Queue {
208
241
  // callback every 10ms during checks with the current number of pending tasks
209
242
  idle(callback) {
210
243
  return yieldFor(() => {
211
- var _classPrivateFieldGet6;
212
- callback === null || callback === void 0 ? void 0 : callback(_classPrivateFieldGet(_pending, this).size);
213
- let starting = ((_classPrivateFieldGet6 = _classPrivateFieldGet(_start, this)) === null || _classPrivateFieldGet6 === void 0 ? void 0 : _classPrivateFieldGet6.pending) === true;
214
- return !starting && !_classPrivateFieldGet(_pending, this).size;
244
+ var _classPrivateFieldGet7;
245
+ callback === null || callback === void 0 ? void 0 : callback(_classPrivateFieldGet(this, _pending).size);
246
+ let starting = ((_classPrivateFieldGet7 = _classPrivateFieldGet(this, _start)) === null || _classPrivateFieldGet7 === void 0 ? void 0 : _classPrivateFieldGet7.pending) === true;
247
+ return !starting && !_classPrivateFieldGet(this, _pending).size;
215
248
  }, {
216
249
  idle: 10
217
250
  });
@@ -220,57 +253,57 @@ export class Queue {
220
253
  // process items up to the latest queued item, starting the queue if necessary;
221
254
  // returns a generator that yields until the flushed item has finished processing
222
255
  flush(callback) {
223
- this.log.debug(`Flushing ${this.name} queue, queued state: ${_classPrivateFieldGet(_queued, this).size}, pending state: ${_classPrivateFieldGet(_pending, this).size}`);
256
+ this.log.debug(`Flushing ${this.name} queue, queued state: ${_classPrivateFieldGet(this, _queued).size}, pending state: ${_classPrivateFieldGet(this, _pending).size}`);
224
257
  let interrupt =
225
258
  // check for existing interrupts
226
- [..._classPrivateFieldGet(_pending, this)].find(t => t.stop) ?? [..._classPrivateFieldGet(_queued, this)].find(t => t.stop);
259
+ [..._classPrivateFieldGet(this, _pending)].find(t => t.stop) ?? [..._classPrivateFieldGet(this, _queued)].find(t => t.stop);
227
260
 
228
261
  // get the latest queued or pending task to track
229
- let flush = [..._classPrivateFieldGet(_queued, this)].pop() ?? [..._classPrivateFieldGet(_pending, this)].pop();
262
+ let flush = [..._classPrivateFieldGet(this, _queued)].pop() ?? [..._classPrivateFieldGet(this, _pending)].pop();
230
263
  // determine if the queue should be stopped after flushing
231
264
  if (flush) flush.stop = (interrupt === null || interrupt === void 0 ? void 0 : interrupt.stop) ?? this.readyState < 2;
232
265
  // remove the old interrupt to avoid stopping early
233
266
  if (interrupt) delete interrupt.stop;
234
267
  // start the queue if not started
235
- if (!_classPrivateFieldGet(_start, this)) this.start();
268
+ if (!_classPrivateFieldGet(this, _start)) this.start();
236
269
  // run the queue if stopped
237
270
  if (flush !== null && flush !== void 0 && flush.stop) this.run();
238
271
 
239
272
  // will yield with the callback until done flushing
240
- return _assertClassBrand(_Queue_brand, this, _until).call(this, flush, callback);
273
+ return _classPrivateMethodGet(this, _until, _until2).call(this, flush, callback);
241
274
  }
242
275
 
243
276
  // Repeatedly yields, calling the callback with the position of the task within the queue
244
277
  }
245
- function _dequeue() {
246
- if (!_classPrivateFieldGet(_queued, this).size || this.readyState < 2) return;
247
- if (_classPrivateFieldGet(_pending, this).size >= this.concurrency) return;
248
- let [task] = _classPrivateFieldGet(_queued, this);
249
- return _assertClassBrand(_Queue_brand, this, _process).call(this, task);
278
+ function _dequeue2() {
279
+ if (!_classPrivateFieldGet(this, _queued).size || this.readyState < 2) return;
280
+ if (_classPrivateFieldGet(this, _pending).size >= this.concurrency) return;
281
+ let [task] = _classPrivateFieldGet(this, _queued);
282
+ return _classPrivateMethodGet(this, _process, _process2).call(this, task);
250
283
  }
251
- function _find(subject) {
252
- let find = _classPrivateFieldGet(_handlers, this).find
284
+ function _find2(subject) {
285
+ let find = _classPrivateFieldGet(this, _handlers).find
253
286
  // use any configured find handler to match items
254
287
  ? ({
255
288
  item
256
- }) => _classPrivateFieldGet(_handlers, this).find(subject, item) : ({
289
+ }) => _classPrivateFieldGet(this, _handlers).find(subject, item) : ({
257
290
  item
258
291
  }) => subject === item;
259
292
  return (
260
293
  // look at queued then pending items
261
- [..._classPrivateFieldGet(_queued, this)].find(find) ?? [..._classPrivateFieldGet(_pending, this)].find(find)
294
+ [..._classPrivateFieldGet(this, _queued)].find(find) ?? [..._classPrivateFieldGet(this, _pending)].find(find)
262
295
  );
263
296
  }
264
- function _process(task) {
297
+ function _process2(task) {
265
298
  var _task$ctrl;
266
299
  if (!task || task.promise) return task;
267
- let queued = _classPrivateFieldGet(_queued, this).has(task);
300
+ let queued = _classPrivateFieldGet(this, _queued).has(task);
268
301
  // remove queued tasks from the queue
269
- if (queued) _classPrivateFieldGet(_queued, this).delete(task);
302
+ if (queued) _classPrivateFieldGet(this, _queued).delete(task);
270
303
  // clear queued tasks when ending
271
- if (task === _classPrivateFieldGet(_end, this)) this.clear();
304
+ if (task === _classPrivateFieldGet(this, _end)) this.clear();
272
305
  // add queued tasks to pending queue
273
- if (queued) _classPrivateFieldGet(_pending, this).add(task);
306
+ if (queued) _classPrivateFieldGet(this, _pending).add(task);
274
307
  // stop the queue when necessary
275
308
  if (task.stop) this.stop();
276
309
  // mark task as pending
@@ -279,11 +312,11 @@ function _process(task) {
279
312
  // handle the task using a generator promise
280
313
  task.promise = generatePromise(task.handler, (_task$ctrl = task.ctrl) === null || _task$ctrl === void 0 ? void 0 : _task$ctrl.signal, (err, val) => {
281
314
  // clean up pending tasks that have not been aborted
282
- if (queued && !task.ctrl.signal.aborted) _classPrivateFieldGet(_pending, this).delete(task);
315
+ if (queued && !task.ctrl.signal.aborted) _classPrivateFieldGet(this, _pending).delete(task);
283
316
  // update queue state when necessary
284
317
  if (task.readyState != null) this.readyState = task.readyState;
285
318
  // clean up internal tasks after ending
286
- if (!this.readyState) _classPrivateFieldSet(_start, this, _classPrivateFieldSet(_end, this, null));
319
+ if (!this.readyState) _classPrivateFieldSet(this, _start, _classPrivateFieldSet(this, _end, null));
287
320
  // resolve or reject the deferred task promise
288
321
  task[err ? 'reject' : 'resolve'](err ?? val);
289
322
  // keep dequeuing when running
@@ -293,15 +326,15 @@ function _process(task) {
293
326
  });
294
327
  return task;
295
328
  }
296
- async function* _until(task, callback) {
329
+ async function* _until2(task, callback) {
297
330
  try {
298
331
  yield* yieldFor(() => {
299
- var _classPrivateFieldGet7;
300
- if ((_classPrivateFieldGet7 = _classPrivateFieldGet(_start, this)) !== null && _classPrivateFieldGet7 !== void 0 && _classPrivateFieldGet7.pending) return false;
332
+ var _classPrivateFieldGet8;
333
+ if ((_classPrivateFieldGet8 = _classPrivateFieldGet(this, _start)) !== null && _classPrivateFieldGet8 !== void 0 && _classPrivateFieldGet8.pending) return false;
301
334
  let queued,
302
- pending = _classPrivateFieldGet(_pending, this).size;
335
+ pending = _classPrivateFieldGet(this, _pending).size;
303
336
  // calculate the position within queued when not pending
304
- if (task && task.pending == null) queued = positionOf(_classPrivateFieldGet(_queued, this), task);
337
+ if (task && task.pending == null) queued = positionOf(_classPrivateFieldGet(this, _queued), task);
305
338
  // call the callback and return true when not queued or pending
306
339
  let position = (queued ?? 0) + pending;
307
340
  callback === null || callback === void 0 ? void 0 : callback(position);
package/dist/server.js CHANGED
@@ -1,9 +1,13 @@
1
1
  function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
2
2
  function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
3
3
  function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
4
+ function _classPrivateFieldGet2(e, t) { var r = _classPrivateFieldGet(t, e); return _classApplyDescriptorGet(e, r); }
5
+ function _classApplyDescriptorGet(e, t) { return t.get ? t.get.call(e) : t.value; }
6
+ function _classPrivateMethodGet(s, a, r) { return _assertClassBrand(a, s), r; }
7
+ function _classPrivateFieldSet(e, t, r) { var s = _classPrivateFieldGet(t, e); return _classApplyDescriptorSet(e, s, r), r; }
4
8
  function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
5
- function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
6
9
  function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
10
+ function _classApplyDescriptorSet(e, t, l) { if (t.set) t.set.call(e, l);else { if (!t.writable) throw new TypeError("attempted to set read only private field"); t.value = l; } }
7
11
  import fs from 'fs';
8
12
  import path from 'path';
9
13
  import http from 'http';
@@ -99,7 +103,8 @@ export class ServerError extends Error {
99
103
  var _sockets = /*#__PURE__*/new WeakMap();
100
104
  var _defaultPort = /*#__PURE__*/new WeakMap();
101
105
  var _routes = /*#__PURE__*/new WeakMap();
102
- var _Server_brand = /*#__PURE__*/new WeakSet();
106
+ var _route = /*#__PURE__*/new WeakSet();
107
+ var _handleRequest = /*#__PURE__*/new WeakSet();
103
108
  export class Server extends http.Server {
104
109
  constructor({
105
110
  port
@@ -108,40 +113,48 @@ export class Server extends http.Server {
108
113
  IncomingMessage,
109
114
  ServerResponse
110
115
  });
111
- // adds a route in the correct priority order
112
- _classPrivateMethodInitSpec(this, _Server_brand);
113
- _classPrivateFieldInitSpec(this, _sockets, new Set());
114
- _classPrivateFieldInitSpec(this, _defaultPort, void 0);
115
- // initial routes include cors and 404 handling
116
- _classPrivateFieldInitSpec(this, _routes, [{
117
- priority: -1,
118
- handle: (req, res, next) => {
119
- res.setHeader('Access-Control-Allow-Origin', '*');
120
- if (req.method === 'OPTIONS') {
121
- let allowHeaders = req.headers['access-control-request-headers'] || '*';
122
- let allowMethods = [...new Set(_classPrivateFieldGet(_routes, this).flatMap(route => (!route.match || route.match(req.url.pathname)) && route.methods || []))].join(', ');
123
- res.setHeader('Access-Control-Allow-Headers', allowHeaders);
124
- res.setHeader('Access-Control-Allow-Methods', allowMethods);
125
- res.writeHead(204).end();
126
- } else {
127
- res.setHeader('Access-Control-Expose-Headers', '*');
128
- return next();
116
+ _classPrivateMethodInitSpec(this, _handleRequest);
117
+ _classPrivateMethodInitSpec(this, _route);
118
+ _classPrivateFieldInitSpec(this, _sockets, {
119
+ writable: true,
120
+ value: new Set()
121
+ });
122
+ _classPrivateFieldInitSpec(this, _defaultPort, {
123
+ writable: true,
124
+ value: void 0
125
+ });
126
+ _classPrivateFieldInitSpec(this, _routes, {
127
+ writable: true,
128
+ value: [{
129
+ priority: -1,
130
+ handle: (req, res, next) => {
131
+ res.setHeader('Access-Control-Allow-Origin', '*');
132
+ if (req.method === 'OPTIONS') {
133
+ let allowHeaders = req.headers['access-control-request-headers'] || '*';
134
+ let allowMethods = [...new Set(_classPrivateFieldGet2(this, _routes).flatMap(route => (!route.match || route.match(req.url.pathname)) && route.methods || []))].join(', ');
135
+ res.setHeader('Access-Control-Allow-Headers', allowHeaders);
136
+ res.setHeader('Access-Control-Allow-Methods', allowMethods);
137
+ res.writeHead(204).end();
138
+ } else {
139
+ res.setHeader('Access-Control-Expose-Headers', '*');
140
+ return next();
141
+ }
129
142
  }
130
- }
131
- }, {
132
- priority: 3,
133
- handle: req => ServerError.throw(404)
134
- }]);
135
- _classPrivateFieldSet(_defaultPort, this, port);
143
+ }, {
144
+ priority: 3,
145
+ handle: req => ServerError.throw(404)
146
+ }]
147
+ });
148
+ _classPrivateFieldSet(this, _defaultPort, port);
136
149
 
137
150
  // handle requests on end
138
151
  this.on('request', (req, res) => {
139
- req.on('end', () => _assertClassBrand(_Server_brand, this, _handleRequest).call(this, req, res));
152
+ req.on('end', () => _classPrivateMethodGet(this, _handleRequest, _handleRequest2).call(this, req, res));
140
153
  });
141
154
  // track open connections to terminate when the server closes
142
155
  this.on('connection', socket => {
143
- let handleClose = () => _classPrivateFieldGet(_sockets, this).delete(socket);
144
- _classPrivateFieldGet(_sockets, this).add(socket.on('close', handleClose));
156
+ let handleClose = () => _classPrivateFieldGet2(this, _sockets).delete(socket);
157
+ _classPrivateFieldGet2(this, _sockets).add(socket.on('close', handleClose));
145
158
  });
146
159
  }
147
160
 
@@ -153,7 +166,7 @@ export class Server extends http.Server {
153
166
  // return the listening port or any default port
154
167
  get port() {
155
168
  var _super$address;
156
- return ((_super$address = super.address()) === null || _super$address === void 0 ? void 0 : _super$address.port) ?? _classPrivateFieldGet(_defaultPort, this);
169
+ return ((_super$address = super.address()) === null || _super$address === void 0 ? void 0 : _super$address.port) ?? _classPrivateFieldGet2(this, _defaultPort);
157
170
  }
158
171
 
159
172
  // return a string representation of the server address
@@ -168,7 +181,7 @@ export class Server extends http.Server {
168
181
  }
169
182
 
170
183
  // return a promise that resolves when the server is listening
171
- listen(port = _classPrivateFieldGet(_defaultPort, this)) {
184
+ listen(port = _classPrivateFieldGet2(this, _defaultPort)) {
172
185
  return new Promise((resolve, reject) => {
173
186
  let handle = err => off() && err ? reject(err) : resolve(this);
174
187
  let off = () => this.off('error', handle).off('listening', handle);
@@ -179,16 +192,21 @@ export class Server extends http.Server {
179
192
  // return a promise that resolves when the server closes
180
193
  close() {
181
194
  return new Promise(resolve => {
182
- _classPrivateFieldGet(_sockets, this).forEach(socket => socket.destroy());
195
+ _classPrivateFieldGet2(this, _sockets).forEach(socket => socket.destroy());
183
196
  super.close(resolve);
184
197
  });
185
198
  }
199
+
200
+ // initial routes include cors and 404 handling
201
+
202
+ // adds a route in the correct priority order
203
+
186
204
  // set request routing and handling for pathnames and methods
187
205
  route(method, pathname, handle) {
188
206
  if (arguments.length === 1) [handle, method] = [method];
189
207
  if (arguments.length === 2) [handle, pathname] = [pathname];
190
208
  if (arguments.length === 2 && !Array.isArray(method) && method[0] === '/') [pathname, method] = [method];
191
- return _assertClassBrand(_Server_brand, this, _route).call(this, {
209
+ return _classPrivateMethodGet(this, _route, _route2).call(this, {
192
210
  priority: !pathname ? 0 : !method ? 1 : 2,
193
211
  methods: method && [].concat(method).map(m => m.toUpperCase()),
194
212
  match: pathname && pathToMatch(pathname),
@@ -215,7 +233,7 @@ export class Server extends http.Server {
215
233
  throw new ServerError(400);
216
234
  }
217
235
  });
218
- return _assertClassBrand(_Server_brand, this, _route).call(this, {
236
+ return _classPrivateMethodGet(this, _route, _route2).call(this, {
219
237
  priority: 2,
220
238
  methods: ['GET'],
221
239
  match: pathname => mountPattern.test(pathname),
@@ -239,12 +257,12 @@ export class Server extends http.Server {
239
257
  }
240
258
 
241
259
  // create a url rewriter from provided rewrite rules
242
- function _route(route) {
243
- let i = _classPrivateFieldGet(_routes, this).findIndex(r => r.priority >= route.priority);
244
- _classPrivateFieldGet(_routes, this).splice(i, 0, route);
260
+ function _route2(route) {
261
+ let i = _classPrivateFieldGet2(this, _routes).findIndex(r => r.priority >= route.priority);
262
+ _classPrivateFieldGet2(this, _routes).splice(i, 0, route);
245
263
  return this;
246
264
  }
247
- async function _handleRequest(req, res) {
265
+ async function _handleRequest2(req, res) {
248
266
  // support node < 15.7.0
249
267
  res.req ?? (res.req = req);
250
268
  try {
@@ -260,7 +278,7 @@ async function _handleRequest(req, res) {
260
278
  result && (result = !match || match(req.url.pathname));
261
279
  if (result) req.params = result.params;
262
280
  return result ? handle(req, res, next) : next();
263
- }(_classPrivateFieldGet(_routes, this));
281
+ }(_classPrivateFieldGet2(this, _routes));
264
282
  } catch (error) {
265
283
  var _req$headers$accept, _req$headers$content;
266
284
  let {
package/dist/snapshot.js CHANGED
@@ -3,7 +3,7 @@ import PercyConfig from '@percy/config';
3
3
  import micromatch from 'micromatch';
4
4
  import { configSchema } from './config.js';
5
5
  import Queue from './queue.js';
6
- import { request, hostnameMatches, yieldTo, snapshotLogName } from './utils.js';
6
+ import { request, hostnameMatches, yieldTo, snapshotLogName, decodeAndEncodeURLWithLogging } from './utils.js';
7
7
  import { JobData } from './wait-for-job.js';
8
8
 
9
9
  // Throw a better error message for missing or invalid urls
@@ -17,6 +17,23 @@ function validURL(url, base) {
17
17
  throw new Error(`Invalid snapshot URL: ${e.input}`);
18
18
  }
19
19
  }
20
+ function validateAndFixSnapshotUrl(snapshot) {
21
+ let log = logger('core:snapshot');
22
+ // encoding snapshot url, if contians invalid URI characters/syntax
23
+ let modifiedURL = decodeAndEncodeURLWithLogging(snapshot.url, log, {
24
+ meta: {
25
+ snapshot: {
26
+ name: snapshot.name || snapshot.url
27
+ }
28
+ },
29
+ shouldLogWarning: true,
30
+ warningMessage: `Invalid URL detected for url: ${snapshot.url} - the snapshot may fail on Percy. Please confirm that your website URL is valid.`
31
+ });
32
+ if (modifiedURL !== snapshot.url) {
33
+ log.debug(`Snapshot URL modified to: ${modifiedURL}`);
34
+ snapshot.url = modifiedURL;
35
+ }
36
+ }
20
37
 
21
38
  // used to deserialize regular expression strings
22
39
  const RE_REGEXP = /^\/(.+)\/(\w+)?$/;
@@ -86,9 +103,9 @@ function mapSnapshotOptions(snapshots, context) {
86
103
  if (typeof snapshot === 'string') snapshot = {
87
104
  url: snapshot
88
105
  };
89
-
90
- // normalize the snapshot url and use it for the default name
106
+ validateAndFixSnapshotUrl(snapshot);
91
107
  let url = validURL(snapshot.url, context === null || context === void 0 ? void 0 : context.baseUrl);
108
+ // normalize the snapshot url and use it for the default name
92
109
  (_snapshot = snapshot).name || (_snapshot.name = `${url.pathname}${url.search}${url.hash}`);
93
110
  snapshot.url = url.href;
94
111
 
package/dist/timing.js CHANGED
@@ -15,7 +15,7 @@ export default class TimeIt {
15
15
  return await callback();
16
16
  } finally {
17
17
  const duration = Date.now() - startTime;
18
- this.log.info(`${name} - ${identifier} - ${duration / 1000}s`);
18
+ this.log.debug(`${name} - ${identifier} - ${duration / 1000}s`);
19
19
  }
20
20
  }
21
21
  }
package/dist/utils.js CHANGED
@@ -5,6 +5,8 @@ import YAML from 'yaml';
5
5
  import path from 'path';
6
6
  import url from 'url';
7
7
  import { readFileSync } from 'fs';
8
+ import logger from '@percy/logger';
9
+ import DetectProxy from '@percy/client/detect-proxy';
8
10
  export { request, getPackageJSON, hostnameMatches } from '@percy/client/utils';
9
11
  export { Server, createServer } from './server.js';
10
12
 
@@ -377,6 +379,35 @@ export function redactSecrets(data) {
377
379
  export function base64encode(content) {
378
380
  return Buffer.from(content).toString('base64');
379
381
  }
382
+
383
+ // This function replaces invalid character that are not the
384
+ // part of valid URI syntax with there correct encoded value.
385
+ // Also, if a character is a part of valid URI syntax, those characters
386
+ // are not encoded
387
+ // Eg: [abc] -> gets encoded to %5Babc%5D
388
+ // ab c -> ab%20c
389
+ export function decodeAndEncodeURLWithLogging(url, logger, options = {}) {
390
+ // In case the url is partially encoded, then directly using encodeURI()
391
+ // will encode those characters again. Therefore decodeURI once helps is decoding
392
+ // partially encoded URL and then after encoding it again, full URL get encoded
393
+ // correctly.
394
+ const {
395
+ meta,
396
+ shouldLogWarning,
397
+ warningMessage
398
+ } = options;
399
+ try {
400
+ let decodedURL = decodeURI(url); // This can throw error, so handle it will trycatch
401
+ let encodedURL = encodeURI(decodedURL);
402
+ return encodedURL;
403
+ } catch (error) {
404
+ logger.debug(error, meta);
405
+ if (error.name === 'URIError' && shouldLogWarning) {
406
+ logger.warn(warningMessage);
407
+ }
408
+ return url;
409
+ }
410
+ }
380
411
  export function snapshotLogName(name, meta) {
381
412
  var _meta$snapshot;
382
413
  if (meta !== null && meta !== void 0 && (_meta$snapshot = meta.snapshot) !== null && _meta$snapshot !== void 0 && _meta$snapshot.testCase) {
@@ -384,6 +415,34 @@ export function snapshotLogName(name, meta) {
384
415
  }
385
416
  return name;
386
417
  }
418
+ export async function detectSystemProxyAndLog(applyProxy) {
419
+ // if proxy is already set no need to check again
420
+ if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) return;
421
+ let proxyPresent = false;
422
+ const log = logger('core:utils');
423
+ // Checking proxy shouldn't cause failure
424
+ try {
425
+ const detectProxy = new DetectProxy();
426
+ const proxies = await detectProxy.getSystemProxy();
427
+ proxyPresent = proxies.length !== 0;
428
+ if (proxyPresent) {
429
+ if (applyProxy) {
430
+ proxies.forEach(proxy => {
431
+ if (proxy.type === 'HTTPS') {
432
+ process.env.HTTPS_PROXY = 'https://' + proxy.host + ':' + proxy.port;
433
+ } else if (proxy.type === 'HTTP') {
434
+ process.env.HTTP_PROXY = 'http://' + proxy.host + ':' + proxy.port;
435
+ }
436
+ });
437
+ } else {
438
+ log.warn('We have detected a system level proxy in your system. use HTTP_PROXY or HTTPS_PROXY env vars or To auto apply proxy set useSystemProxy: true under percy in config file');
439
+ }
440
+ }
441
+ } catch (e) {
442
+ log.debug(`Failed to detect system proxy ${e}`);
443
+ }
444
+ return proxyPresent;
445
+ }
387
446
 
388
447
  // DefaultMap, which returns a default value for an uninitialized key
389
448
  // Similar to defaultDict in python
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.29.1-alpha.0",
3
+ "version": "1.29.1-beta.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "publishConfig": {
11
11
  "access": "public",
12
- "tag": "alpha"
12
+ "tag": "beta"
13
13
  },
14
14
  "engines": {
15
15
  "node": ">=14"
@@ -43,11 +43,11 @@
43
43
  "test:types": "tsd"
44
44
  },
45
45
  "dependencies": {
46
- "@percy/client": "1.29.1-alpha.0",
47
- "@percy/config": "1.29.1-alpha.0",
48
- "@percy/dom": "1.29.1-alpha.0",
49
- "@percy/logger": "1.29.1-alpha.0",
50
- "@percy/webdriver-utils": "1.29.1-alpha.0",
46
+ "@percy/client": "1.29.1-beta.0",
47
+ "@percy/config": "1.29.1-beta.0",
48
+ "@percy/dom": "1.29.1-beta.0",
49
+ "@percy/logger": "1.29.1-beta.0",
50
+ "@percy/webdriver-utils": "1.29.1-beta.0",
51
51
  "content-disposition": "^0.5.4",
52
52
  "cross-spawn": "^7.0.3",
53
53
  "extract-zip": "^2.0.1",
@@ -60,5 +60,5 @@
60
60
  "ws": "^8.17.1",
61
61
  "yaml": "^2.4.1"
62
62
  },
63
- "gitHead": "5044adb5caa7507fdec629eda8f33d6ddde07997"
63
+ "gitHead": "d325b7bbe56764dbde494477d1f4f3bfdc562d6e"
64
64
  }