@percy/core 1.12.0 → 1.14.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/percy.js CHANGED
@@ -5,22 +5,23 @@ import Browser from './browser.js';
5
5
  import { createPercyServer, createStaticServer } from './api.js';
6
6
  import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js';
7
7
  import { discoverSnapshotResources, createDiscoveryQueue } from './discovery.js';
8
- import { generatePromise, yieldAll, yieldTo } from './utils.js'; // A Percy instance will create a new build when started, handle snapshot creation, asset discovery,
8
+ import { generatePromise, yieldAll, yieldTo } from './utils.js';
9
+
10
+ // A Percy instance will create a new build when started, handle snapshot creation, asset discovery,
9
11
  // and resource uploads, and will finalize the build when stopped. Snapshots are processed
10
12
  // concurrently and the build is not finalized until all snapshots have been handled.
11
-
12
13
  export class Percy {
13
14
  log = logger('core');
14
15
  readyState = null;
15
16
  #discovery = null;
16
- #snapshots = null; // Static shortcut to create and start an instance in one call
17
+ #snapshots = null;
17
18
 
19
+ // Static shortcut to create and start an instance in one call
18
20
  static async start(options) {
19
21
  let instance = new this(options);
20
22
  await instance.start();
21
23
  return instance;
22
24
  }
23
-
24
25
  constructor({
25
26
  // initial log level
26
27
  loglevel,
@@ -74,56 +75,56 @@ export class Percy {
74
75
  if (server) this.server = createPercyServer(this, port);
75
76
  this.browser = new Browser(this);
76
77
  this.#discovery = createDiscoveryQueue(this);
77
- this.#snapshots = createSnapshotsQueue(this); // generator methods are wrapped to autorun and return promises
78
+ this.#snapshots = createSnapshotsQueue(this);
78
79
 
80
+ // generator methods are wrapped to autorun and return promises
79
81
  for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload']) {
80
82
  // the original generator can be referenced with percy.yield.<method>
81
83
  let method = (this.yield || (this.yield = {}))[m] = this[m].bind(this);
82
-
83
84
  this[m] = (...args) => generatePromise(method(...args));
84
85
  }
85
- } // Shortcut for controlling the global logger's log level.
86
-
86
+ }
87
87
 
88
+ // Shortcut for controlling the global logger's log level.
88
89
  loglevel(level) {
89
90
  return logger.loglevel(level);
90
- } // Snapshot server API address
91
-
91
+ }
92
92
 
93
+ // Snapshot server API address
93
94
  address() {
94
95
  var _this$server;
95
-
96
96
  return (_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.address();
97
- } // Set client & environment info, and override loaded config options
98
-
97
+ }
99
98
 
99
+ // Set client & environment info, and override loaded config options
100
100
  set({
101
101
  clientInfo,
102
102
  environmentInfo,
103
103
  ...config
104
104
  }) {
105
105
  this.client.addClientInfo(clientInfo);
106
- this.client.addEnvironmentInfo(environmentInfo); // normalize config and do nothing if empty
106
+ this.client.addEnvironmentInfo(environmentInfo);
107
107
 
108
+ // normalize config and do nothing if empty
108
109
  config = PercyConfig.normalize(config, {
109
110
  schema: '/config'
110
111
  });
111
- if (!config) return this.config; // validate provided config options
112
+ if (!config) return this.config;
112
113
 
114
+ // validate provided config options
113
115
  let errors = PercyConfig.validate(config);
114
-
115
116
  if (errors) {
116
117
  this.log.warn('Invalid config:');
117
-
118
118
  for (let e of errors) this.log.warn(`- ${e.path}: ${e.message}`);
119
- } // merge and override existing config options
120
-
119
+ }
121
120
 
121
+ // merge and override existing config options
122
122
  this.config = PercyConfig.merge([this.config, config], (path, prev, next) => {
123
123
  // replace arrays instead of merging
124
124
  return Array.isArray(next) && [path, next];
125
- }); // adjust queue concurrency
125
+ });
126
126
 
127
+ // adjust queue concurrency
127
128
  let {
128
129
  concurrency
129
130
  } = this.config.discovery;
