@percy/core
Advanced tools
Comparing version 1.0.0-beta.75 to 1.0.0-beta.76
@@ -20,3 +20,3 @@ "use strict"; | ||
var _server = _interopRequireDefault(require("./server")); | ||
var _api = require("./api"); | ||
@@ -143,3 +143,3 @@ var _snapshot = require("./snapshot"); | ||
yield (_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.listen(this.port); // mark instance as started | ||
yield (_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.listen(); // mark instance as started | ||
@@ -280,4 +280,3 @@ this.log.info('Percy has started!'); | ||
if (server) { | ||
this.server = (0, _server.default)(this); | ||
this.port = port; | ||
this.server = (0, _api.createPercyServer)(this, port); | ||
} | ||
@@ -293,3 +292,5 @@ } // Shortcut for controlling the global logger's log level. | ||
address() { | ||
return `http://localhost:${this.port}`; | ||
var _this$server4; | ||
return (_this$server4 = this.server) === null || _this$server4 === void 0 ? void 0 : _this$server4.address(); | ||
} // Set client & environment info, and override loaded config options | ||
@@ -410,3 +411,3 @@ | ||
let failed = ((_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.status) === 422 && error.response.body.errors.find(e => { | ||
let failed = ((_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.statusCode) === 422 && error.response.body.errors.find(e => { | ||
var _e$source; | ||
@@ -413,0 +414,0 @@ |
@@ -6,191 +6,467 @@ "use strict"; | ||
}); | ||
exports.createPercyServer = createPercyServer; | ||
exports.createServer = createServer; | ||
exports.default = void 0; | ||
exports.default = exports.ServerResponse = exports.ServerError = exports.Server = exports.IncomingMessage = void 0; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _path = _interopRequireDefault(require("path")); | ||
var _http = _interopRequireDefault(require("http")); | ||
var _ws = require("ws"); | ||
var _ws = _interopRequireDefault(require("ws")); | ||
var _logger = _interopRequireDefault(require("@percy/logger")); | ||
var _mimeTypes = _interopRequireDefault(require("mime-types")); | ||
var _package = _interopRequireDefault(require("../package.json")); | ||
var _contentDisposition = _interopRequireDefault(require("content-disposition")); | ||
var _pathToRegexp = require("path-to-regexp"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
async function getReply({ | ||
version, | ||
routes | ||
}, request, response) { | ||
var _headers$ContentType, _body$length, _body; | ||
function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); } | ||
let [url] = request.url.split('?'); | ||
let route = routes[url] || routes.default; | ||
let reply; // cors preflight | ||
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); } | ||
if (request.method === 'OPTIONS') { | ||
reply = [204, {}]; | ||
reply[1]['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS'; | ||
reply[1]['Access-Control-Request-Headers'] = 'Vary'; | ||
let allowed = request.headers['access-control-request-headers']; | ||
if (allowed !== null && allowed !== void 0 && allowed.length) reply[1]['Access-Control-Allow-Headers'] = allowed; | ||
} else { | ||
reply = await Promise.resolve().then(() => { | ||
var _routes$middleware; | ||
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } } | ||
return (_routes$middleware = routes.middleware) === null || _routes$middleware === void 0 ? void 0 : _routes$middleware.call(routes, request, response); | ||
}).then(() => route === null || route === void 0 ? void 0 : route(request, response)).catch(routes.catch); | ||
} // response was handled | ||
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); } | ||
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } | ||
if (response.headersSent) return []; // default 404 when reply is not an array | ||
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; } | ||
let [status, headers, body] = Array.isArray(reply) ? reply : [404, {}]; // support content-type header shortcut | ||
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; } | ||
if (typeof headers === 'string') headers = { | ||
'Content-Type': headers | ||
}; // auto stringify json | ||
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); } | ||
if ((_headers$ContentType = headers['Content-Type']) !== null && _headers$ContentType !== void 0 && _headers$ContentType.includes('json')) body = JSON.stringify(body); // add additional headers | ||
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } } | ||
headers['Content-Length'] = (_body$length = (_body = body) === null || _body === void 0 ? void 0 : _body.length) !== null && _body$length !== void 0 ? _body$length : 0; // cors headers | ||
// custom incoming message adds a `url` and `body` properties containing the parsed URL and message | ||
// buffer respectively; both available after the 'end' event is emitted | ||
class IncomingMessage extends _http.default.IncomingMessage { | ||
constructor(socket) { | ||
let buffer = []; | ||
super(socket).on('data', d => buffer.push(d)).on('end', () => { | ||
var _this$headers$content; | ||
headers['Access-Control-Expose-Headers'] = 'X-Percy-Core-Version'; | ||
headers['Access-Control-Allow-Origin'] = '*'; // version header | ||
this.url = new URL(this.url, `http://${this.headers.host}`); | ||
if (buffer.length) this.body = Buffer.concat(buffer); | ||
headers['X-Percy-Core-Version'] = version; | ||
return [status, headers, body]; | ||
} | ||
if (this.body && (_this$headers$content = this.headers['content-type']) !== null && _this$headers$content !== void 0 && _this$headers$content.includes('json')) { | ||
try { | ||
this.body = JSON.parse(this.body); | ||
} catch {} | ||
} | ||
}); | ||
} | ||
function createServer(routes) { | ||
let context = { | ||
version: _package.default.version, | ||
} // custom server response adds additional convenience methods | ||
get listening() { | ||
return context.server.listening; | ||
exports.IncomingMessage = IncomingMessage; | ||
class ServerResponse extends _http.default.ServerResponse { | ||
// responds with a status, headers, and body; the second argument can be an content-type string, | ||
// or a headers object, with content-length being automatically set when a `body` is provided | ||
send(status, headers, body) { | ||
if (typeof headers === 'string') { | ||
this.setHeader('Content-Type', headers); | ||
headers = null; | ||
} | ||
}; // create a simple server to route request responses | ||
if (body != null && !this.hasHeader('Content-Length')) { | ||
this.setHeader('Content-Length', Buffer.byteLength(body)); | ||
} | ||
context.routes = routes; | ||
context.server = _http.default.createServer((request, response) => { | ||
request.params = new URLSearchParams(request.url.split('?')[1]); | ||
request.on('data', chunk => { | ||
request.body = (request.body || '') + chunk; | ||
return this.writeHead(status, headers).end(body); | ||
} // responds with a status and content with a plain/text content-type | ||
text(status, content) { | ||
if (arguments.length < 2) [status, content] = [200, status]; | ||
return this.send(status, 'text/plain', content.toString()); | ||
} // responds with a status and stringified `data` with a json content-type | ||
json(status, data) { | ||
if (arguments.length < 2) [status, data] = [200, status]; | ||
return this.send(status, 'application/json', JSON.stringify(data)); | ||
} // responds with a status and streams a file with appropriate headers | ||
file(status, filepath) { | ||
if (arguments.length < 2) [status, filepath] = [200, status]; | ||
filepath = _path.default.resolve(filepath); | ||
let { | ||
size | ||
} = _fs.default.lstatSync(filepath); | ||
let range = parseByteRange(this.req.headers.range, size); // support simple range requests | ||
if (this.req.headers.range) { | ||
let byteRange = range ? `${range.start}-${range.end}` : '*'; | ||
this.setHeader('Content-Range', `bytes ${byteRange}/${size}`); | ||
if (!range) return this.send(416); | ||
} | ||
this.writeHead(range ? 206 : status, { | ||
'Accept-Ranges': 'bytes', | ||
'Content-Type': _mimeTypes.default.contentType(_path.default.extname(filepath)), | ||
'Content-Length': range ? range.end - range.start + 1 : size, | ||
'Content-Disposition': (0, _contentDisposition.default)(filepath, { | ||
type: 'inline' | ||
}) | ||
}); | ||
request.on('end', async () => { | ||
_fs.default.createReadStream(filepath, range).pipe(this); | ||
return this; | ||
} | ||
} // custom server error with a status and default reason | ||
exports.ServerResponse = ServerResponse; | ||
class ServerError extends Error { | ||
static throw(status, reason) { | ||
throw new this(status, reason); | ||
} | ||
constructor(status = 500, reason) { | ||
super(reason || _http.default.STATUS_CODES[status]); | ||
this.status = status; | ||
} | ||
} // custom server class handles routing requests and provides alternate methods and properties | ||
exports.ServerError = ServerError; | ||
var _sockets = /*#__PURE__*/new WeakMap(); | ||
var _defaultPort = /*#__PURE__*/new WeakMap(); | ||
var _up = /*#__PURE__*/new WeakMap(); | ||
var _handleUpgrade = /*#__PURE__*/new WeakSet(); | ||
var _routes = /*#__PURE__*/new WeakMap(); | ||
var _route = /*#__PURE__*/new WeakSet(); | ||
var _handleRequest = /*#__PURE__*/new WeakSet(); | ||
class Server extends _http.default.Server { | ||
constructor({ | ||
port | ||
} = {}) { | ||
super({ | ||
IncomingMessage, | ||
ServerResponse | ||
}); | ||
_classPrivateMethodInitSpec(this, _handleRequest); | ||
_classPrivateMethodInitSpec(this, _route); | ||
_classPrivateMethodInitSpec(this, _handleUpgrade); | ||
_classPrivateFieldInitSpec(this, _sockets, { | ||
writable: true, | ||
value: new Set() | ||
}); | ||
_classPrivateFieldInitSpec(this, _defaultPort, { | ||
writable: true, | ||
value: void 0 | ||
}); | ||
_classPrivateFieldInitSpec(this, _up, { | ||
writable: true, | ||
value: [] | ||
}); | ||
_classPrivateFieldInitSpec(this, _routes, { | ||
writable: true, | ||
value: [{ | ||
priority: -1, | ||
handle: (req, res, next) => { | ||
res.setHeader('Access-Control-Allow-Origin', '*'); | ||
if (req.method === 'OPTIONS') { | ||
let allowHeaders = req.headers['access-control-request-headers'] || '*'; | ||
let allowMethods = [...new Set(_classPrivateFieldGet(this, _routes).flatMap(route => (!route.match || route.match(req.url.pathname)) && route.methods || []))].join(', '); | ||
res.setHeader('Access-Control-Allow-Headers', allowHeaders); | ||
res.setHeader('Access-Control-Allow-Methods', allowMethods); | ||
res.writeHead(204).end(); | ||
} else { | ||
res.setHeader('Access-Control-Expose-Headers', '*'); | ||
return next(); | ||
} | ||
} | ||
}, { | ||
priority: 3, | ||
handle: req => ServerError.throw(404) | ||
}] | ||
}); | ||
_classPrivateFieldSet(this, _defaultPort, port); // handle requests on end | ||
this.on('request', (req, res) => { | ||
req.on('end', () => _classPrivateMethodGet(this, _handleRequest, _handleRequest2).call(this, req, res)); | ||
}); // handle websocket upgrades | ||
this.on('upgrade', (req, sock, head) => { | ||
_classPrivateMethodGet(this, _handleUpgrade, _handleUpgrade2).call(this, req, sock, head); | ||
}); // track open connections to terminate when the server closes | ||
this.on('connection', socket => { | ||
let handleClose = () => _classPrivateFieldGet(this, _sockets).delete(socket); | ||
_classPrivateFieldGet(this, _sockets).add(socket.on('close', handleClose)); | ||
}); | ||
} // return the listening port or any default port | ||
get port() { | ||
var _super$address$port, _super$address; | ||
return (_super$address$port = (_super$address = super.address()) === null || _super$address === void 0 ? void 0 : _super$address.port) !== null && _super$address$port !== void 0 ? _super$address$port : _classPrivateFieldGet(this, _defaultPort); | ||
} // return a string representation of the server address | ||
address() { | ||
let port = this.port; | ||
let host = 'http://localhost'; | ||
return port ? `${host}:${port}` : host; | ||
} // return a promise that resolves when the server is listening | ||
listen(port = _classPrivateFieldGet(this, _defaultPort)) { | ||
return new Promise((resolve, reject) => { | ||
let handle = err => off() && err ? reject(err) : resolve(this); | ||
let off = () => this.off('error', handle).off('listening', handle); | ||
super.listen(port, handle).once('error', handle); | ||
}); | ||
} // return a promise that resolves when the server closes | ||
close() { | ||
return new Promise(resolve => { | ||
_classPrivateFieldGet(this, _sockets).forEach(socket => socket.destroy()); | ||
super.close(resolve); | ||
}); | ||
} // handle websocket upgrades | ||
websocket(pathname, handle) { | ||
if (!handle) [pathname, handle] = [null, pathname]; | ||
_classPrivateFieldGet(this, _up).push({ | ||
match: pathname && (0, _pathToRegexp.match)(pathname), | ||
handle: (req, sock, head) => new Promise(resolve => { | ||
let wss = new _ws.default.Server({ | ||
noServer: true, | ||
clientTracking: false | ||
}); | ||
wss.handleUpgrade(req, sock, head, resolve); | ||
}).then(ws => handle(ws, req)) | ||
}); | ||
if (pathname) { | ||
_classPrivateFieldGet(this, _up).sort((a, b) => (a.match ? -1 : 1) - (b.match ? -1 : 1)); | ||
} | ||
return this; | ||
} | ||
// set request routing and handling for pathnames and methods | ||
route(method, pathname, handle) { | ||
if (arguments.length === 1) [handle, method] = [method]; | ||
if (arguments.length === 2) [handle, pathname] = [pathname]; | ||
if (arguments.length === 2 && !Array.isArray(method) && method[0] === '/') [pathname, method] = [method]; | ||
return _classPrivateMethodGet(this, _route, _route2).call(this, { | ||
priority: !pathname ? 0 : !method ? 1 : 2, | ||
methods: method && [].concat(method).map(m => m.toUpperCase()), | ||
match: pathname && (0, _pathToRegexp.match)(pathname), | ||
handle | ||
}); | ||
} // install a route that serves requested files from the provided directory | ||
serve(pathname, directory, options) { | ||
var _options; | ||
if (typeof directory !== 'string') [options, directory] = [directory]; | ||
if (!directory) [pathname, directory] = ['/', pathname]; | ||
let root = _path.default.resolve(directory); | ||
let mountPattern = (0, _pathToRegexp.pathToRegexp)(pathname, null, { | ||
end: false | ||
}); | ||
let rewritePath = createRewriter((_options = options) === null || _options === void 0 ? void 0 : _options.rewrites, (pathname, rewrite) => { | ||
try { | ||
request.body = JSON.parse(request.body); | ||
} catch (e) {} | ||
let filepath = decodeURIComponent(pathname.replace(mountPattern, '')); | ||
if (!isPathInside(root, filepath)) ServerError.throw(); | ||
return rewrite(filepath); | ||
} catch { | ||
throw new ServerError(400); | ||
} | ||
}); | ||
return _classPrivateMethodGet(this, _route, _route2).call(this, { | ||
priority: 2, | ||
methods: ['GET'], | ||
match: pathname => mountPattern.test(pathname), | ||
handle: async (req, res, next) => { | ||
try { | ||
var _options2; | ||
let [status, headers, body] = await getReply(context, request, response); | ||
if (!response.headersSent) response.writeHead(status, headers).end(body); | ||
let pathname = rewritePath(req.url.pathname); | ||
let file = await getFile(root, pathname, (_options2 = options) === null || _options2 === void 0 ? void 0 : _options2.cleanUrls); | ||
if (!(file !== null && file !== void 0 && file.stats.isFile())) return await next(); | ||
return res.file(file.path); | ||
} catch (err) { | ||
let statusPage = _path.default.join(root, `${err.status}.html`); | ||
if (!_fs.default.existsSync(statusPage)) throw err; | ||
return res.file(err.status, statusPage); | ||
} | ||
} | ||
}); | ||
}); // track connections | ||
} // route and respond to requests; handling errors if necessary | ||
context.sockets = new Set(); | ||
context.server.on('connection', s => { | ||
context.sockets.add(s.on('close', () => context.sockets.delete(s))); | ||
}); // immediately kill connections on close | ||
context.close = () => new Promise(resolve => { | ||
context.sockets.forEach(s => s.destroy()); | ||
context.server.close(resolve); | ||
}); // starts the server | ||
} // create a url rewriter from provided rewrite rules | ||
context.listen = port => new Promise((resolve, reject) => { | ||
context.server.on('listening', () => resolve(context)); | ||
context.server.on('error', reject); | ||
context.server.listen(port); | ||
}); // add routes programatically | ||
exports.Server = Server; | ||
function _handleUpgrade2(req, sock, head) { | ||
let up = _classPrivateFieldGet(this, _up).find(u => !u.match || u.match(req.url)); | ||
context.reply = (url, handler) => { | ||
routes[url] = handler; | ||
return context; | ||
}; | ||
if (up) return up.handle(req, sock, head); | ||
sock.write(`HTTP/1.1 400 ${_http.default.STATUS_CODES[400]}\r\n` + 'Connection: close\r\n\r\n'); | ||
sock.destroy(); | ||
} | ||
return context; | ||
function _route2(route) { | ||
let i = _classPrivateFieldGet(this, _routes).findIndex(r => r.priority >= route.priority); | ||
_classPrivateFieldGet(this, _routes).splice(i, 0, route); | ||
return this; | ||
} | ||
function createPercyServer(percy) { | ||
let log = (0, _logger.default)('core:server'); | ||
let context = createServer({ | ||
// healthcheck returns meta info on success | ||
'/percy/healthcheck': () => [200, 'application/json', { | ||
success: true, | ||
config: percy.config, | ||
loglevel: percy.loglevel(), | ||
build: percy.build | ||
}], | ||
// remotely get and set percy config options | ||
'/percy/config': ({ | ||
body | ||
}) => [200, 'application/json', { | ||
config: body ? percy.setConfig(body) : percy.config, | ||
success: true | ||
}], | ||
// responds when idle | ||
'/percy/idle': () => percy.idle().then(() => [200, 'application/json', { | ||
success: true | ||
}]), | ||
// serves @percy/dom as a convenience | ||
'/percy/dom.js': () => _fs.default.promises.readFile(require.resolve('@percy/dom'), 'utf-8').then(content => [200, 'applicaton/javascript', content]), | ||
// serves the new DOM library, wrapped for compatability to `@percy/agent` | ||
'/percy-agent.js': () => _fs.default.promises.readFile(require.resolve('@percy/dom'), 'utf-8').then(content => { | ||
let wrapper = '(window.PercyAgent = class PercyAgent { snapshot(n, o) { return PercyDOM.serialize(o); } });'; | ||
log.deprecated('It looks like you’re using @percy/cli with an older SDK. Please upgrade to the latest version' + ' to fix this warning. See these docs for more info: https://docs.percy.io/docs/migrating-to-percy-cli'); | ||
return [200, 'applicaton/javascript', content.concat(wrapper)]; | ||
}), | ||
// forward snapshot requests | ||
'/percy/snapshot': async ({ | ||
body, | ||
params | ||
}) => { | ||
let snapshot = percy.snapshot(body); | ||
if (!params.has('async')) await snapshot; | ||
return [200, 'application/json', { | ||
success: true | ||
}]; | ||
}, | ||
// stops the instance async at the end of the event loop | ||
'/percy/stop': () => { | ||
setImmediate(async () => await percy.stop()); | ||
return [200, 'application/json', { | ||
success: true | ||
}]; | ||
}, | ||
// other routes 404 | ||
default: () => [404, 'application/json', { | ||
error: 'Not found', | ||
success: false | ||
}], | ||
// generic error handler | ||
catch: ({ | ||
message | ||
}) => [500, 'application/json', { | ||
error: message, | ||
success: false | ||
}] | ||
}); // start a websocket server | ||
async function _handleRequest2(req, res) { | ||
var _res$req; | ||
context.wss = new _ws.Server({ | ||
noServer: true | ||
}); // manually handle upgrades to avoid wss handling all events | ||
// support node < 15.7.0 | ||
(_res$req = res.req) !== null && _res$req !== void 0 ? _res$req : res.req = req; | ||
context.server.on('upgrade', (req, sock, head) => { | ||
context.wss.handleUpgrade(req, sock, head, socket => { | ||
// allow remote logging connections | ||
let disconnect = _logger.default.connect(socket); | ||
try { | ||
// invoke routes like middleware | ||
await async function cont(routes, i = 0) { | ||
let next = () => cont(routes, i + 1); | ||
socket.once('close', () => disconnect()); | ||
}); | ||
}); | ||
return context; | ||
let { | ||
methods, | ||
match, | ||
handle | ||
} = routes[i]; | ||
let result = !methods || methods.includes(req.method); | ||
result && (result = !match || match(req.url.pathname)); | ||
if (result) req.params = result.params; | ||
return result ? handle(req, res, next) : next(); | ||
}(_classPrivateFieldGet(this, _routes)); | ||
} catch (error) { | ||
var _req$headers$accept, _req$headers$content; | ||
let { | ||
status = 500, | ||
message | ||
} = error; // fallback error handling | ||
if ((_req$headers$accept = req.headers.accept) !== null && _req$headers$accept !== void 0 && _req$headers$accept.includes('json') || (_req$headers$content = req.headers['content-type']) !== null && _req$headers$content !== void 0 && _req$headers$content.includes('json')) { | ||
res.json(status, { | ||
error: message | ||
}); | ||
} else { | ||
res.text(status, message); | ||
} | ||
} | ||
} | ||
var _default = createPercyServer; | ||
function createRewriter(rewrites = [], cb) { | ||
let normalize = p => _path.default.posix.normalize(_path.default.posix.join('/', p)); | ||
if (!Array.isArray(rewrites)) rewrites = Object.entries(rewrites); | ||
let rewrite = [{ | ||
// resolve and normalize the path before rewriting | ||
apply: p => _path.default.posix.resolve(normalize(p)) | ||
}].concat(rewrites.map(([src, dest]) => { | ||
// compile rewrite rules into functions | ||
let match = (0, _pathToRegexp.match)(normalize(src)); | ||
let toPath = (0, _pathToRegexp.compile)(normalize(dest)); | ||
return { | ||
match, | ||
apply: r => toPath(r.params) | ||
}; | ||
})).reduceRight((next, rule) => pathname => { | ||
var _rule$match, _rule$match2; | ||
// compose all rewrites into a single function | ||
let result = (_rule$match = (_rule$match2 = rule.match) === null || _rule$match2 === void 0 ? void 0 : _rule$match2.call(rule, pathname)) !== null && _rule$match !== void 0 ? _rule$match : pathname; | ||
if (result) pathname = rule.apply(result); | ||
return next(pathname); | ||
}, p => p); // allow additional pathname processing around the rewriter | ||
return p => cb(p, rewrite); | ||
} // returns true if the pathname is inside the root pathname | ||
function isPathInside(root, pathname) { | ||
let abs = _path.default.resolve(_path.default.join(root, pathname)); | ||
return !abs.lastIndexOf(root, 0) && (abs[root.length] === _path.default.sep || !abs[root.length]); | ||
} // get the absolute path and stats of a possible file | ||
async function getFile(root, pathname, cleanUrls) { | ||
for (let filename of [pathname].concat(cleanUrls ? _path.default.join(pathname, 'index.html') : [], cleanUrls && pathname.length > 2 ? pathname.replace(/\/?$/, '.html') : [])) { | ||
let filepath = _path.default.resolve(_path.default.join(root, filename)); | ||
let stats = await _fs.default.promises.lstat(filepath).catch(() => {}); | ||
if (stats !== null && stats !== void 0 && stats.isFile()) return { | ||
path: filepath, | ||
stats | ||
}; | ||
} | ||
} // returns the start and end of a byte range or undefined if unable to parse | ||
const RANGE_REGEXP = /^bytes=(\d*)?-(\d*)?(?:\b|$)/; | ||
function parseByteRange(range, size) { | ||
var _range$match; | ||
let [, start, end = size] = (_range$match = range === null || range === void 0 ? void 0 : range.match(RANGE_REGEXP)) !== null && _range$match !== void 0 ? _range$match : [0, 0, 0]; | ||
start = Math.max(parseInt(start, 10), 0); | ||
end = Math.min(parseInt(end, 10), size - 1); | ||
if (isNaN(start)) [start, end] = [size - end, size - 1]; | ||
if (start >= 0 && start < end) return { | ||
start, | ||
end | ||
}; | ||
} // include ServerError and createRewriter as static properties | ||
Server.Error = ServerError; | ||
Server.createRewriter = createRewriter; | ||
var _default = Server; | ||
exports.default = _default; |
{ | ||
"name": "@percy/core", | ||
"version": "1.0.0-beta.75", | ||
"version": "1.0.0-beta.76", | ||
"license": "MIT", | ||
@@ -33,12 +33,15 @@ "repository": { | ||
"dependencies": { | ||
"@percy/client": "1.0.0-beta.75", | ||
"@percy/config": "1.0.0-beta.75", | ||
"@percy/dom": "1.0.0-beta.75", | ||
"@percy/logger": "1.0.0-beta.75", | ||
"@percy/client": "1.0.0-beta.76", | ||
"@percy/config": "1.0.0-beta.76", | ||
"@percy/dom": "1.0.0-beta.76", | ||
"@percy/logger": "1.0.0-beta.76", | ||
"content-disposition": "^0.5.4", | ||
"cross-spawn": "^7.0.3", | ||
"extract-zip": "^2.0.1", | ||
"mime-types": "^2.1.34", | ||
"path-to-regexp": "^6.2.0", | ||
"rimraf": "^3.0.2", | ||
"ws": "^8.0.0" | ||
}, | ||
"gitHead": "3b778a9c0b72dcbf113910227f1a073a2714042a" | ||
"gitHead": "445af68d8e270e2a35fc74e26422ed5d3c91d2ae" | ||
} |
// aliased to src for coverage during tests without needing to compile this file | ||
const { createServer } = require('@percy/core/dist/server'); | ||
const { default: Server } = require('@percy/core/dist/server'); | ||
function createTestServer(routes, port = 8000) { | ||
let context = createServer(routes); | ||
function createTestServer({ default: defaultReply, ...replies }, port = 8000) { | ||
let server = new Server(); | ||
// handle route errors | ||
context.routes.catch = ({ message }) => [500, 'text/plain', message]; | ||
// track requests | ||
context.requests = []; | ||
context.routes.middleware = ({ url, body }) => { | ||
context.requests.push(body ? [url, body] : [url]); | ||
// alternate route handling | ||
let handleReply = reply => async (req, res) => { | ||
let [status, headers, body] = typeof reply === 'function' ? await reply(req) : reply; | ||
if (!Buffer.isBuffer(body) && typeof body !== 'string') body = JSON.stringify(body); | ||
return res.send(status, headers, body); | ||
}; | ||
// map replies to alternate route handlers | ||
server.reply = (p, reply) => (replies[p] = handleReply(reply)); | ||
for (let [p, reply] of Object.entries(replies)) server.reply(p, reply); | ||
if (defaultReply) defaultReply = handleReply(defaultReply); | ||
// track requests and route replies | ||
server.requests = []; | ||
server.route(async (req, res, next) => { | ||
let pathname = req.url.pathname; | ||
if (req.url.search) pathname += req.url.search; | ||
server.requests.push(req.body ? [pathname, req.body] : [pathname]); | ||
let reply = replies[req.url.pathname] || defaultReply; | ||
return reply ? await reply(req, res) : next(); | ||
}); | ||
// automatically listen | ||
return context.listen(port); | ||
return server.listen(port); | ||
}; | ||
@@ -19,0 +32,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
134914
20
2846
11
10
+ Addedcontent-disposition@^0.5.4
+ Addedmime-types@^2.1.34
+ Addedpath-to-regexp@^6.2.0
+ Added@percy/client@1.0.0-beta.76(transitive)
+ Added@percy/config@1.0.0-beta.76(transitive)
+ Added@percy/dom@1.0.0-beta.76(transitive)
+ Added@percy/env@1.0.0-beta.76(transitive)
+ Added@percy/logger@1.0.0-beta.76(transitive)
+ Addedcontent-disposition@0.5.4(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedpath-to-regexp@6.3.0(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
- Removed@percy/client@1.0.0-beta.75(transitive)
- Removed@percy/config@1.0.0-beta.75(transitive)
- Removed@percy/dom@1.0.0-beta.75(transitive)
- Removed@percy/env@1.0.0-beta.75(transitive)
- Removed@percy/logger@1.0.0-beta.75(transitive)
Updated@percy/client@1.0.0-beta.76
Updated@percy/config@1.0.0-beta.76
Updated@percy/dom@1.0.0-beta.76
Updated@percy/logger@1.0.0-beta.76