@percy/core 1.12.0 → 1.13.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/api.js +60 -45
- package/dist/browser.js +82 -68
- package/dist/config.js +16 -9
- package/dist/discovery.js +65 -60
- package/dist/install.js +29 -27
- package/dist/network.js +72 -79
- package/dist/page.js +47 -54
- package/dist/percy.js +85 -85
- package/dist/queue.js +103 -146
- package/dist/server.js +51 -88
- package/dist/session.js +8 -15
- package/dist/snapshot.js +105 -92
- package/dist/utils.js +60 -58
- package/package.json +6 -6
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';
|
|
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;
|
|
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);
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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);
|
|
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;
|
|
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
|
-
}
|
|
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
|
-
});
|
|
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
|
-
}
|
|
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();
|
|
148
|
-
|
|
149
|
-
if (!this.skipDiscovery) yield this.#discovery.start();
|
|
150
|
-
|
|
151
|
-
if (this.server) yield this.server.listen();
|
|
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();
|
|
160
|
+
await this.#snapshots.end();
|
|
162
161
|
|
|
163
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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);
|
|
184
|
+
options && (options = !callback ? [].concat(options) : null);
|
|
184
185
|
|
|
185
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
206
|
-
|
|
207
|
+
}
|
|
207
208
|
|
|
208
|
-
|
|
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
|
-
}
|
|
214
|
-
|
|
216
|
+
}
|
|
215
217
|
|
|
218
|
+
// already stopping
|
|
216
219
|
if (this.readyState === 2) return;
|
|
217
|
-
this.readyState = 2;
|
|
220
|
+
this.readyState = 2;
|
|
218
221
|
|
|
219
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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();
|
|
247
|
+
await this.#snapshots.end();
|
|
245
248
|
|
|
249
|
+
// mark instance as stopped
|
|
246
250
|
this.readyState = 3;
|
|
247
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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);
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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');
|
|
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
|
-
}
|
|
350
|
-
|
|
349
|
+
}
|
|
351
350
|
|
|
351
|
+
// add client & environment info
|
|
352
352
|
this.client.addClientInfo(options.clientInfo);
|
|
353
|
-
this.client.addEnvironmentInfo(options.environmentInfo);
|
|
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;
|