@percy/core 1.0.0 → 1.0.3
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 +94 -0
- package/dist/browser.js +292 -0
- package/dist/config.js +551 -0
- package/dist/discovery.js +118 -0
- package/dist/index.js +5 -0
- package/dist/install.js +156 -0
- package/dist/network.js +298 -0
- package/dist/page.js +264 -0
- package/dist/percy.js +484 -0
- package/dist/queue.js +152 -0
- package/dist/server.js +430 -0
- package/dist/session.js +103 -0
- package/dist/snapshot.js +433 -0
- package/dist/utils.js +127 -0
- package/package.json +11 -11
- package/post-install.js +20 -0
- package/test/helpers/dedent.js +27 -0
- package/test/helpers/index.js +34 -0
- package/test/helpers/request.js +15 -0
- package/test/helpers/server.js +33 -0
- package/types/index.d.ts +101 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// Common config options used in Percy commands
|
|
2
|
+
export const configSchema = {
|
|
3
|
+
snapshot: {
|
|
4
|
+
type: 'object',
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
properties: {
|
|
7
|
+
widths: {
|
|
8
|
+
type: 'array',
|
|
9
|
+
default: [375, 1280],
|
|
10
|
+
items: {
|
|
11
|
+
type: 'integer',
|
|
12
|
+
maximum: 2000,
|
|
13
|
+
minimum: 10
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
minHeight: {
|
|
17
|
+
type: 'integer',
|
|
18
|
+
default: 1024,
|
|
19
|
+
maximum: 2000,
|
|
20
|
+
minimum: 10
|
|
21
|
+
},
|
|
22
|
+
percyCSS: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
default: ''
|
|
25
|
+
},
|
|
26
|
+
enableJavaScript: {
|
|
27
|
+
type: 'boolean'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
discovery: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
properties: {
|
|
35
|
+
allowedHostnames: {
|
|
36
|
+
type: 'array',
|
|
37
|
+
default: [],
|
|
38
|
+
items: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
allOf: [{
|
|
41
|
+
not: {
|
|
42
|
+
pattern: '[^/]/'
|
|
43
|
+
},
|
|
44
|
+
error: 'must not include a pathname'
|
|
45
|
+
}, {
|
|
46
|
+
not: {
|
|
47
|
+
pattern: '^([a-zA-Z]+:)?//'
|
|
48
|
+
},
|
|
49
|
+
error: 'must not include a protocol'
|
|
50
|
+
}]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
disallowedHostnames: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
default: [],
|
|
56
|
+
items: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
allOf: [{
|
|
59
|
+
not: {
|
|
60
|
+
pattern: '[^/]/'
|
|
61
|
+
},
|
|
62
|
+
error: 'must not include a pathname'
|
|
63
|
+
}, {
|
|
64
|
+
not: {
|
|
65
|
+
pattern: '^([a-zA-Z]+:)?//'
|
|
66
|
+
},
|
|
67
|
+
error: 'must not include a protocol'
|
|
68
|
+
}]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
networkIdleTimeout: {
|
|
72
|
+
type: 'integer',
|
|
73
|
+
default: 100,
|
|
74
|
+
maximum: 750,
|
|
75
|
+
minimum: 1
|
|
76
|
+
},
|
|
77
|
+
disableCache: {
|
|
78
|
+
type: 'boolean'
|
|
79
|
+
},
|
|
80
|
+
requestHeaders: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
normalize: false,
|
|
83
|
+
additionalProperties: {
|
|
84
|
+
type: 'string'
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
authorization: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
properties: {
|
|
91
|
+
username: {
|
|
92
|
+
type: 'string'
|
|
93
|
+
},
|
|
94
|
+
password: {
|
|
95
|
+
type: 'string'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
cookies: {
|
|
100
|
+
anyOf: [{
|
|
101
|
+
type: 'object',
|
|
102
|
+
normalize: false,
|
|
103
|
+
additionalProperties: {
|
|
104
|
+
type: 'string'
|
|
105
|
+
}
|
|
106
|
+
}, {
|
|
107
|
+
type: 'array',
|
|
108
|
+
normalize: false,
|
|
109
|
+
items: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
required: ['name', 'value'],
|
|
112
|
+
properties: {
|
|
113
|
+
name: {
|
|
114
|
+
type: 'string'
|
|
115
|
+
},
|
|
116
|
+
value: {
|
|
117
|
+
type: 'string'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}]
|
|
122
|
+
},
|
|
123
|
+
userAgent: {
|
|
124
|
+
type: 'string'
|
|
125
|
+
},
|
|
126
|
+
concurrency: {
|
|
127
|
+
type: 'integer',
|
|
128
|
+
minimum: 1
|
|
129
|
+
},
|
|
130
|
+
launchOptions: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties: {
|
|
134
|
+
executable: {
|
|
135
|
+
type: 'string'
|
|
136
|
+
},
|
|
137
|
+
timeout: {
|
|
138
|
+
type: 'integer'
|
|
139
|
+
},
|
|
140
|
+
args: {
|
|
141
|
+
type: 'array',
|
|
142
|
+
items: {
|
|
143
|
+
type: 'string'
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
headless: {
|
|
147
|
+
type: 'boolean'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}; // Common per-snapshot capture options
|
|
154
|
+
|
|
155
|
+
export const snapshotSchema = {
|
|
156
|
+
$id: '/snapshot',
|
|
157
|
+
$ref: '#/$defs/snapshot',
|
|
158
|
+
$defs: {
|
|
159
|
+
common: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
widths: {
|
|
163
|
+
$ref: '/config/snapshot#/properties/widths'
|
|
164
|
+
},
|
|
165
|
+
minHeight: {
|
|
166
|
+
$ref: '/config/snapshot#/properties/minHeight'
|
|
167
|
+
},
|
|
168
|
+
percyCSS: {
|
|
169
|
+
$ref: '/config/snapshot#/properties/percyCSS'
|
|
170
|
+
},
|
|
171
|
+
enableJavaScript: {
|
|
172
|
+
$ref: '/config/snapshot#/properties/enableJavaScript'
|
|
173
|
+
},
|
|
174
|
+
discovery: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
additionalProperties: false,
|
|
177
|
+
properties: {
|
|
178
|
+
allowedHostnames: {
|
|
179
|
+
$ref: '/config/discovery#/properties/allowedHostnames'
|
|
180
|
+
},
|
|
181
|
+
disallowedHostnames: {
|
|
182
|
+
$ref: '/config/discovery#/properties/disallowedHostnames'
|
|
183
|
+
},
|
|
184
|
+
requestHeaders: {
|
|
185
|
+
$ref: '/config/discovery#/properties/requestHeaders'
|
|
186
|
+
},
|
|
187
|
+
authorization: {
|
|
188
|
+
$ref: '/config/discovery#/properties/authorization'
|
|
189
|
+
},
|
|
190
|
+
disableCache: {
|
|
191
|
+
$ref: '/config/discovery#/properties/disableCache'
|
|
192
|
+
},
|
|
193
|
+
userAgent: {
|
|
194
|
+
$ref: '/config/discovery#/properties/userAgent'
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
exec: {
|
|
201
|
+
error: 'must be a function, function body, or array of functions',
|
|
202
|
+
oneOf: [{
|
|
203
|
+
oneOf: [{
|
|
204
|
+
type: 'string'
|
|
205
|
+
}, {
|
|
206
|
+
instanceof: 'Function'
|
|
207
|
+
}]
|
|
208
|
+
}, {
|
|
209
|
+
type: 'array',
|
|
210
|
+
items: {
|
|
211
|
+
$ref: '/snapshot#/$defs/exec/oneOf/0'
|
|
212
|
+
}
|
|
213
|
+
}]
|
|
214
|
+
},
|
|
215
|
+
precapture: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
waitForSelector: {
|
|
219
|
+
type: 'string'
|
|
220
|
+
},
|
|
221
|
+
waitForTimeout: {
|
|
222
|
+
type: 'integer',
|
|
223
|
+
minimum: 1,
|
|
224
|
+
maximum: 30000
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
capture: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
allOf: [{
|
|
231
|
+
$ref: '/snapshot#/$defs/common'
|
|
232
|
+
}, {
|
|
233
|
+
$ref: '/snapshot#/$defs/precapture'
|
|
234
|
+
}],
|
|
235
|
+
properties: {
|
|
236
|
+
name: {
|
|
237
|
+
type: 'string'
|
|
238
|
+
},
|
|
239
|
+
execute: {
|
|
240
|
+
oneOf: [{
|
|
241
|
+
$ref: '/snapshot#/$defs/exec'
|
|
242
|
+
}, {
|
|
243
|
+
type: 'object',
|
|
244
|
+
additionalProperties: false,
|
|
245
|
+
properties: {
|
|
246
|
+
afterNavigation: {
|
|
247
|
+
$ref: '/snapshot#/$defs/exec'
|
|
248
|
+
},
|
|
249
|
+
beforeResize: {
|
|
250
|
+
$ref: '/snapshot#/$defs/exec'
|
|
251
|
+
},
|
|
252
|
+
afterResize: {
|
|
253
|
+
$ref: '/snapshot#/$defs/exec'
|
|
254
|
+
},
|
|
255
|
+
beforeSnapshot: {
|
|
256
|
+
$ref: '/snapshot#/$defs/exec'
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}]
|
|
260
|
+
},
|
|
261
|
+
additionalSnapshots: {
|
|
262
|
+
type: 'array',
|
|
263
|
+
items: {
|
|
264
|
+
type: 'object',
|
|
265
|
+
$ref: '/snapshot#/$defs/precapture',
|
|
266
|
+
unevaluatedProperties: false,
|
|
267
|
+
oneOf: [{
|
|
268
|
+
required: ['name']
|
|
269
|
+
}, {
|
|
270
|
+
anyOf: [{
|
|
271
|
+
required: ['prefix']
|
|
272
|
+
}, {
|
|
273
|
+
required: ['suffix']
|
|
274
|
+
}]
|
|
275
|
+
}],
|
|
276
|
+
properties: {
|
|
277
|
+
name: {
|
|
278
|
+
type: 'string'
|
|
279
|
+
},
|
|
280
|
+
prefix: {
|
|
281
|
+
type: 'string'
|
|
282
|
+
},
|
|
283
|
+
suffix: {
|
|
284
|
+
type: 'string'
|
|
285
|
+
},
|
|
286
|
+
execute: {
|
|
287
|
+
$ref: '/snapshot#/$defs/exec'
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
errors: {
|
|
291
|
+
oneOf: ({
|
|
292
|
+
params
|
|
293
|
+
}) => params.passingSchemas ? 'prefix & suffix are ignored when a name is provided' : 'missing required name, prefix, or suffix'
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
predicate: {
|
|
300
|
+
error: 'must be a pattern or an array of patterns',
|
|
301
|
+
oneOf: [{
|
|
302
|
+
oneOf: [{
|
|
303
|
+
type: 'string'
|
|
304
|
+
}, {
|
|
305
|
+
instanceof: 'RegExp'
|
|
306
|
+
}, {
|
|
307
|
+
instanceof: 'Function'
|
|
308
|
+
}]
|
|
309
|
+
}, {
|
|
310
|
+
type: 'array',
|
|
311
|
+
items: {
|
|
312
|
+
$ref: '/snapshot#/$defs/predicate/oneOf/0'
|
|
313
|
+
}
|
|
314
|
+
}]
|
|
315
|
+
},
|
|
316
|
+
filter: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
include: {
|
|
320
|
+
$ref: '/snapshot#/$defs/predicate'
|
|
321
|
+
},
|
|
322
|
+
exclude: {
|
|
323
|
+
$ref: '/snapshot#/$defs/predicate'
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
options: {
|
|
328
|
+
oneOf: [{
|
|
329
|
+
type: 'object',
|
|
330
|
+
unevaluatedProperties: false,
|
|
331
|
+
allOf: [{
|
|
332
|
+
$ref: '/snapshot#/$defs/filter'
|
|
333
|
+
}, {
|
|
334
|
+
$ref: '/snapshot#/$defs/capture'
|
|
335
|
+
}]
|
|
336
|
+
}, {
|
|
337
|
+
type: 'array',
|
|
338
|
+
items: {
|
|
339
|
+
$ref: '/snapshot#/$defs/options/oneOf/0'
|
|
340
|
+
}
|
|
341
|
+
}]
|
|
342
|
+
},
|
|
343
|
+
snapshot: {
|
|
344
|
+
type: 'object',
|
|
345
|
+
required: ['url'],
|
|
346
|
+
$ref: '/snapshot#/$defs/capture',
|
|
347
|
+
unevaluatedProperties: false,
|
|
348
|
+
properties: {
|
|
349
|
+
url: {
|
|
350
|
+
type: 'string'
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
snapshots: {
|
|
355
|
+
type: 'array',
|
|
356
|
+
items: {
|
|
357
|
+
oneOf: [{
|
|
358
|
+
$ref: '/snapshot#/$defs/snapshot'
|
|
359
|
+
}, {
|
|
360
|
+
$ref: '/snapshot#/$defs/snapshot/properties/url'
|
|
361
|
+
}]
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
dom: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
$id: '/snapshot/dom',
|
|
367
|
+
$ref: '/snapshot#/$defs/common',
|
|
368
|
+
required: ['url', 'domSnapshot'],
|
|
369
|
+
unevaluatedProperties: false,
|
|
370
|
+
properties: {
|
|
371
|
+
url: {
|
|
372
|
+
type: 'string'
|
|
373
|
+
},
|
|
374
|
+
name: {
|
|
375
|
+
type: 'string'
|
|
376
|
+
},
|
|
377
|
+
domSnapshot: {
|
|
378
|
+
type: 'string'
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
errors: {
|
|
382
|
+
unevaluatedProperties: e => snapshotSchema.$defs.precapture.properties[e.params.unevaluatedProperty] || snapshotSchema.$defs.capture.properties[e.params.unevaluatedProperty] ? 'not accepted with DOM snapshots' : 'unknown property'
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
list: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
$id: '/snapshot/list',
|
|
388
|
+
$ref: '/snapshot#/$defs/filter',
|
|
389
|
+
unevaluatedProperties: false,
|
|
390
|
+
required: ['snapshots'],
|
|
391
|
+
properties: {
|
|
392
|
+
baseUrl: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
pattern: '^https?://',
|
|
395
|
+
errors: {
|
|
396
|
+
pattern: 'must include a protocol and hostname'
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
snapshots: {
|
|
400
|
+
$ref: '/snapshot#/$defs/snapshots'
|
|
401
|
+
},
|
|
402
|
+
options: {
|
|
403
|
+
$ref: '/snapshot#/$defs/options'
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
server: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
$id: '/snapshot/server',
|
|
410
|
+
$ref: '/snapshot#/$defs/filter',
|
|
411
|
+
unevaluatedProperties: false,
|
|
412
|
+
required: ['serve'],
|
|
413
|
+
properties: {
|
|
414
|
+
serve: {
|
|
415
|
+
type: 'string'
|
|
416
|
+
},
|
|
417
|
+
port: {
|
|
418
|
+
type: 'integer'
|
|
419
|
+
},
|
|
420
|
+
baseUrl: {
|
|
421
|
+
type: 'string',
|
|
422
|
+
pattern: '^/',
|
|
423
|
+
errors: {
|
|
424
|
+
pattern: 'must start with a forward slash (/)'
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
cleanUrls: {
|
|
428
|
+
type: 'boolean'
|
|
429
|
+
},
|
|
430
|
+
rewrites: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
normalize: false,
|
|
433
|
+
additionalProperties: {
|
|
434
|
+
type: 'string'
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
snapshots: {
|
|
438
|
+
$ref: '/snapshot#/$defs/snapshots'
|
|
439
|
+
},
|
|
440
|
+
options: {
|
|
441
|
+
$ref: '/snapshot#/$defs/options'
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
sitemap: {
|
|
446
|
+
type: 'object',
|
|
447
|
+
$id: '/snapshot/sitemap',
|
|
448
|
+
$ref: '/snapshot#/$defs/filter',
|
|
449
|
+
required: ['sitemap'],
|
|
450
|
+
unevaluatedProperties: false,
|
|
451
|
+
properties: {
|
|
452
|
+
sitemap: {
|
|
453
|
+
type: 'string'
|
|
454
|
+
},
|
|
455
|
+
options: {
|
|
456
|
+
$ref: '/snapshot#/$defs/options'
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}; // Grouped schemas for easier registration
|
|
462
|
+
|
|
463
|
+
export const schemas = [configSchema, snapshotSchema]; // Config migrate function
|
|
464
|
+
|
|
465
|
+
export function configMigration(config, util) {
|
|
466
|
+
/* eslint-disable curly */
|
|
467
|
+
if (config.version < 2) {
|
|
468
|
+
// discovery options have moved
|
|
469
|
+
util.map('agent.assetDiscovery.allowedHostnames', 'discovery.allowedHostnames');
|
|
470
|
+
util.map('agent.assetDiscovery.networkIdleTimeout', 'discovery.networkIdleTimeout');
|
|
471
|
+
util.map('agent.assetDiscovery.cacheResponses', 'discovery.disableCache', v => !v);
|
|
472
|
+
util.map('agent.assetDiscovery.requestHeaders', 'discovery.requestHeaders');
|
|
473
|
+
util.map('agent.assetDiscovery.pagePoolSizeMax', 'discovery.concurrency');
|
|
474
|
+
util.del('agent');
|
|
475
|
+
} else {
|
|
476
|
+
let notice = {
|
|
477
|
+
type: 'config',
|
|
478
|
+
until: '1.0.0'
|
|
479
|
+
}; // snapshot discovery options have moved
|
|
480
|
+
|
|
481
|
+
util.deprecate('snapshot.authorization', {
|
|
482
|
+
map: 'discovery.authorization',
|
|
483
|
+
...notice
|
|
484
|
+
});
|
|
485
|
+
util.deprecate('snapshot.requestHeaders', {
|
|
486
|
+
map: 'discovery.requestHeaders',
|
|
487
|
+
...notice
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
} // Snapshot option migrate function
|
|
491
|
+
|
|
492
|
+
export function snapshotMigration(config, util, root = '') {
|
|
493
|
+
let notice = {
|
|
494
|
+
type: 'snapshot',
|
|
495
|
+
until: '1.0.0',
|
|
496
|
+
warn: true
|
|
497
|
+
}; // discovery options have moved
|
|
498
|
+
|
|
499
|
+
util.deprecate(`${root}.authorization`, {
|
|
500
|
+
map: `${root}.discovery.authorization`,
|
|
501
|
+
...notice
|
|
502
|
+
});
|
|
503
|
+
util.deprecate(`${root}.requestHeaders`, {
|
|
504
|
+
map: `${root}.discovery.requestHeaders`,
|
|
505
|
+
...notice
|
|
506
|
+
}); // snapshots option was renamed
|
|
507
|
+
|
|
508
|
+
util.deprecate(`${root}.snapshots`, {
|
|
509
|
+
map: `${root}.additionalSnapshots`,
|
|
510
|
+
...notice
|
|
511
|
+
});
|
|
512
|
+
} // Snapshot list options migrate function
|
|
513
|
+
|
|
514
|
+
export function snapshotListMigration(config, util) {
|
|
515
|
+
if (config.snapshots) {
|
|
516
|
+
// migrate each snapshot options
|
|
517
|
+
for (let i in config.snapshots) {
|
|
518
|
+
if (typeof config.snapshots[i] !== 'string') {
|
|
519
|
+
snapshotMigration(config, util, `snapshots[${i}]`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
} // overrides option was renamed
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
let notice = {
|
|
526
|
+
type: 'snapshot',
|
|
527
|
+
until: '1.0.0',
|
|
528
|
+
warn: true
|
|
529
|
+
};
|
|
530
|
+
util.deprecate('overrides', {
|
|
531
|
+
map: 'options',
|
|
532
|
+
...notice
|
|
533
|
+
}); // migrate options
|
|
534
|
+
|
|
535
|
+
if (Array.isArray(config.options)) {
|
|
536
|
+
for (let i in config.options) {
|
|
537
|
+
snapshotMigration(config, util, `options[${i}]`);
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
snapshotMigration(config, util, 'options');
|
|
541
|
+
}
|
|
542
|
+
} // Grouped migrations for easier registration
|
|
543
|
+
|
|
544
|
+
export const migrations = {
|
|
545
|
+
'/config': configMigration,
|
|
546
|
+
'/snapshot': snapshotMigration,
|
|
547
|
+
'/snapshot/dom': snapshotMigration,
|
|
548
|
+
'/snapshot/list': snapshotListMigration,
|
|
549
|
+
'/snapshot/server': snapshotListMigration,
|
|
550
|
+
'/snapshot/sitemap': snapshotListMigration
|
|
551
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import logger from '@percy/logger';
|
|
2
|
+
import { normalizeURL, hostnameMatches, createResource } from './utils.js';
|
|
3
|
+
const MAX_RESOURCE_SIZE = 15 * 1024 ** 2; // 15MB
|
|
4
|
+
|
|
5
|
+
const ALLOWED_STATUSES = [200, 201, 301, 302, 304, 307, 308];
|
|
6
|
+
const ALLOWED_RESOURCES = ['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Other'];
|
|
7
|
+
export function createRequestHandler(network, {
|
|
8
|
+
disableCache,
|
|
9
|
+
disallowedHostnames,
|
|
10
|
+
getResource
|
|
11
|
+
}) {
|
|
12
|
+
let log = logger('core:discovery');
|
|
13
|
+
return async request => {
|
|
14
|
+
let url = request.url;
|
|
15
|
+
let meta = { ...network.meta,
|
|
16
|
+
url
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
log.debug(`Handling request: ${url}`, meta);
|
|
21
|
+
let resource = getResource(url);
|
|
22
|
+
|
|
23
|
+
if (resource !== null && resource !== void 0 && resource.root) {
|
|
24
|
+
log.debug('- Serving root resource', meta);
|
|
25
|
+
await request.respond(resource);
|
|
26
|
+
} else if (hostnameMatches(disallowedHostnames, url)) {
|
|
27
|
+
log.debug('- Skipping disallowed hostname', meta);
|
|
28
|
+
await request.abort(true);
|
|
29
|
+
} else if (resource && !disableCache) {
|
|
30
|
+
log.debug('- Resource cache hit', meta);
|
|
31
|
+
await request.respond(resource);
|
|
32
|
+
} else {
|
|
33
|
+
await request.continue();
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
log.debug(`Encountered an error handling request: ${url}`, meta);
|
|
37
|
+
log.debug(error);
|
|
38
|
+
/* istanbul ignore next: race condition */
|
|
39
|
+
|
|
40
|
+
await request.abort(error).catch(e => log.debug(e, meta));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function createRequestFinishedHandler(network, {
|
|
45
|
+
enableJavaScript,
|
|
46
|
+
allowedHostnames,
|
|
47
|
+
disableCache,
|
|
48
|
+
getResource,
|
|
49
|
+
saveResource
|
|
50
|
+
}) {
|
|
51
|
+
let log = logger('core:discovery');
|
|
52
|
+
return async request => {
|
|
53
|
+
let origin = request.redirectChain[0] || request;
|
|
54
|
+
let url = normalizeURL(origin.url);
|
|
55
|
+
let meta = { ...network.meta,
|
|
56
|
+
url
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
var _resource;
|
|
61
|
+
|
|
62
|
+
let resource = getResource(url); // process and cache the response and resource
|
|
63
|
+
|
|
64
|
+
if (!((_resource = resource) !== null && _resource !== void 0 && _resource.root) && (!resource || disableCache)) {
|
|
65
|
+
let response = request.response;
|
|
66
|
+
let capture = response && hostnameMatches(allowedHostnames, url);
|
|
67
|
+
let body = capture && (await response.buffer());
|
|
68
|
+
log.debug(`Processing resource: ${url}`, meta);
|
|
69
|
+
/* istanbul ignore next: sanity check */
|
|
70
|
+
|
|
71
|
+
if (!response) {
|
|
72
|
+
return log.debug('- Skipping no response', meta);
|
|
73
|
+
} else if (!capture) {
|
|
74
|
+
return log.debug('- Skipping remote resource', meta);
|
|
75
|
+
} else if (!body.length) {
|
|
76
|
+
return log.debug('- Skipping empty response', meta);
|
|
77
|
+
} else if (body.length > MAX_RESOURCE_SIZE) {
|
|
78
|
+
return log.debug('- Skipping resource larger than 15MB', meta);
|
|
79
|
+
} else if (!ALLOWED_STATUSES.includes(response.status)) {
|
|
80
|
+
return log.debug(`- Skipping disallowed status [${response.status}]`, meta);
|
|
81
|
+
} else if (!enableJavaScript && !ALLOWED_RESOURCES.includes(request.type)) {
|
|
82
|
+
return log.debug(`- Skipping disallowed resource type [${request.type}]`, meta);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
resource = createResource(url, body, response.mimeType, {
|
|
86
|
+
status: response.status,
|
|
87
|
+
// 'Network.responseReceived' returns headers split by newlines, however
|
|
88
|
+
// `Fetch.fulfillRequest` (used for cached responses) will hang with newlines.
|
|
89
|
+
headers: Object.entries(response.headers).reduce((norm, [key, value]) => Object.assign(norm, {
|
|
90
|
+
[key]: value.split('\n')
|
|
91
|
+
}), {})
|
|
92
|
+
});
|
|
93
|
+
log.debug(`- sha: ${resource.sha}`, meta);
|
|
94
|
+
log.debug(`- mimetype: ${resource.mimetype}`, meta);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
saveResource(resource);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
log.debug(`Encountered an error processing resource: ${url}`, meta);
|
|
100
|
+
log.debug(error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function createRequestFailedHandler(network) {
|
|
105
|
+
let log = logger('core:discovery');
|
|
106
|
+
return ({
|
|
107
|
+
url,
|
|
108
|
+
error
|
|
109
|
+
}) => {
|
|
110
|
+
// do not log generic failures since the real error was most likely
|
|
111
|
+
// already logged from elsewhere
|
|
112
|
+
if (error !== 'net::ERR_FAILED') {
|
|
113
|
+
log.debug(`Request failed for ${url}: ${error}`, { ...network.meta,
|
|
114
|
+
url
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|