@@ -134,92 +135,95 @@ export class Percy {
134
135
  concurrency
135
136
  });
136
137
  return this.config;
137
- } // Starts a local API server, a browser process, and internal queues.
138
-
138
+ }
139
139
 
140
+ // Starts a local API server, a browser process, and internal queues.
140
141
  async *start() {
141
142
  // already starting or started
142
143
  if (this.readyState != null) return;
143
144
  this.readyState = 0;
144
-
145
145
  try {
146
146
  // start the snapshots queue immediately when not delayed or deferred
147
- if (!this.delayUploads && !this.deferUploads) yield this.#snapshots.start(); // do not start the discovery queue when not needed
148
-
149
- if (!this.skipDiscovery) yield this.#discovery.start(); // start a local API server for SDK communication
150
-
151
- if (this.server) yield this.server.listen(); // log and mark this instance as started
152
-
147
+ if (!this.delayUploads && !this.deferUploads) yield this.#snapshots.start();
148
+ // do not start the discovery queue when not needed
149
+ if (!this.skipDiscovery) yield this.#discovery.start();
150
+ // start a local API server for SDK communication
151
+ if (this.server) yield this.server.listen();
152
+ // log and mark this instance as started
153
153
  this.log.info('Percy has started!');
154
154
  this.readyState = 1;
155
155
  } catch (error) {
156
156
  var _this$server2;
157
-
158
157
  // on error, close any running server and end queues
159
158
  await ((_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.close());
160
159
  await this.#discovery.end();
161
- await this.#snapshots.end(); // mark this instance as closed unless aborting
160
+ await this.#snapshots.end();
162
161
 
163
- this.readyState = error.name !== 'AbortError' ? 3 : null; // throw an easier-to-understand error when the port is in use
162
+ // mark this instance as closed unless aborting
163
+ this.readyState = error.name !== 'AbortError' ? 3 : null;
164
164
 
165
+ // throw an easier-to-understand error when the port is in use
165
166
  if (error.code === 'EADDRINUSE') {
166
167
  throw new Error('Percy is already running or the port is in use');
167
168
  } else {
168
169
  throw error;
169
170
  }
170
171
  }
171
- } // Resolves once snapshot and upload queues are idle
172
-
172
+ }
173
173
 
174
+ // Resolves once snapshot and upload queues are idle
174
175
  async *idle() {
175
176
  yield* this.#discovery.idle();
176
177
  yield* this.#snapshots.idle();
177
- } // Wait for currently queued snapshots then run and wait for resulting uploads
178
-
178
+ }
179
179
 
