@percy/core 1.5.1 → 1.6.2

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/api.js CHANGED
@@ -8,7 +8,7 @@ export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom'); /
8
8
 
9
9
  export function createPercyServer(percy, port) {
10
10
  let pkg = getPackageJSON(import.meta.url);
11
- return Server.createServer({
11
+ let server = Server.createServer({
12
12
  port
13
13
  }) // facilitate logger websocket connections
14
14
  .websocket('/(logger)?', ws => {
@@ -29,13 +29,31 @@ export function createPercyServer(percy, port) {
29
29
  }));
30
30
  }) // general middleware
31
31
  .route((req, res, next) => {
32
+ var _percy$testing, _percy$testing3, _percy$testing3$api, _percy$testing4, _percy$testing4$api;
33
+
32
34
  // treat all request bodies as json
33
35
  if (req.body) try {
34
36
  req.body = JSON.parse(req.body);
35
37
  } catch {} // add version header
36
38
 
37
- res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version');
38
- res.setHeader('X-Percy-Core-Version', pkg.version); // return json errors
39
+ res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version'); // skip or change api version header in testing mode
40
+
41
+ if (((_percy$testing = percy.testing) === null || _percy$testing === void 0 ? void 0 : _percy$testing.version) !== false) {
42
+ var _percy$testing2;
43
+
44
+ res.setHeader('X-Percy-Core-Version', ((_percy$testing2 = percy.testing) === null || _percy$testing2 === void 0 ? void 0 : _percy$testing2.version) ?? pkg.version);
45
+ } // support sabotaging requests in testing mode
46
+
47
+
48
+ if (((_percy$testing3 = percy.testing) === null || _percy$testing3 === void 0 ? void 0 : (_percy$testing3$api = _percy$testing3.api) === null || _percy$testing3$api === void 0 ? void 0 : _percy$testing3$api[req.url.pathname]) === 'error') {
49
+ return res.json(500, {
50
+ success: false,
51
+ error: 'Error: testing'
52
+ });
53
+ } else if (((_percy$testing4 = percy.testing) === null || _percy$testing4 === void 0 ? void 0 : (_percy$testing4$api = _percy$testing4.api) === null || _percy$testing4$api === void 0 ? void 0 : _percy$testing4$api[req.url.pathname]) === 'disconnect') {
54
+ return req.connection.destroy();
55
+ } // return json errors
56
+
39
57
 
40
58
  return next().catch(e => res.json(e.status ?? 500, {
41
59
  build: percy.build,
@@ -77,6 +95,43 @@ export function createPercyServer(percy, port) {
77
95
  return res.json(200, {
78
96
  success: true
79
97
  });
98
+ }); // add test endpoints only in testing mode
99
+
100
+ return !percy.testing ? server : server // manipulates testing mode configuration to trigger specific scenarios
101
+ .route('/test/api/:cmd', ({
102
+ body,
103
+ params: {
104
+ cmd
105
+ }
106
+ }, res) => {
107
+ body = Buffer.isBuffer(body) ? body.toString() : body;
108
+
109
+ if (cmd === 'reset') {
110
+ // the reset command will reset testing mode and clear any logs
111
+ percy.testing = {};
112
+ logger.instance.messages.clear();
113
+ } else if (cmd === 'version') {
114
+ // the version command will update the api version header for testing
115
+ percy.testing.version = body;
116
+ } else if (cmd === 'error' || cmd === 'disconnect') {
117
+ // the error or disconnect commands will cause specific endpoints to fail
118
+ percy.testing.api = { ...percy.testing.api,
119
+ [body]: cmd
120
+ };
121
+ } else {
122
+ // 404 for unknown commands
123
+ return res.send(404);
124
+ }
125
+
126
+ return res.json(200, {
127
+ testing: percy.testing
128
+ });
129
+ }) // returns an array of raw logs from the logger
130
+ .route('get', '/test/logs', (req, res) => res.json(200, {
131
+ logs: Array.from(logger.instance.messages)
132
+ })) // serves a very basic html page for testing snapshots
133
+ .route('get', '/test/snapshot', (req, res) => {
134
+ return res.send(200, 'text/html', '<p>Snapshot Me!</p>');
80
135
  });
81
136
  } // Create a static server instance with an automatic sitemap
82
137
 
package/dist/discovery.js CHANGED
@@ -64,6 +64,7 @@ export function createRequestFinishedHandler(network, {
64
64
  let resource = getResource(url); // process and cache the response and resource
65
65
 
66
66
  if (!((_resource = resource) !== null && _resource !== void 0 && _resource.root) && (!resource || disableCache)) {
67
+ let headers = request.headers;
67
68
  let response = request.response;
68
69
  let capture = response && hostnameMatches(allowedHostnames, url);
69
70
  let body = capture && (await response.buffer());
@@ -85,14 +86,21 @@ export function createRequestFinishedHandler(network, {
85
86
  } // Try to get the proper mimetype if the server or asset discovery browser is sending `text/plain`
86
87
 
87
88
 
88
- let mimeType = response.mimeType === 'text/plain' && mime.lookup(response.url) || response.mimeType;
89
+ let mimeType = response.mimeType === 'text/plain' && mime.lookup(response.url) || response.mimeType; // font responses from the browser may not be properly encoded, so request them directly
89
90
 
90
91
  if (mimeType !== null && mimeType !== void 0 && mimeType.includes('font')) {
91
- // font responses from the browser may not be properly encoded, so request them directly
92
+ var _network$authorizatio;
93
+
92
94
  log.debug('- Requesting asset directly');
95
+
96
+ if (!headers.Authorization && (_network$authorizatio = network.authorization) !== null && _network$authorizatio !== void 0 && _network$authorizatio.username) {
97
+ let token = Buffer.from([network.authorization.username, network.authorization.password || ''].join(':')).toString('base64');
98
+ headers.Authorization = `Basic ${token}`;
99
+ }
100
+
93
101
  body = await makeRequest(response.url, {
94
102
  buffer: true,
95
- headers: request.headers
103
+ headers
96
104
  });
97
105
  }
98
106
 
package/dist/percy.js CHANGED
@@ -25,12 +25,16 @@ export class Percy {
25
25
  constructor({
26
26
  // initial log level
27
27
  loglevel,
28
- // do not eagerly upload snapshots
28
+ // process uploads before the next snapshot
29
+ delayUploads,
30
+ // process uploads after all snapshots
29
31
  deferUploads,
30
32
  // run without uploading anything
31
33
  skipUploads,
32
34
  // implies `skipUploads` and also skips asset discovery
33
35
  dryRun,
36
+ // implies `dryRun`, silent logs, and adds extra api endpoints
37
+ testing,
34
38
  // configuration filepath
35
39
  config,
36
40
  // provided to @percy/client
@@ -44,10 +48,13 @@ export class Percy {
44
48
  // options which will become accessible via the `.config` property
45
49
  ...options
46
50
  } = {}) {
51
+ if (testing) loglevel = 'silent';
47
52
  if (loglevel) this.loglevel(loglevel);
48
- this.dryRun = !!dryRun;
53
+ this.testing = testing ? {} : null;
54
+ this.dryRun = !!testing || !!dryRun;
49
55
  this.skipUploads = this.dryRun || !!skipUploads;
50
- this.deferUploads = this.skipUploads || !!deferUploads;
56
+ this.delayUploads = this.skipUploads || !!delayUploads;
57
+ this.deferUploads = this.delayUploads || !!deferUploads;
51
58
  if (this.deferUploads) this.#uploads.stop();
52
59
  this.config = PercyConfig.load({
53
60
  overrides: options,
@@ -62,6 +69,10 @@ export class Percy {
62
69
  this.#snapshots.concurrency = concurrency;
63
70
  }
64
71
 
72
+ if (this.delayUploads) {
73
+ this.#uploads.concurrency = 1;
74
+ }
75
+
65
76
  this.client = new PercyClient({
66
77
  token,
67
78
  clientInfo,
@@ -162,7 +173,7 @@ export class Percy {
162
173
  };
163
174
  this.build.number = attributes['build-number'];
164
175
  this.build.url = attributes['web-url'];
165
- this.#uploads.run();
176
+ if (!this.delayUploads) this.#uploads.run();
166
177
  });
167
178
  }, 0); // handle deferred build errors
168
179
 
@@ -447,9 +458,14 @@ export class Percy {
447
458
 
448
459
  if ((_this$build4 = this.build) !== null && _this$build4 !== void 0 && _this$build4.error) {
449
460
  throw new Error(this.build.error);
450
- }
461
+ } // when not dry-running, process any existing delayed uploads
451
462
 
463
+
464
+ if (!this.dryRun && this.delayUploads) this.#uploads.run();
452
465
  return this.#uploads.push(`upload/${name}`, async () => {
466
+ // when delayed, stop the queue before other uploads are processed
467
+ if (this.delayUploads) this.#uploads.stop();
468
+
453
469
  try {
454
470
  /* istanbul ignore if: useful for other internal packages */
455
471
  if (typeof options === 'function') options = await options();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/core",
3
- "version": "1.5.1",
3
+ "version": "1.6.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,10 +39,10 @@
39
39
  "test:types": "tsd"
40
40
  },
41
41
  "dependencies": {
42
- "@percy/client": "1.5.1",
43
- "@percy/config": "1.5.1",
44
- "@percy/dom": "1.5.1",
45
- "@percy/logger": "1.5.1",
42
+ "@percy/client": "1.6.2",
43
+ "@percy/config": "1.6.2",
44
+ "@percy/dom": "1.6.2",
45
+ "@percy/logger": "1.6.2",
46
46
  "content-disposition": "^0.5.4",
47
47
  "cross-spawn": "^7.0.3",
48
48
  "extract-zip": "^2.0.1",
@@ -53,5 +53,5 @@
53
53
  "rimraf": "^3.0.2",
54
54
  "ws": "^8.0.0"
55
55
  },
56
- "gitHead": "c67c0d482d3da18e529f7bc2e1c219c04e59bbea"
56
+ "gitHead": "c033d52167cdd5873027d9de2676f3fc9f55ef2d"
57
57
  }
@@ -7,7 +7,7 @@ export async function request(url, method = 'GET', handle) {
7
7
  try {
8
8
  return await request(url, options, cb);
9
9
  } catch (error) {
10
- if (typeof handle !== 'boolean') throw error;
10
+ if (!error.response || typeof handle !== 'boolean') throw error;
11
11
  return handle ? [error.response.body, error.response] : error.response;
12
12
  }
13
13
  }