@percy/core 1.0.0-beta.7 → 1.0.0-beta.73

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
@@ -3,35 +3,35 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.schema = void 0;
7
- // Common options used in Percy commands
8
- const schema = {
6
+ exports.configMigration = configMigration;
7
+ exports.snapshotDOMSchema = exports.schemas = exports.migrations = exports.configSchema = void 0;
8
+ exports.snapshotMigration = snapshotMigration;
9
+ exports.snapshotSchema = void 0;
10
+ // Common config options used in Percy commands
11
+ const configSchema = {
9
12
  snapshot: {
10
13
  type: 'object',
11
14
  additionalProperties: false,
12
15
  properties: {
13
16
  widths: {
14
17
  type: 'array',
18
+ default: [375, 1280],
15
19
  items: {
16
- type: 'integer'
17
- },
18
- default: [375, 1280]
20
+ type: 'integer',
21
+ maximum: 2000,
22
+ minimum: 10
23
+ }
19
24
  },
20
25
  minHeight: {
21
26
  type: 'integer',
22
- default: 1024
27
+ default: 1024,
28
+ maximum: 2000,
29
+ minimum: 10
23
30
  },
24
31
  percyCSS: {
25
32
  type: 'string',
26
33
  default: ''
27
34
  },
28
- requestHeaders: {
29
- type: 'object',
30
- additionalProperties: {
31
- type: 'string'
32
- },
33
- default: {}
34
- },
35
35
  enableJavaScript: {
36
36
  type: 'boolean'
37
37
  }
@@ -43,27 +43,316 @@ const schema = {
43
43
  properties: {
44
44
  allowedHostnames: {
45
45
  type: 'array',
46
+ default: [],
46
47
  items: {
47
- type: 'string'
48
- },
49
- default: []
48
+ type: 'string',
49
+ allOf: [{
50
+ not: {
51
+ pattern: '[^/]/'
52
+ },
53
+ error: 'must not include a pathname'
54
+ }, {
55
+ not: {
56
+ pattern: '^([a-zA-Z]+:)?//'
57
+ },
58
+ error: 'must not include a protocol'
59
+ }]
60
+ }
50
61
  },
51
62
  networkIdleTimeout: {
52
63
  type: 'integer',
53
- default: 100
64
+ default: 100,
65
+ maximum: 750,
66
+ minimum: 1
67
+ },
68
+ disableCache: {
69
+ type: 'boolean'
70
+ },
71
+ requestHeaders: {
72
+ type: 'object',
73
+ normalize: false,
74
+ additionalProperties: {
75
+ type: 'string'
76
+ }
54
77
  },
55
- disableAssetCache: {
56
- type: 'boolean',
57
- default: false
78
+ authorization: {
79
+ type: 'object',
80
+ additionalProperties: false,
81
+ properties: {
82
+ username: {
83
+ type: 'string'
84
+ },
85
+ password: {
86
+ type: 'string'
87
+ }
88
+ }
89
+ },
90
+ cookies: {
91
+ anyOf: [{
92
+ type: 'object',
93
+ normalize: false,
94
+ additionalProperties: {
95
+ type: 'string'
96
+ }
97
+ }, {
98
+ type: 'array',
99
+ normalize: false,
100
+ items: {
101
+ type: 'object',
102
+ required: ['name', 'value'],
103
+ properties: {
104
+ name: {
105
+ type: 'string'
106
+ },
107
+ value: {
108
+ type: 'string'
109
+ }
110
+ }
111
+ }
112
+ }]
113
+ },
114
+ userAgent: {
115
+ type: 'string'
58
116
  },
59
117
  concurrency: {
60
118
  type: 'integer',
61
- default: 5
119
+ minimum: 1
62
120
  },
63
121
  launchOptions: {
64
- type: 'object'
122
+ type: 'object',
123
+ additionalProperties: false,
124
+ properties: {
125
+ executable: {
126
+ type: 'string'
127
+ },
128
+ timeout: {
129
+ type: 'integer'
130
+ },
131
+ args: {
132
+ type: 'array',
133
+ items: {
134
+ type: 'string'
135
+ }
136
+ },
137
+ headless: {
138
+ type: 'boolean'
139
+ }
140
+ }
65
141
  }
66
142
  }
67
143
  }
68
- };
69
- exports.schema = schema;
144
+ }; // Common per-snapshot capture options
145
+
146
+ exports.configSchema = configSchema;
147
+ const snapshotSchema = {
148
+ $id: '/snapshot',
149
+ type: 'object',
150
+ required: ['url'],
151
+ additionalProperties: false,
152
+ $refs: {
153
+ exec: {
154
+ oneOf: [{
155
+ oneOf: [{
156
+ type: 'string'
157
+ }, {
158
+ instanceof: 'Function'
159
+ }]
160
+ }, {
161
+ type: 'array',
162
+ items: {
163
+ $ref: '#/$refs/exec/oneOf/0'
164
+ }
165
+ }]
166
+ }
167
+ },
168
+ properties: {
169
+ url: {
170
+ type: 'string'
171
+ },
172
+ name: {
173
+ type: 'string'
174
+ },
175
+ widths: {
176
+ $ref: '/config/snapshot#/properties/widths'
177
+ },
178
+ minHeight: {
179
+ $ref: '/config/snapshot#/properties/minHeight'
180
+ },
181
+ percyCSS: {
182
+ $ref: '/config/snapshot#/properties/percyCSS'
183
+ },
184
+ enableJavaScript: {
185
+ $ref: '/config/snapshot#/properties/enableJavaScript'
186
+ },
187
+ discovery: {
188
+ type: 'object',
189
+ additionalProperties: false,
190
+ properties: {
191
+ allowedHostnames: {
192
+ $ref: '/config/discovery#/properties/allowedHostnames'
193
+ },
194
+ requestHeaders: {
195
+ $ref: '/config/discovery#/properties/requestHeaders'
196
+ },
197
+ authorization: {
198
+ $ref: '/config/discovery#/properties/authorization'
199
+ },
200
+ disableCache: {
201
+ $ref: '/config/discovery#/properties/disableCache'
202
+ },
203
+ userAgent: {
204
+ $ref: '/config/discovery#/properties/userAgent'
205
+ }
206
+ }
207
+ },
208
+ waitForSelector: {
209
+ type: 'string'
210
+ },
211
+ waitForTimeout: {
212
+ type: 'integer',
213
+ minimum: 1,
214
+ maximum: 30000
215
+ },
216
+ execute: {
217
+ oneOf: [{
218
+ $ref: '/snapshot#/$refs/exec'
219
+ }, {
220
+ type: 'object',
221
+ additionalProperties: false,
222
+ properties: {
223
+ afterNavigation: {
224
+ $ref: '/snapshot#/$refs/exec'
225
+ },
226
+ beforeResize: {
227
+ $ref: '/snapshot#/$refs/exec'
228
+ },
229
+ afterResize: {
230
+ $ref: '/snapshot#/$refs/exec'
231
+ },
232
+ beforeSnapshot: {
233
+ $ref: '/snapshot#/$refs/exec'
234
+ }
235
+ }
236
+ }]
237
+ },
238
+ additionalSnapshots: {
239
+ type: 'array',
240
+ items: {
241
+ type: 'object',
242
+ additionalProperties: false,
243
+ oneOf: [{
244
+ required: ['name']
245
+ }, {
246
+ anyOf: [{
247
+ required: ['prefix']
248
+ }, {
249
+ required: ['suffix']
250
+ }]
251
+ }],
252
+ properties: {
253
+ prefix: {
254
+ type: 'string'
255
+ },
256
+ suffix: {
257
+ type: 'string'
258
+ },
259
+ name: {
260
+ $ref: '/snapshot#/properties/name'
261
+ },
262
+ waitForTimeout: {
263
+ $ref: '/snapshot#/properties/waitForTimeout'
264
+ },
265
+ waitForSelector: {
266
+ $ref: '/snapshot#/properties/waitForSelector'
267
+ },
268
+ execute: {
269
+ $ref: '/snapshot#/$refs/exec'
270
+ }
271
+ },
272
+ errors: {
273
+ oneOf: ({
274
+ params
275
+ }) => params.passingSchemas ? 'prefix & suffix are ignored when a name is provided' : 'missing required name, prefix, or suffix'
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }; // Disallow capture options for dom snapshots
281
+
282
+ exports.snapshotSchema = snapshotSchema;
283
+ const snapshotDOMSchema = {
284
+ $id: '/snapshot/dom',
285
+ type: 'object',
286
+ additionalProperties: false,
287
+ required: ['url', 'domSnapshot'],
288
+ disallowed: ['additionalSnapshots', 'waitForTimeout', 'waitForSelector', 'execute'],
289
+ errors: {
290
+ disallowed: 'not accepted with DOM snapshots'
291
+ },
292
+ properties: {
293
+ domSnapshot: {
294
+ type: 'string'
295
+ },
296
+ // schemas have no concept of inheritance, but we can leverage JS for brevity
297
+ ...snapshotSchema.properties
298
+ }
299
+ }; // Grouped schemas for easier registration
300
+
301
+ exports.snapshotDOMSchema = snapshotDOMSchema;
302
+ const schemas = [configSchema, snapshotSchema, snapshotDOMSchema]; // Config migrate function
303
+
304
+ exports.schemas = schemas;
305
+
306
+ function configMigration(config, util) {
307
+ /* eslint-disable curly */
308
+ if (config.version < 2) {
309
+ // discovery options have moved
310
+ util.map('agent.assetDiscovery.allowedHostnames', 'discovery.allowedHostnames');
311
+ util.map('agent.assetDiscovery.networkIdleTimeout', 'discovery.networkIdleTimeout');
312
+ util.map('agent.assetDiscovery.cacheResponses', 'discovery.disableCache', v => !v);
313
+ util.map('agent.assetDiscovery.requestHeaders', 'discovery.requestHeaders');
314
+ util.map('agent.assetDiscovery.pagePoolSizeMax', 'discovery.concurrency');
315
+ util.del('agent');
316
+ } else {
317
+ let notice = {
318
+ type: 'config',
319
+ until: '1.0.0'
320
+ }; // snapshot discovery options have moved
321
+
322
+ util.deprecate('snapshot.authorization', {
323
+ map: 'discovery.authorization',
324
+ ...notice
325
+ });
326
+ util.deprecate('snapshot.requestHeaders', {
327
+ map: 'discovery.requestHeaders',
328
+ ...notice
329
+ });
330
+ }
331
+ } // Snapshot option migrate function
332
+
333
+
334
+ function snapshotMigration(config, util) {
335
+ let notice = {
336
+ type: 'snapshot',
337
+ until: '1.0.0',
338
+ warn: true
339
+ }; // discovery options have moved
340
+
341
+ util.deprecate('authorization', {
342
+ map: 'discovery.authorization',
343
+ ...notice
344
+ });
345
+ util.deprecate('requestHeaders', {
346
+ map: 'discovery.requestHeaders',
347
+ ...notice
348
+ }); // snapshots option was renamed
349
+
350
+ util.deprecate('snapshots', {
351
+ map: 'additionalSnapshots',
352
+ ...notice
353
+ });
354
+ } // Grouped migrations for easier registration
355
+
356
+
357
+ const migrations = [['/config', configMigration], ['/snapshot', snapshotMigration]];
358
+ exports.migrations = migrations;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createRequestFailedHandler = createRequestFailedHandler;
7
+ exports.createRequestFinishedHandler = createRequestFinishedHandler;
8
+ exports.createRequestHandler = createRequestHandler;
9
+
10
+ var _logger = _interopRequireDefault(require("@percy/logger"));
11
+
12
+ var _utils = require("./utils");
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ const MAX_RESOURCE_SIZE = 15 * 1024 ** 2; // 15MB
17
+
18
+ const ALLOWED_STATUSES = [200, 201, 301, 302, 304, 307, 308];
19
+ const ALLOWED_RESOURCES = ['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Other'];
20
+
21
+ function createRequestHandler(network, {
22
+ disableCache,
23
+ getResource
24
+ }) {
25
+ let log = (0, _logger.default)('core:discovery');
26
+ return async request => {
27
+ let url = request.url;
28
+ let meta = { ...network.meta,
29
+ url
30
+ };
31
+
32
+ try {
33
+ log.debug(`Handling request: ${url}`, meta);
34
+ let resource = getResource(url);
35
+
36
+ if (resource !== null && resource !== void 0 && resource.root) {
37
+ log.debug('- Serving root resource', meta);
38
+ await request.respond(resource);
39
+ } else if (resource && !disableCache) {
40
+ log.debug('- Resource cache hit', meta);
41
+ await request.respond(resource);
42
+ } else {
43
+ await request.continue();
44
+ }
45
+ } catch (error) {
46
+ log.debug(`Encountered an error handling request: ${url}`, meta);
47
+ log.debug(error);
48
+ /* istanbul ignore next: race condition */
49
+
50
+ await request.abort(error).catch(e => log.debug(e, meta));
51
+ }
52
+ };
53
+ }
54
+
55
+ function createRequestFinishedHandler(network, {
56
+ enableJavaScript,
57
+ allowedHostnames,
58
+ disableCache,
59
+ getResource,
60
+ saveResource
61
+ }) {
62
+ let log = (0, _logger.default)('core:discovery');
63
+ return async request => {
64
+ let origin = request.redirectChain[0] || request;
65
+ let url = (0, _utils.normalizeURL)(origin.url);
66
+ let meta = { ...network.meta,
67
+ url
68
+ };
69
+
70
+ try {
71
+ var _resource;
72
+
73
+ let resource = getResource(url); // process and cache the response and resource
74
+
75
+ if (!((_resource = resource) !== null && _resource !== void 0 && _resource.root) && (!resource || disableCache)) {
76
+ let response = request.response;
77
+ let capture = response && (0, _utils.hostnameMatches)(allowedHostnames, url);
78
+ let body = capture && (await response.buffer());
79
+ log.debug(`Processing resource: ${url}`, meta);
80
+ /* istanbul ignore next: sanity check */
81
+
82
+ if (!response) {
83
+ return log.debug('- Skipping no response', meta);
84
+ } else if (!capture) {
85
+ return log.debug('- Skipping remote resource', meta);
86
+ } else if (!body.length) {
87
+ return log.debug('- Skipping empty response', meta);
88
+ } else if (body.length > MAX_RESOURCE_SIZE) {
89
+ return log.debug('- Skipping resource larger than 15MB', meta);
90
+ } else if (!ALLOWED_STATUSES.includes(response.status)) {
91
+ return log.debug(`- Skipping disallowed status [${response.status}]`, meta);
92
+ } else if (!enableJavaScript && !ALLOWED_RESOURCES.includes(request.type)) {
93
+ return log.debug(`- Skipping disallowed resource type [${request.type}]`, meta);
94
+ }
95
+
96
+ resource = (0, _utils.createResource)(url, body, response.mimeType, {
97
+ status: response.status,
98
+ // 'Network.responseReceived' returns headers split by newlines, however
99
+ // `Fetch.fulfillRequest` (used for cached responses) will hang with newlines.
100
+ headers: Object.entries(response.headers).reduce((norm, [key, value]) => Object.assign(norm, {
101
+ [key]: value.split('\n')
102
+ }), {})
103
+ });
104
+ log.debug(`- sha: ${resource.sha}`, meta);
105
+ log.debug(`- mimetype: ${resource.mimetype}`, meta);
106
+ }
107
+
108
+ saveResource(resource);
109
+ } catch (error) {
110
+ log.debug(`Encountered an error processing resource: ${url}`, meta);
111
+ log.debug(error);
112
+ }
113
+ };
114
+ }
115
+
116
+ function createRequestFailedHandler(network) {
117
+ let log = (0, _logger.default)('core:discovery');
118
+ return ({
119
+ url,
120
+ error
121
+ }) => {
122
+ // do not log generic failures since the real error was most likely
123
+ // already logged from elsewhere
124
+ if (error !== 'net::ERR_FAILED') {
125
+ log.debug(`Request failed for ${url}: ${error}`, { ...network.meta,
126
+ url
127
+ });
128
+ }
129
+ };
130
+ }
package/dist/index.js CHANGED
@@ -1,29 +1,15 @@
1
1
  "use strict";
2
2
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- var _exportNames = {};
7
- Object.defineProperty(exports, "default", {
8
- enumerable: true,
9
- get: function () {
10
- return _percy.default;
11
- }
12
- });
3
+ const PercyConfig = require('@percy/config');
13
4
 
14
- var _percy = _interopRequireDefault(require("./percy"));
5
+ const CoreConfig = require('./config');
15
6
 
16
- var _resources = require("./utils/resources");
7
+ const {
8
+ Percy
9
+ } = require('./percy');
17
10
 
18
- Object.keys(_resources).forEach(function (key) {
19
- if (key === "default" || key === "__esModule") return;
20
- if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
21
- Object.defineProperty(exports, key, {
22
- enumerable: true,
23
- get: function () {
24
- return _resources[key];
25
- }
26
- });
27
- });
11
+ PercyConfig.addSchema(CoreConfig.schemas);
12
+ PercyConfig.addMigration(CoreConfig.migrations); // export the Percy class with commonjs compatibility
28
13
 
29
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
+ module.exports = Percy;
15
+ module.exports.Percy = Percy;