180
+ // Wait for currently queued snapshots then run and wait for resulting uploads
180
181
  async *flush(options) {
181
182
  if (!this.readyState || this.readyState > 2) return;
182
183
  let callback = typeof options === 'function' ? options : null;
183
- options && (options = !callback ? [].concat(options) : null); // wait until the next event loop for synchronous snapshots
184
+ options && (options = !callback ? [].concat(options) : null);
184
185
 
185
- yield new Promise(r => setImmediate(r)); // flush and log progress for discovery before snapshots
186
+ // wait until the next event loop for synchronous snapshots
187
+ yield new Promise(r => setImmediate(r));
186
188
 
189
+ // flush and log progress for discovery before snapshots
187
190
  if (!this.skipDiscovery && this.#discovery.size) {
188
191
  if (options) yield* yieldAll(options.map(o => this.#discovery.process(o)));else yield* this.#discovery.flush(size => callback === null || callback === void 0 ? void 0 : callback('Processing', size));
189
- } // flush and log progress for snapshot uploads
190
-
192
+ }
191
193
 
194
+ // flush and log progress for snapshot uploads
192
195
  if (!this.skipUploads && this.#snapshots.size) {
193
196
  if (options) yield* yieldAll(options.map(o => this.#snapshots.process(o)));else yield* this.#snapshots.flush(size => callback === null || callback === void 0 ? void 0 : callback('Uploading', size));
194
197
  }
195
- } // Stops the local API server and closes the browser and internal queues once snapshots have
196
- // completed. Does nothing if not running. When `force` is true, any queued snapshots are cleared.
197
-
198
+ }
198
199
 
200
+ // Stops the local API server and closes the browser and internal queues once snapshots have
201
+ // completed. Does nothing if not running. When `force` is true, any queued snapshots are cleared.
199
202
  async *stop(force) {
200
203
  var _this$server3;
201
-
202
204
  // not started, but the browser was launched
203
205
  if (!this.readyState && this.browser.isConnected()) {
204
206
  await this.browser.close();
205
- } // not started or already stopped
206
-
207
+ }
207
208
 
208
- if (!this.readyState || this.readyState > 2) return; // close queues asap
209
+ // not started or already stopped
210
+ if (!this.readyState || this.readyState > 2) return;
209
211
 
212
+ // close queues asap
210
213
  if (force) {
211
214
  this.#discovery.close(true);
212
215
  this.#snapshots.close(true);
213
- } // already stopping
214
-
216
+ }
215
217
 
218
+ // already stopping
216
219
  if (this.readyState === 2) return;
217
- this.readyState = 2; // log when force stopping
220
+ this.readyState = 2;
218
221
 
219
- if (force) this.log.info('Stopping percy...'); // used to log snapshot count information
222
+ // log when force stopping
223
+ if (force) this.log.info('Stopping percy...');
220
224
 
225
+ // used to log snapshot count information
221
226
  let info = (state, size) => `${state} ` + `${size} snapshot${size !== 1 ? 's' : ''}`;
222
-
223
227
  try {
224
228
  // flush discovery and snapshot queues
225
229
  yield* this.yield.flush((state, size) => {
@@ -227,61 +231,60 @@ export class Percy {
227
231
  });
228
232
  } catch (error) {
229
233
  // reset ready state when aborted
230
-
231
234
  /* istanbul ignore else: all errors bubble */
232
235
  if (error.name === 'AbortError') this.readyState = 1;
233
236
  throw error;
234
- } // if dry-running, log the total number of snapshots
235
-
237
+ }
236
238
 
239
+ // if dry-running, log the total number of snapshots
237
240
  if (this.dryRun && this.#snapshots.size) {
238
241
  this.log.info(info('Found', this.#snapshots.size));
239
- } // close server and end queues
240
-
242
+ }
241
243
 
244
+ // close server and end queues
242
245
  await ((_this$server3 = this.server) === null || _this$server3 === void 0 ? void 0 : _this$server3.close());
243
246
  await this.#discovery.end();
244
- await this.#snapshots.end(); // mark instance as stopped
247
+ await this.#snapshots.end();
245
248
 
249
+ // mark instance as stopped
246
250
  this.readyState = 3;
247
- } // Takes one or more snapshots of a page while discovering resources to upload with the resulting
251
+ }
252
+
253
+ // Takes one or more snapshots of a page while discovering resources to upload with the resulting
248
254
  // snapshots. Once asset discovery has completed for the provided snapshots, the queued task will
249
255
  // resolve and an upload task will be queued separately.
250
-
251
-
252
256
  snapshot(options) {
253
257
  var _this$build;
254
-
255
258
  if (this.readyState !== 1) {
256
259
  throw new Error('Not running');
257
260
  } else if ((_this$build = this.build) !== null && _this$build !== void 0 && _this$build.error) {
258
261
  throw new Error(this.build.error);
259
262
  } else if (Array.isArray(options)) {
260
263
  return yieldAll(options.map(o => this.yield.snapshot(o)));
261
- } // accept a url for a sitemap or snapshot
262
-
264
+ }
263
265
 
266
+ // accept a url for a sitemap or snapshot
264
267
  if (typeof options === 'string') {
265
268
  options = options.endsWith('.xml') ? {
266
269
  sitemap: options
267
270
  } : {
268
271
  url: options
269
272
  };
270
- } // validate options and add client & environment info
271
-
273
+ }
272
274
 
275
+ // validate options and add client & environment info
273
276
  options = validateSnapshotOptions(options);
274
277
  this.client.addClientInfo(options.clientInfo);
