@percy/core 1.8.1 → 1.10.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 +85 -32
- package/dist/percy.js +8 -8
- package/dist/snapshot.js +14 -3
- package/package.json +6 -6
package/dist/api.js
CHANGED
|
@@ -12,6 +12,18 @@ export function createPercyServer(percy, port) {
|
|
|
12
12
|
port
|
|
13
13
|
}) // facilitate logger websocket connections
|
|
14
14
|
.websocket('/(logger)?', ws => {
|
|
15
|
+
var _percy$testing, _percy$testing2;
|
|
16
|
+
|
|
17
|
+
// support sabotaging remote logging connections in testing mode
|
|
18
|
+
if (((_percy$testing = percy.testing) === null || _percy$testing === void 0 ? void 0 : _percy$testing.remoteLogging) === false) return ws.terminate(); // track all remote logging connections in testing mode
|
|
19
|
+
|
|
20
|
+
if (percy.testing) ((_percy$testing2 = percy.testing).remoteLoggers || (_percy$testing2.remoteLoggers = new Set())).add(ws);
|
|
21
|
+
ws.addEventListener('close', () => {
|
|
22
|
+
var _percy$testing3, _percy$testing3$remot;
|
|
23
|
+
|
|
24
|
+
return (_percy$testing3 = percy.testing) === null || _percy$testing3 === void 0 ? void 0 : (_percy$testing3$remot = _percy$testing3.remoteLoggers) === null || _percy$testing3$remot === void 0 ? void 0 : _percy$testing3$remot.delete(ws);
|
|
25
|
+
}); // listen for messages with specific logging payloads
|
|
26
|
+
|
|
15
27
|
ws.addEventListener('message', ({
|
|
16
28
|
data
|
|
17
29
|
}) => {
|
|
@@ -23,13 +35,14 @@ export function createPercyServer(percy, port) {
|
|
|
23
35
|
for (let m of messages) logger.instance.messages.add(m);
|
|
24
36
|
|
|
25
37
|
if (log) logger.instance.log(...log);
|
|
26
|
-
});
|
|
38
|
+
}); // respond with the current loglevel
|
|
39
|
+
|
|
27
40
|
ws.send(JSON.stringify({
|
|
28
41
|
loglevel: logger.loglevel()
|
|
29
42
|
}));
|
|
30
43
|
}) // general middleware
|
|
31
44
|
.route((req, res, next) => {
|
|
32
|
-
var _percy$
|
|
45
|
+
var _percy$testing4, _percy$testing7, _percy$testing7$api, _percy$testing8, _percy$testing8$api;
|
|
33
46
|
|
|
34
47
|
// treat all request bodies as json
|
|
35
48
|
if (req.body) try {
|
|
@@ -38,35 +51,55 @@ export function createPercyServer(percy, port) {
|
|
|
38
51
|
|
|
39
52
|
res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version'); // skip or change api version header in testing mode
|
|
40
53
|
|
|
41
|
-
if (((_percy$
|
|
42
|
-
var _percy$
|
|
54
|
+
if (((_percy$testing4 = percy.testing) === null || _percy$testing4 === void 0 ? void 0 : _percy$testing4.version) !== false) {
|
|
55
|
+
var _percy$testing5;
|
|
43
56
|
|
|
44
|
-
res.setHeader('X-Percy-Core-Version', ((_percy$
|
|
45
|
-
} //
|
|
57
|
+
res.setHeader('X-Percy-Core-Version', ((_percy$testing5 = percy.testing) === null || _percy$testing5 === void 0 ? void 0 : _percy$testing5.version) ?? pkg.version);
|
|
58
|
+
} // track all api reqeusts in testing mode
|
|
46
59
|
|
|
47
60
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
if (percy.testing && !req.url.pathname.startsWith('/test/')) {
|
|
62
|
+
var _percy$testing6;
|
|
63
|
+
|
|
64
|
+
((_percy$testing6 = percy.testing).requests || (_percy$testing6.requests = [])).push({
|
|
65
|
+
url: `${req.url.pathname}${req.url.search}`,
|
|
66
|
+
method: req.method,
|
|
67
|
+
body: req.body
|
|
52
68
|
});
|
|
53
|
-
}
|
|
54
|
-
|
|
69
|
+
} // support sabotaging requests in testing mode
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if (((_percy$testing7 = percy.testing) === null || _percy$testing7 === void 0 ? void 0 : (_percy$testing7$api = _percy$testing7.api) === null || _percy$testing7$api === void 0 ? void 0 : _percy$testing7$api[req.url.pathname]) === 'error') {
|
|
73
|
+
next = () => {
|
|
74
|
+
var _percy$testing$build;
|
|
75
|
+
|
|
76
|
+
return Promise.reject(new Error(((_percy$testing$build = percy.testing.build) === null || _percy$testing$build === void 0 ? void 0 : _percy$testing$build.error) || 'testing'));
|
|
77
|
+
};
|
|
78
|
+
} else if (((_percy$testing8 = percy.testing) === null || _percy$testing8 === void 0 ? void 0 : (_percy$testing8$api = _percy$testing8.api) === null || _percy$testing8$api === void 0 ? void 0 : _percy$testing8$api[req.url.pathname]) === 'disconnect') {
|
|
79
|
+
next = () => req.connection.destroy();
|
|
55
80
|
} // return json errors
|
|
56
81
|
|
|
57
82
|
|
|
58
|
-
return next().catch(e =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
return next().catch(e => {
|
|
84
|
+
var _percy$testing9;
|
|
85
|
+
|
|
86
|
+
return res.json(e.status ?? 500, {
|
|
87
|
+
build: ((_percy$testing9 = percy.testing) === null || _percy$testing9 === void 0 ? void 0 : _percy$testing9.build) || percy.build,
|
|
88
|
+
error: e.message,
|
|
89
|
+
success: false
|
|
90
|
+
});
|
|
91
|
+
});
|
|
63
92
|
}) // healthcheck returns basic information
|
|
64
|
-
.route('get', '/percy/healthcheck', (req, res) =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
.route('get', '/percy/healthcheck', (req, res) => {
|
|
94
|
+
var _percy$testing10;
|
|
95
|
+
|
|
96
|
+
return res.json(200, {
|
|
97
|
+
build: ((_percy$testing10 = percy.testing) === null || _percy$testing10 === void 0 ? void 0 : _percy$testing10.build) ?? percy.build,
|
|
98
|
+
loglevel: percy.loglevel(),
|
|
99
|
+
config: percy.config,
|
|
100
|
+
success: true
|
|
101
|
+
});
|
|
102
|
+
}) // get or set config options
|
|
70
103
|
.route(['get', 'post'], '/percy/config', async (req, res) => res.json(200, {
|
|
71
104
|
config: req.body ? await percy.setConfig(req.body) : percy.config,
|
|
72
105
|
success: true
|
|
@@ -105,28 +138,46 @@ export function createPercyServer(percy, port) {
|
|
|
105
138
|
}
|
|
106
139
|
}, res) => {
|
|
107
140
|
body = Buffer.isBuffer(body) ? body.toString() : body;
|
|
141
|
+
let {
|
|
142
|
+
remoteLoggers
|
|
143
|
+
} = percy.testing;
|
|
108
144
|
|
|
109
145
|
if (cmd === 'reset') {
|
|
110
146
|
// the reset command will reset testing mode and clear any logs
|
|
111
|
-
percy.testing = {
|
|
147
|
+
percy.testing = remoteLoggers ? {
|
|
148
|
+
remoteLoggers
|
|
149
|
+
} : {};
|
|
112
150
|
logger.instance.messages.clear();
|
|
113
151
|
} else if (cmd === 'version') {
|
|
114
152
|
// the version command will update the api version header for testing
|
|
115
153
|
percy.testing.version = body;
|
|
116
154
|
} else if (cmd === 'error' || cmd === 'disconnect') {
|
|
155
|
+
var _percy$testing11;
|
|
156
|
+
|
|
117
157
|
// the error or disconnect commands will cause specific endpoints to fail
|
|
118
|
-
percy.testing.api = {
|
|
119
|
-
|
|
158
|
+
((_percy$testing11 = percy.testing).api || (_percy$testing11.api = {}))[body] = cmd;
|
|
159
|
+
} else if (cmd === 'build-failure') {
|
|
160
|
+
// the build-failure command will cause api errors to include a failed build
|
|
161
|
+
percy.testing.build = {
|
|
162
|
+
failed: true,
|
|
163
|
+
error: 'Build failed'
|
|
120
164
|
};
|
|
165
|
+
} else if (cmd === 'remote-logging') {
|
|
166
|
+
// the remote-logging command will toggle remote logging support
|
|
167
|
+
if (body === false) remoteLoggers === null || remoteLoggers === void 0 ? void 0 : remoteLoggers.forEach(ws => ws.terminate());
|
|
168
|
+
percy.testing.remoteLogging = body;
|
|
121
169
|
} else {
|
|
122
170
|
// 404 for unknown commands
|
|
123
171
|
return res.send(404);
|
|
124
172
|
}
|
|
125
173
|
|
|
126
174
|
return res.json(200, {
|
|
127
|
-
|
|
175
|
+
success: true
|
|
128
176
|
});
|
|
129
|
-
}) // returns an array of raw
|
|
177
|
+
}) // returns an array of raw requests made to the api
|
|
178
|
+
.route('get', '/test/requests', (req, res) => res.json(200, {
|
|
179
|
+
requests: percy.testing.requests
|
|
180
|
+
})) // returns an array of raw logs from the logger
|
|
130
181
|
.route('get', '/test/logs', (req, res) => res.json(200, {
|
|
131
182
|
logs: Array.from(logger.instance.messages)
|
|
132
183
|
})) // serves a very basic html page for testing snapshots
|
|
@@ -138,15 +189,17 @@ export function createPercyServer(percy, port) {
|
|
|
138
189
|
export function createStaticServer(options) {
|
|
139
190
|
let {
|
|
140
191
|
serve: dir,
|
|
141
|
-
baseUrl = '
|
|
192
|
+
baseUrl = ''
|
|
142
193
|
} = options;
|
|
143
|
-
let server = Server.createServer(options); //
|
|
194
|
+
let server = Server.createServer(options); // remove trailing slashes so the base snapshot name matches other snapshots
|
|
195
|
+
|
|
196
|
+
baseUrl = baseUrl.replace(/\/$/, ''); // used when generating an automatic sitemap
|
|
144
197
|
|
|
145
198
|
let toURL = Server.createRewriter( // reverse rewrites' src, dest, & order
|
|
146
|
-
Object.entries((options === null || options === void 0 ? void 0 : options.rewrites) ?? {}).reduce((acc, rw) => [rw.reverse(), ...acc], []), (filename, rewrite) => new URL(path.posix.join(baseUrl, // cleanUrls will trim trailing .html/index.html from paths
|
|
199
|
+
Object.entries((options === null || options === void 0 ? void 0 : options.rewrites) ?? {}).reduce((acc, rw) => [rw.reverse(), ...acc], []), (filename, rewrite) => new URL(path.posix.join('/', baseUrl, // cleanUrls will trim trailing .html/index.html from paths
|
|
147
200
|
!options.cleanUrls ? rewrite(filename) : rewrite(filename).replace(/(\/index)?\.html$/, '')), server.address())); // include automatic sitemap route
|
|
148
201
|
|
|
149
|
-
server.route('get',
|
|
202
|
+
server.route('get', `${baseUrl}/sitemap.xml`, async (req, res) => {
|
|
150
203
|
let {
|
|
151
204
|
default: glob
|
|
152
205
|
} = await import('fast-glob');
|
package/dist/percy.js
CHANGED
|
@@ -31,7 +31,9 @@ export class Percy {
|
|
|
31
31
|
deferUploads,
|
|
32
32
|
// run without uploading anything
|
|
33
33
|
skipUploads,
|
|
34
|
-
//
|
|
34
|
+
// run without asset discovery
|
|
35
|
+
skipDiscovery,
|
|
36
|
+
// implies `skipUploads` and `skipDiscovery`
|
|
35
37
|
dryRun,
|
|
36
38
|
// implies `dryRun`, silent logs, and adds extra api endpoints
|
|
37
39
|
testing,
|
|
@@ -53,6 +55,7 @@ export class Percy {
|
|
|
53
55
|
this.testing = testing ? {} : null;
|
|
54
56
|
this.dryRun = !!testing || !!dryRun;
|
|
55
57
|
this.skipUploads = this.dryRun || !!skipUploads;
|
|
58
|
+
this.skipDiscovery = this.dryRun || !!skipDiscovery;
|
|
56
59
|
this.delayUploads = this.skipUploads || !!delayUploads;
|
|
57
60
|
this.deferUploads = this.delayUploads || !!deferUploads;
|
|
58
61
|
if (this.deferUploads) this.#uploads.stop();
|
|
@@ -150,7 +153,7 @@ export class Percy {
|
|
|
150
153
|
// at a later time when uploads are deferred, or run immediately when not deferred.
|
|
151
154
|
|
|
152
155
|
|
|
153
|
-
async *start(
|
|
156
|
+
async *start() {
|
|
154
157
|
// already starting or started
|
|
155
158
|
if (this.readyState != null) return;
|
|
156
159
|
this.readyState = 0; // create a percy build as the first immediately queued task
|
|
@@ -193,10 +196,7 @@ export class Percy {
|
|
|
193
196
|
// when not deferred, wait until the build is created first
|
|
194
197
|
if (!this.deferUploads) await buildTask; // maybe launch the discovery browser
|
|
195
198
|
|
|
196
|
-
if (!this.
|
|
197
|
-
yield this.browser.launch();
|
|
198
|
-
} // start the server after everything else is ready
|
|
199
|
-
|
|
199
|
+
if (!this.skipDiscovery) yield this.browser.launch(); // start the server after everything else is ready
|
|
200
200
|
|
|
201
201
|
yield (_this$server2 = this.server) === null || _this$server2 === void 0 ? void 0 : _this$server2.listen(); // mark instance as started
|
|
202
202
|
|
|
@@ -234,8 +234,8 @@ export class Percy {
|
|
|
234
234
|
if (this.#snapshots.size) {
|
|
235
235
|
if (close) this.#snapshots.close();
|
|
236
236
|
yield* this.#snapshots.flush(s => {
|
|
237
|
-
// do not log a count when not closing or
|
|
238
|
-
if (!close || this.
|
|
237
|
+
// do not log a count when not closing or if asset discovery is disabled
|
|
238
|
+
if (!close || this.skipDiscovery) return;
|
|
239
239
|
this.log.progress(`Processing ${s} snapshot${s !== 1 ? 's' : ''}...`, !!s);
|
|
240
240
|
});
|
|
241
241
|
} // run, close, and wait for the upload queue to empty
|
package/dist/snapshot.js
CHANGED
|
@@ -117,6 +117,8 @@ export async function* gatherSnapshots(percy, options) {
|
|
|
117
117
|
// other invalid options which are also scrubbed from the returned migrated options.
|
|
118
118
|
|
|
119
119
|
export function validateSnapshotOptions(options) {
|
|
120
|
+
var _migrated$baseUrl;
|
|
121
|
+
|
|
120
122
|
// decide which schema to validate against
|
|
121
123
|
let schema = ['domSnapshot', 'dom-snapshot', 'dom_snapshot'].some(k => k in options) && '/snapshot/dom' || 'url' in options && '/snapshot' || 'sitemap' in options && '/snapshot/sitemap' || 'serve' in options && '/snapshot/server' || 'snapshots' in options && '/snapshot/list' || '/snapshot';
|
|
122
124
|
let {
|
|
@@ -125,10 +127,12 @@ export function validateSnapshotOptions(options) {
|
|
|
125
127
|
environmentInfo,
|
|
126
128
|
snapshots,
|
|
127
129
|
...migrated
|
|
128
|
-
} = PercyConfig.migrate(options, schema); //
|
|
130
|
+
} = PercyConfig.migrate(options, schema); // maintain a trailing slash for base URLs to normalize them
|
|
131
|
+
|
|
132
|
+
if (((_migrated$baseUrl = migrated.baseUrl) === null || _migrated$baseUrl === void 0 ? void 0 : _migrated$baseUrl.endsWith('/')) === false) migrated.baseUrl += '/';
|
|
133
|
+
let baseUrl = schema === '/snapshot/server' ? 'http://localhost/' : migrated.baseUrl; // gather info for validating individual snapshot URLs
|
|
129
134
|
|
|
130
135
|
let isSnapshot = schema === '/snapshot/dom' || schema === '/snapshot';
|
|
131
|
-
let baseUrl = schema === '/snapshot/server' ? 'http://localhost' : options.baseUrl;
|
|
132
136
|
let snaps = isSnapshot ? [migrated] : Array.isArray(snapshots) ? snapshots : [];
|
|
133
137
|
|
|
134
138
|
for (let snap of snaps) validURL(typeof snap === 'string' ? snap : snap.url, baseUrl); // add back snapshots before validating and scrubbing; function snapshots are validated later
|
|
@@ -370,7 +374,14 @@ export async function* discoverSnapshotResources(percy, snapshot, callback) {
|
|
|
370
374
|
|
|
371
375
|
let cache = percy[RESOURCE_CACHE_KEY] || (percy[RESOURCE_CACHE_KEY] = new Map()); // preload the root resource for existing dom snapshots
|
|
372
376
|
|
|
373
|
-
let resources = new Map(snapshot.domSnapshot && [createRootResource(snapshot.url, snapshot.domSnapshot)].map(resource => [resource.url, resource])); //
|
|
377
|
+
let resources = new Map(snapshot.domSnapshot && [createRootResource(snapshot.url, snapshot.domSnapshot)].map(resource => [resource.url, resource])); // when no discovery browser is available, do not attempt to discover other resources
|
|
378
|
+
|
|
379
|
+
if (percy.skipDiscovery && !snapshot.domSnapshot) {
|
|
380
|
+
throw new Error('Cannot capture DOM snapshot when asset discovery is disabled');
|
|
381
|
+
} else if (percy.skipDiscovery) {
|
|
382
|
+
return handleSnapshotResources(snapshot, resources, callback);
|
|
383
|
+
} // open a new browser page
|
|
384
|
+
|
|
374
385
|
|
|
375
386
|
let page = yield percy.browser.page({
|
|
376
387
|
enableJavaScript: snapshot.enableJavaScript ?? !snapshot.domSnapshot,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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.
|
|
43
|
-
"@percy/config": "1.
|
|
44
|
-
"@percy/dom": "1.
|
|
45
|
-
"@percy/logger": "1.
|
|
42
|
+
"@percy/client": "1.10.0",
|
|
43
|
+
"@percy/config": "1.10.0",
|
|
44
|
+
"@percy/dom": "1.10.0",
|
|
45
|
+
"@percy/logger": "1.10.0",
|
|
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": "
|
|
56
|
+
"gitHead": "a6934eda4fc3b84845ae606d7f5a901f25e0a56f"
|
|
57
57
|
}
|