Comparing version 0.4.9 to 0.5.0
@@ -1,1 +0,1 @@ | ||
{"version":"0.4.9","commands":{"exec":{"id":"exec","description":"Start and stop Percy around a supplied command.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy exec -- echo \"percy is running around this echo command\"","$ percy exec -- bash -c \"echo foo && echo bar\""],"flags":{"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"finalize":{"id":"finalize","description":"Finalize a build. Commonly used for parallelized builds, especially when the number of parallelized processes is unknown.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy finalize --all\n[percy] Finalized parallel build."],"flags":{"all":{"name":"all","type":"boolean","char":"a","required":true,"allowNo":false}},"args":[]},"health-check":{"id":"health-check","description":"Determines if the Percy Agent process is currently running","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy healthcheck","$ percy healthcheck --port 6884"],"flags":{"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"percy-command":{"id":"percy-command","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"flags":{},"args":[]},"snapshot":{"id":"snapshot","description":"Snapshot a directory containing a pre-built static website.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy snapshot _site/","$ percy snapshot _site/ --base-url \"/blog\"","$ percy snapshot _site/ --ignore-files \"/blog/drafts/**\""],"flags":{"snapshot-files":{"name":"snapshot-files","type":"option","char":"s","description":"Glob or comma-seperated string of globs for matching the files and directories to snapshot.","default":"**/*.html,**/*.htm"},"ignore-files":{"name":"ignore-files","type":"option","char":"i","description":"Glob or comma-seperated string of globs for matching the files and directories to ignore.","default":""},"base-url":{"name":"base-url","type":"option","char":"b","description":"If your static files will be hosted in a subdirectory, instead \nof the webserver's root path, set that subdirectory with this flag.","default":"/"},"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"Asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"Port","default":5338}},"args":[{"name":"snapshotDirectory","description":"A path to the directory you would like to snapshot","required":true}]},"start":{"id":"start","description":"Starts the percy process.","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy start\ninfo: percy has started on port 5338."],"flags":{"detached":{"name":"detached","type":"boolean","char":"d","description":"start as a detached process","allowNo":false},"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"stop":{"id":"stop","description":"Stops the percy process.","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy stop\ninfo: percy has stopped."],"flags":{"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]}}} | ||
{"version":"0.5.0","commands":{"exec":{"id":"exec","description":"Start and stop Percy around a supplied command.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy exec -- echo \"percy is running around this echo command\"","$ percy exec -- bash -c \"echo foo && echo bar\""],"flags":{"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"finalize":{"id":"finalize","description":"Finalize a build. Commonly used for parallelized builds, especially when the number of parallelized processes is unknown.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy finalize --all\n[percy] Finalized parallel build."],"flags":{"all":{"name":"all","type":"boolean","char":"a","required":true,"allowNo":false}},"args":[]},"health-check":{"id":"health-check","description":"Determines if the Percy Agent process is currently running","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy healthcheck","$ percy healthcheck --port 6884"],"flags":{"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"percy-command":{"id":"percy-command","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"flags":{},"args":[]},"snapshot":{"id":"snapshot","description":"Snapshot a directory containing a pre-built static website.","pluginName":"percy","pluginType":"core","hidden":false,"aliases":[],"examples":["$ percy snapshot _site/","$ percy snapshot _site/ --base-url \"/blog\"","$ percy snapshot _site/ --ignore-files \"/blog/drafts/**\""],"flags":{"snapshot-files":{"name":"snapshot-files","type":"option","char":"s","description":"Glob or comma-seperated string of globs for matching the files and directories to snapshot.","default":"**/*.html,**/*.htm"},"ignore-files":{"name":"ignore-files","type":"option","char":"i","description":"Glob or comma-seperated string of globs for matching the files and directories to ignore.","default":""},"base-url":{"name":"base-url","type":"option","char":"b","description":"If your static files will be hosted in a subdirectory, instead \nof the webserver's root path, set that subdirectory with this flag.","default":"/"},"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"Asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"Port","default":5338}},"args":[{"name":"snapshotDirectory","description":"A path to the directory you would like to snapshot","required":true}]},"start":{"id":"start","description":"Starts the percy process.","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy start\ninfo: percy has started on port 5338."],"flags":{"detached":{"name":"detached","type":"boolean","char":"d","description":"start as a detached process","allowNo":false},"network-idle-timeout":{"name":"network-idle-timeout","type":"option","char":"t","description":"asset discovery network idle timeout (in milliseconds)","default":50},"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]},"stop":{"id":"stop","description":"Stops the percy process.","pluginName":"percy","pluginType":"core","hidden":true,"aliases":[],"examples":["$ percy stop\ninfo: percy has stopped."],"flags":{"port":{"name":"port","type":"option","char":"p","description":"port","default":5338}},"args":[]}}} |
@@ -0,1 +1,8 @@ | ||
# [0.5.0](https://github.com/percy/percy-agent/compare/v0.4.9...v0.5.0) (2019-05-30) | ||
### Features | ||
* Support parallel snapshots calls ([#168](https://github.com/percy/percy-agent/issues/168)) ([744a399](https://github.com/percy/percy-agent/commit/744a399)) | ||
## [0.4.9](https://github.com/percy/percy-agent/compare/v0.4.8...v0.4.9) (2019-05-21) | ||
@@ -2,0 +9,0 @@ |
@@ -0,1 +1,2 @@ | ||
import * as pool from 'generic-pool'; | ||
import * as puppeteer from 'puppeteer'; | ||
@@ -11,9 +12,14 @@ import { SnapshotOptions } from '../percy-agent-client/snapshot-options'; | ||
browser: puppeteer.Browser | null; | ||
pages: puppeteer.Page[] | null; | ||
pagePool: pool.Pool<puppeteer.Page> | null; | ||
readonly DEFAULT_NETWORK_IDLE_TIMEOUT: number; | ||
networkIdleTimeout: number; | ||
readonly PAGE_POOL_SIZE: number; | ||
readonly MAX_SNAPSHOT_WIDTHS: number; | ||
readonly PAGE_POOL_SIZE_MIN: number; | ||
readonly PAGE_POOL_SIZE_MAX: number; | ||
readonly DEFAULT_WIDTHS: number[]; | ||
constructor(buildId: number, options?: AssetDiscoveryOptions); | ||
setup(): Promise<void>; | ||
createBrowser(): Promise<puppeteer.Browser>; | ||
createPagePool(exec: () => PromiseLike<puppeteer.Page>, min: number, max: number): Promise<pool.Pool<puppeteer.Page>>; | ||
createPage(browser: puppeteer.Browser): Promise<puppeteer.Page>; | ||
discoverResources(rootResourceUrl: string, domSnapshot: string, options: SnapshotOptions): Promise<any[]>; | ||
@@ -23,5 +29,5 @@ shouldRequestResolve(request: puppeteer.Request): boolean; | ||
private resourcesForWidth; | ||
private cleanPagePool; | ||
private closeBrowser; | ||
private closePages; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const pool = require("generic-pool"); | ||
const puppeteer = require("puppeteer"); | ||
@@ -8,2 +9,3 @@ const logger_1 = require("../utils/logger"); | ||
const response_service_1 = require("./response-service"); | ||
const DEFAULT_PAGE_POOL_SIZE = process.env.PERCY_POOL_SIZE; | ||
class AssetDiscoveryService extends percy_client_service_1.default { | ||
@@ -13,5 +15,5 @@ constructor(buildId, options = {}) { | ||
this.DEFAULT_NETWORK_IDLE_TIMEOUT = 50; // ms | ||
// How many 'pages' (i.e. tabs) we'll keep around. | ||
// We will only be able to process at most these many snapshot widths. | ||
this.PAGE_POOL_SIZE = 10; | ||
this.MAX_SNAPSHOT_WIDTHS = 10; | ||
this.PAGE_POOL_SIZE_MIN = 2; | ||
this.PAGE_POOL_SIZE_MAX = DEFAULT_PAGE_POOL_SIZE ? parseInt(DEFAULT_PAGE_POOL_SIZE) : 10; | ||
// Default widths to use for asset discovery. Must match Percy service defaults. | ||
@@ -22,7 +24,15 @@ this.DEFAULT_WIDTHS = [1280, 375]; | ||
this.browser = null; | ||
this.pages = null; | ||
this.pagePool = null; | ||
} | ||
async setup() { | ||
logger_1.profile('-> assetDiscoveryService.setup'); | ||
const browser = this.browser = await this.createBrowser(); | ||
this.pagePool = await this.createPagePool(() => { | ||
return this.createPage(browser); | ||
}, this.PAGE_POOL_SIZE_MIN, this.PAGE_POOL_SIZE_MAX); | ||
logger_1.profile('-> assetDiscoveryService.setup'); | ||
} | ||
async createBrowser() { | ||
logger_1.profile('-> assetDiscoveryService.puppeteer.launch'); | ||
this.browser = await puppeteer.launch({ | ||
const browser = await puppeteer.launch({ | ||
args: [ | ||
@@ -36,23 +46,38 @@ '--no-sandbox', | ||
logger_1.profile('-> assetDiscoveryService.puppeteer.launch'); | ||
logger_1.profile('-> assetDiscoveryService.browser.newPagePool'); | ||
const pagePromises = []; | ||
for (let i = 0; i < this.PAGE_POOL_SIZE; i++) { | ||
const promise = this.browser.newPage().then((page) => { | ||
return page.setRequestInterception(true).then(() => page); | ||
}); | ||
pagePromises.push(promise); | ||
} | ||
this.pages = await Promise.all(pagePromises); | ||
logger_1.profile('-> assetDiscoveryService.browser.newPagePool'); | ||
return browser; | ||
} | ||
async createPagePool(exec, min, max) { | ||
logger_1.profile('-> assetDiscoveryService.createPagePool'); | ||
const result = pool.createPool({ | ||
create() { | ||
return exec(); | ||
}, | ||
destroy(page) { | ||
return page.close(); | ||
}, | ||
}, { min, max }); | ||
logger_1.profile('-> assetDiscoveryService.createPagePool'); | ||
return result; | ||
} | ||
async createPage(browser) { | ||
logger_1.profile('-> assetDiscoveryService.browser.newPage'); | ||
const page = await browser.newPage(); | ||
await page.setRequestInterception(true); | ||
logger_1.profile('-> assetDiscoveryService.browser.newPage'); | ||
return page; | ||
} | ||
async discoverResources(rootResourceUrl, domSnapshot, options) { | ||
logger_1.profile('-> assetDiscoveryService.discoverResources'); | ||
if (!this.browser || !this.pages || !this.pages.length) { | ||
logger_1.default.error('Puppeteer failed to open with a page pool.'); | ||
if (this.browser === null) { | ||
logger_1.default.error('Puppeteer failed to open browser.'); | ||
return []; | ||
} | ||
if (options.widths && options.widths.length > this.pages.length) { | ||
logger_1.default.error(`Too many widths requested. Max allowed is ${this.PAGE_POOL_SIZE}. Requested: ${options.widths}`); | ||
if (!this.pagePool) { | ||
logger_1.default.error('Failed to create pool of pages.'); | ||
return []; | ||
} | ||
if (options.widths && options.widths.length > this.MAX_SNAPSHOT_WIDTHS) { | ||
logger_1.default.error(`Too many widths requested. Max is ${this.MAX_SNAPSHOT_WIDTHS}. Requested: ${options.widths}`); | ||
return []; | ||
} | ||
rootResourceUrl = this.parseRequestPath(rootResourceUrl); | ||
@@ -68,4 +93,4 @@ logger_1.default.debug(`discovering assets for URL: ${rootResourceUrl}`); | ||
const resourcePromises = []; | ||
for (let idx = 0; idx < widths.length; idx++) { | ||
const promise = this.resourcesForWidth(this.pages[idx], widths[idx], domSnapshot, rootResourceUrl, enableJavaScript); | ||
for (const width of widths) { | ||
const promise = this.resourcesForWidth(this.pagePool, width, domSnapshot, rootResourceUrl, enableJavaScript); | ||
resourcePromises.push(promise); | ||
@@ -103,23 +128,31 @@ } | ||
async teardown() { | ||
await this.closePages(); | ||
await this.cleanPagePool(); | ||
await this.closeBrowser(); | ||
} | ||
async resourcesForWidth(page, width, domSnapshot, rootResourceUrl, enableJavaScript) { | ||
async resourcesForWidth(pool, width, domSnapshot, rootResourceUrl, enableJavaScript) { | ||
logger_1.default.debug(`discovering assets for width: ${width}`); | ||
logger_1.profile('--> assetDiscoveryService.pool.acquire', { url: rootResourceUrl }); | ||
const page = await pool.acquire(); | ||
logger_1.profile('--> assetDiscoveryService.pool.acquire'); | ||
await page.setJavaScriptEnabled(enableJavaScript); | ||
await page.setViewport(Object.assign(page.viewport(), { width })); | ||
page.on('request', async (request) => { | ||
if (!this.shouldRequestResolve(request)) { | ||
await request.abort(); | ||
return; | ||
try { | ||
if (!this.shouldRequestResolve(request)) { | ||
await request.abort(); | ||
return; | ||
} | ||
if (request.url() === rootResourceUrl) { | ||
await request.respond({ | ||
body: domSnapshot, | ||
contentType: 'text/html', | ||
status: 200, | ||
}); | ||
return; | ||
} | ||
await request.continue(); | ||
} | ||
if (request.url() === rootResourceUrl) { | ||
await request.respond({ | ||
body: domSnapshot, | ||
contentType: 'text/html', | ||
status: 200, | ||
}); | ||
return; | ||
catch (error) { | ||
logger_1.logError(error); | ||
} | ||
await request.continue(); | ||
}); | ||
@@ -146,16 +179,35 @@ const maybeResourcePromises = []; | ||
}); | ||
logger_1.profile('--> assetDiscoveryService.page.goto', { url: rootResourceUrl }); | ||
await page.goto(rootResourceUrl); | ||
logger_1.profile('--> assetDiscoveryService.page.goto'); | ||
logger_1.profile('--> assetDiscoveryService.waitForNetworkIdle'); | ||
await wait_for_network_idle_1.default(page, this.networkIdleTimeout).catch(logger_1.logError); | ||
logger_1.profile('--> assetDiscoveryService.waitForNetworkIdle'); | ||
page.removeAllListeners(); | ||
logger_1.profile('--> assetDiscoveryServer.waitForResourceProcessing'); | ||
const maybeResources = await Promise.all(maybeResourcePromises); | ||
logger_1.profile('--> assetDiscoveryServer.waitForResourceProcessing'); | ||
return maybeResources.filter((maybeResource) => maybeResource != null); | ||
try { | ||
logger_1.profile('--> assetDiscoveryService.page.goto', { url: rootResourceUrl }); | ||
await page.goto(rootResourceUrl); | ||
logger_1.profile('--> assetDiscoveryService.page.goto'); | ||
logger_1.profile('--> assetDiscoveryService.waitForNetworkIdle'); | ||
await wait_for_network_idle_1.default(page, this.networkIdleTimeout); | ||
logger_1.profile('--> assetDiscoveryService.waitForNetworkIdle'); | ||
logger_1.profile('--> assetDiscoveryServer.waitForResourceProcessing'); | ||
const maybeResources = await Promise.all(maybeResourcePromises); | ||
logger_1.profile('--> assetDiscoveryServer.waitForResourceProcessing'); | ||
logger_1.profile('--> assetDiscoveryService.pool.release', { url: rootResourceUrl }); | ||
await page.removeAllListeners('request'); | ||
await page.removeAllListeners('requestfinished'); | ||
await page.removeAllListeners('requestfailed'); | ||
await pool.release(page); | ||
logger_1.profile('--> assetDiscoveryService.pool.release'); | ||
return maybeResources.filter((maybeResource) => maybeResource != null); | ||
} | ||
catch (error) { | ||
logger_1.logError(error); | ||
} | ||
return []; | ||
} | ||
async cleanPagePool() { | ||
if (this.pagePool === null) { | ||
return; | ||
} | ||
await this.pagePool.drain(); | ||
await this.pagePool.clear(); | ||
this.pagePool = null; | ||
} | ||
async closeBrowser() { | ||
if (!this.browser) { | ||
if (this.browser === null) { | ||
return; | ||
@@ -166,10 +218,3 @@ } | ||
} | ||
async closePages() { | ||
if (!this.pages) { | ||
return; | ||
} | ||
await Promise.all(this.pages.map((page) => page.close())); | ||
this.pages = null; | ||
} | ||
} | ||
exports.default = AssetDiscoveryService; |
{ | ||
"name": "percy", | ||
"description": "An agent process for integrating with Percy.", | ||
"version": "0.4.9", | ||
"version": "0.5.0", | ||
"author": "Perceptual Inc", | ||
@@ -34,2 +34,3 @@ "bin": { | ||
"express": "^4.16.3", | ||
"generic-pool": "^3.7.1", | ||
"globby": "^9.2.0", | ||
@@ -58,2 +59,3 @@ "js-yaml": "^3.13.1", | ||
"@types/cross-spawn": "^6.0.0", | ||
"@types/generic-pool": "^3.1.9", | ||
"@types/http-server": "^0.10.0", | ||
@@ -63,4 +65,5 @@ "@types/mocha": "^5.2.5", | ||
"@types/node": "^12.0.0", | ||
"@types/sinon": "^5.0.1", | ||
"@types/sinon": "^7.0.12", | ||
"@types/sinon-chai": "^3.2.0", | ||
"babel-loader": "^8.0.6", | ||
"browserify": "^16.2.3", | ||
@@ -72,3 +75,9 @@ "chai": "^4.1.2", | ||
"husky": "^1.0.0-rc.13", | ||
"mocha": "^6.1.3", | ||
"karma": "^4.1.0", | ||
"karma-chrome-launcher": "^2.2.0", | ||
"karma-firefox-launcher": "^1.1.0", | ||
"karma-mocha": "^1.3.0", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"karma-webpack": "^3.0.5", | ||
"mocha": "^6.1.4", | ||
"nock": "^10.0.6", | ||
@@ -91,3 +100,4 @@ "npm-watch": "^0.6.0", | ||
"tslint": "^5", | ||
"typescript": "^3" | ||
"typescript": "^3", | ||
"webpack": "^4.32.2" | ||
}, | ||
@@ -136,3 +146,3 @@ "engines": { | ||
"test": "npm run build-client && PERCY_TOKEN=abc mocha --forbid-only \"test/**/*.test.ts\" --exclude \"test/percy-agent-client/**/*.test.ts\" --exclude \"test/integration/**/*\"", | ||
"test-client": "mkdir -p dist-test/ && npm run build-client-test && testem ci --file ./test/percy-agent-client/testem.js", | ||
"test-client": "npm run build-client && karma start ./test/percy-agent-client/karma.conf.js", | ||
"test-integration": "npm run build-client && node ./bin/run exec -- mocha test/integration/**/*.test.ts", | ||
@@ -139,0 +149,0 @@ "test-snapshot-command": "./bin/run snapshot test/integration/test-static-site -b /dummy-base-url -i '(red-keep)' -c '\\.(html)$'", |
@@ -22,3 +22,3 @@ @percy/agent | ||
$ percy (-v|--version|version) | ||
percy/0.4.9 linux-x64 node-v10.15.3 | ||
percy/0.5.0 linux-x64 node-v10.16.0 | ||
$ percy --help [COMMAND] | ||
@@ -54,3 +54,3 @@ USAGE | ||
_See code: [dist/commands/exec.ts](https://github.com/percy/percy-agent/blob/v0.4.9/dist/commands/exec.ts)_ | ||
_See code: [dist/commands/exec.ts](https://github.com/percy/percy-agent/blob/v0.5.0/dist/commands/exec.ts)_ | ||
@@ -73,3 +73,3 @@ ## `percy finalize` | ||
_See code: [dist/commands/finalize.ts](https://github.com/percy/percy-agent/blob/v0.4.9/dist/commands/finalize.ts)_ | ||
_See code: [dist/commands/finalize.ts](https://github.com/percy/percy-agent/blob/v0.5.0/dist/commands/finalize.ts)_ | ||
@@ -125,3 +125,3 @@ ## `percy help [COMMAND]` | ||
_See code: [dist/commands/snapshot.ts](https://github.com/percy/percy-agent/blob/v0.4.9/dist/commands/snapshot.ts)_ | ||
_See code: [dist/commands/snapshot.ts](https://github.com/percy/percy-agent/blob/v0.5.0/dist/commands/snapshot.ts)_ | ||
<!-- commandsstop --> |
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
94077
1903
20
55
12
+ Addedgeneric-pool@^3.7.1
+ Addedgeneric-pool@3.9.0(transitive)