275
- this.client.addEnvironmentInfo(options.environmentInfo); // without a discovery browser, capture is not possible
278
+ this.client.addEnvironmentInfo(options.environmentInfo);
276
279
 
280
+ // without a discovery browser, capture is not possible
277
281
  if (this.skipDiscovery && !this.dryRun && !options.domSnapshot) {
278
282
  throw new Error('Cannot capture DOM snapshots when asset discovery is disabled');
279
- } // return an async generator to allow cancelation
280
-
283
+ }
281
284
 
285
+ // return an async generator to allow cancelation
282
286
  return async function* () {
283
287
  let server;
284
-
285
288
  try {
286
289
  if ('serve' in options) {
287
290
  // create and start a static server
@@ -292,9 +295,9 @@ export class Percy {
292
295
  server = yield createStaticServer(options).listen();
293
296
  baseUrl = options.baseUrl = new URL(baseUrl || '', server.address()).href;
294
297
  if (!snapshots) options.sitemap = new URL('sitemap.xml', baseUrl).href;
295
- } // gather snapshots and discover snapshot resources
296
-
298
+ }
297
299
 
300
+ // gather snapshots and discover snapshot resources
298
301
  yield* discoverSnapshotResources(this.#discovery, {
299
302
  skipDiscovery: this.skipDiscovery,
300
303
  dryRun: this.dryRun,
@@ -310,29 +313,28 @@ export class Percy {
310
313
  });
311
314
  } finally {
312
315
  var _server;
313
-
314
316
  // always close any created server
315
317
  await ((_server = server) === null || _server === void 0 ? void 0 : _server.close());
316
318
  }
317
319
  }.call(this);
318
- } // Uploads one or more snapshots directly to the current Percy build
319
-
320
+ }
320
321
 
322
+ // Uploads one or more snapshots directly to the current Percy build
321
323
  upload(options) {
322
324
  if (this.readyState !== 1) {
323
325
  throw new Error('Not running');
324
326
  } else if (Array.isArray(options)) {
325
327
  return yieldAll(options.map(o => this.yield.upload(o)));
326
- } // validate comparison uploads and warn about any errors
327
-
328
+ }
328
329
 
330
+ // validate comparison uploads and warn about any errors
329
331
  if ('tag' in options || 'tiles' in options) {
330
332
  var _options$tag;
331
-
332
333
  // throw when missing required snapshot or tag name
333
334
  if (!options.name) throw new Error('Missing required snapshot name');
334
- if (!((_options$tag = options.tag) !== null && _options$tag !== void 0 && _options$tag.name)) throw new Error('Missing required tag name for comparison'); // normalize, migrate, and remove certain properties from validating
335
+ if (!((_options$tag = options.tag) !== null && _options$tag !== void 0 && _options$tag.name)) throw new Error('Missing required tag name for comparison');
335
336
 
337
+ // normalize, migrate, and remove certain properties from validating
336
338
  options = PercyConfig.migrate(options, '/comparison');
337
339
  let {
338
340
  clientInfo,
@@ -340,18 +342,17 @@ export class Percy {
340
342
  ...comparison
341
343
  } = options;
342
344
  let errors = PercyConfig.validate(comparison, '/comparison');
343
-
344
345
  if (errors) {
345
346
  this.log.warn('Invalid upload options:');
346
-
347
347
  for (let e of errors) this.log.warn(`- ${e.path}: ${e.message}`);
348
348
  }
349
- } // add client & environment info
350
-
349
+ }
351
350
 
351
+ // add client & environment info
352
352
  this.client.addClientInfo(options.clientInfo);
353
- this.client.addEnvironmentInfo(options.environmentInfo); // return an async generator to allow cancelation
353
+ this.client.addEnvironmentInfo(options.environmentInfo);
354
354
 
355
+ // return an async generator to allow cancelation
355
356
  return async function* () {
356
357
  try {
357
358
  return yield* yieldTo(this.#snapshots.push(options));
@@ -361,6 +362,5 @@ export class Percy {
361
362
  }
362
363
  }.call(this);
363
364
  }
364
-
365
365
  }
366
366
  export default Percy;