UNPKG

pagespeed-quest

Version:

A framework for efficient web front-end speed improvement

230 lines 23.7 kB
#!/usr/bin/env node import Path from 'path'; import { Command } from 'commander'; import Watch from 'node-watch'; import { Dependency } from './dependency.js'; import { execLighthouse } from './lighthouse.js'; import { execLoadshow } from './loadshow.js'; import { execWebshotCapture, execWebshotCompare } from './webshot.js'; import { InventoryRepository, withPlaybackProxy, withRecordingProxy } from './index.js'; const dependency = new Dependency(); function collect(value, previous) { return previous.concat([value]); } const main = new Command(); main.option('-i, --inventory <dir>', 'Inventory directory', './inventory'); function registerLighthouseCommands(main) { const lighthouse = main.command('lighthouse'); lighthouse.description('Run Lighthouse (performance category) via a proxy'); lighthouse.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts'); lighthouse.option('-l, --laud', 'Show browser and open the report after completion', false); lighthouse.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000'); const recording = lighthouse.command('recording'); recording.description('Record contents by lighthouse'); recording.option('-d, --device <mobile|desktop>', 'Device type', 'mobile'); recording.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []); recording.argument('<url>', 'Url to measure performance'); recording.action(async (url) => { const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory'); const deviceType = recording.opts().device || 'mobile'; const artifactsDir = lighthouse.opts().artifacts || './artifacts'; const laud = !!lighthouse.opts().laud; const timeout = Number(lighthouse.opts().timeout || '30000'); const excludePatterns = recording.opts().exclude || []; await withRecordingProxy({ entryUrl: url, deviceType, inventoryRepository, excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined, }, dependency, async (proxy) => { await execLighthouse({ url, proxyPort: proxy.port, deviceType, view: laud, artifactsDir, headless: !laud, timeout, }, dependency); dependency.logger?.info('Lighthouse completed. Saving inventory...'); }); }); const playback = lighthouse.command('playback'); playback.description('Playback contents for lighthouse'); playback.action(async () => { const inventoryDir = main.opts().inventory || './inventory'; const inventoryRepository = new InventoryRepository(inventoryDir); const artifactsDir = lighthouse.opts().artifacts || './artifacts'; const laud = !!lighthouse.opts().laud; const timeout = Number(lighthouse.opts().timeout || '30000'); // Load inventory for traffic statistics const inventory = await inventoryRepository.loadInventory(); await withPlaybackProxy({ inventoryRepository, }, dependency, async (proxy) => { await execLighthouse({ url: proxy.entryUrl, proxyPort: proxy.port, deviceType: proxy.deviceType, view: laud, artifactsDir, headless: !laud, timeout, inventoryDir, resources: inventory.resources, }, dependency); dependency.logger?.info('Lighthouse completed'); }); }); } function registerLoadshowCommands(main) { const loadshow = main.command('loadshow'); loadshow.description('Run loadshow via a proxy'); loadshow.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts'); loadshow.option('-c, --credit <string>', 'Credit string'); loadshow.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000'); const recording = loadshow.command('recording'); recording.description('Record contents by loadshow'); recording.option('-d, --device <mobile|desktop>', 'Device type', 'mobile'); recording.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []); recording.argument('<url>', 'Url to measure performance'); recording.action(async (url) => { const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory'); const deviceType = recording.opts().device || 'mobile'; const artifactsDir = loadshow.opts().artifacts || './artifacts'; const credit = loadshow.opts().credit || ''; const timeout = Number(loadshow.opts().timeout || '30000'); const excludePatterns = recording.opts().exclude || []; await withRecordingProxy({ entryUrl: url, deviceType, inventoryRepository, excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined, }, dependency, async (proxy) => { await execLoadshow({ url, proxyPort: proxy.port, deviceType, artifactsDir, credit, timeout }, dependency); dependency.logger?.info('Loadshow completed. Saving inventory...'); }); }); const playback = loadshow.command('playback'); playback.description('Playback contents for loadshow'); playback.action(async () => { const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory'); const artifactsDir = loadshow.opts().artifacts || './artifacts'; const credit = loadshow.opts().credit || ''; const timeout = Number(loadshow.opts().timeout || '30000'); await withPlaybackProxy({ inventoryRepository, }, dependency, async (proxy) => { await execLoadshow({ url: proxy.entryUrl, proxyPort: proxy.port, deviceType: proxy.deviceType, artifactsDir, credit, timeout, }, dependency); dependency.logger?.info('Loadshow completed'); }); }); } function registerProxyCommands(main) { const proxy = main.command('proxy'); proxy.option('-p, --port <number>', 'Proxy port', '8080'); proxy.option('-r, --record <url>', 'Recording URL to start the proxy as recording mode', ''); proxy.option('-x, --exclude <pattern>', 'Regex patterns to exclude URLs from recording (repeatable)', collect, []); proxy.action(async () => { const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory'); const port = Number(proxy.opts().port || '8080'); if (proxy.opts().record) { const url = proxy.opts().record; if (!url) { throw new Error('Recording URL must be specified with --record option.'); } const excludePatterns = proxy.opts().exclude || []; // Recordingモード await withRecordingProxy({ inventoryRepository, port, entryUrl: url, excludePatterns: excludePatterns.length > 0 ? excludePatterns : undefined, }, dependency, async () => { dependency.logger?.info(`Recording proxy started on port ${port}. Press Ctrl+C to stop.`); // Wait for Ctrl+C signal return new Promise((resolve) => { process.on('SIGINT', () => { dependency.logger?.info('Saving the inventory...'); resolve(); }); }); }); } else { // Playbackモード // eslint-disable-next-line no-constant-condition while (true) { await withPlaybackProxy({ inventoryRepository, port }, dependency, async () => { const watcher = Watch(inventoryRepository.dirPath, { recursive: true }); return new Promise((ok) => { watcher.on('change', () => { watcher.close(); dependency.logger?.info('Inventory changed. Restarting proxy...'); ok(); }); }); }); } } }); } function registerCaptureCommands(main) { const visualTest = main.command('capture'); visualTest.description('Capture a screenshot via playback proxy and optionally compare with a baseline'); visualTest.option('-a, --artifacts <dir>', 'Artifacts directory', './artifacts'); visualTest.option('-t, --timeout <ms>', 'Timeout milliseconds', '30000'); visualTest.option('--compare <file>', 'Baseline PNG file to compare against'); visualTest.option('--baseline-label <label>', 'Label for the baseline image', '前回'); visualTest.option('--current-label <label>', 'Label for the current image', '今回'); visualTest.action(async () => { const inventoryRepository = new InventoryRepository(main.opts().inventory || './inventory'); const artifactsDir = visualTest.opts().artifacts || './artifacts'; const timeout = Number(visualTest.opts().timeout || '30000'); const compareFile = visualTest.opts().compare; const baselineLabel = visualTest.opts().baselineLabel || '前回'; const currentLabel = visualTest.opts().currentLabel || '今回'; await withPlaybackProxy({ inventoryRepository, fullThrottle: true, }, dependency, async (proxy) => { const outputPath = Path.join(artifactsDir, 'capture.png'); await dependency.mkdirp(artifactsDir); await execWebshotCapture({ url: proxy.entryUrl, proxyPort: proxy.port, preset: proxy.deviceType === 'desktop' ? 'desktop' : 'mobile', output: outputPath, timeout, }, dependency); dependency.logger?.info(`Screenshot saved to ${outputPath}`); if (compareFile) { const diffOutput = Path.join(artifactsDir, 'capture-diff.png'); const digestTxt = Path.join(artifactsDir, 'capture-diff.txt'); await execWebshotCompare({ baseline: compareFile, current: outputPath, output: diffOutput, digestTxt, baselineLabel, currentLabel, }, dependency); dependency.logger?.info(`Diff image saved to ${diffOutput}`); dependency.logger?.info(`Diff digest saved to ${digestTxt}`); } }); }); } registerLighthouseCommands(main); registerLoadshowCommands(main); registerProxyCommands(main); registerCaptureCommands(main); main.parse(process.argv); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb21tYW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFFQSxPQUFPLElBQUksTUFBTSxNQUFNLENBQUE7QUFFdkIsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUNuQyxPQUFPLEtBQUssTUFBTSxZQUFZLENBQUE7QUFFOUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzVDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQTtBQUNoRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBRTVDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUVyRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFFdkYsTUFBTSxVQUFVLEdBQUcsSUFBSSxVQUFVLEVBQUUsQ0FBQTtBQUVuQyxTQUFTLE9BQU8sQ0FBQyxLQUFhLEVBQUUsUUFBa0I7SUFDaEQsT0FBTyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtBQUNqQyxDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQTtBQUMxQixJQUFJLENBQUMsTUFBTSxDQUFDLHVCQUF1QixFQUFFLHFCQUFxQixFQUFFLGFBQWEsQ0FBQyxDQUFBO0FBRTFFLFNBQVMsMEJBQTBCLENBQUMsSUFBYTtJQUMvQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBQzdDLFVBQVUsQ0FBQyxXQUFXLENBQUMsbURBQW1ELENBQUMsQ0FBQTtJQUMzRSxVQUFVLENBQUMsTUFBTSxDQUFDLHVCQUF1QixFQUFFLHFCQUFxQixFQUFFLGFBQWEsQ0FBQyxDQUFBO0lBQ2hGLFVBQVUsQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFFLG1EQUFtRCxFQUFFLEtBQUssQ0FBQyxDQUFBO0lBQzNGLFVBQVUsQ0FBQyxNQUFNLENBQUMsb0JBQW9CLEVBQUUsc0JBQXNCLEVBQUUsT0FBTyxDQUFDLENBQUE7SUFFeEUsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQTtJQUNqRCxTQUFTLENBQUMsV0FBVyxDQUFDLCtCQUErQixDQUFDLENBQUE7SUFDdEQsU0FBUyxDQUFDLE1BQU0sQ0FBQywrQkFBK0IsRUFBRSxhQUFhLEVBQUUsUUFBUSxDQUFDLENBQUE7SUFDMUUsU0FBUyxDQUFDLE1BQU0sQ0FBQyx5QkFBeUIsRUFBRSw0REFBNEQsRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDdEgsU0FBUyxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsNEJBQTRCLENBQUMsQ0FBQTtJQUN6RCxTQUFTLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxHQUFXLEVBQUUsRUFBRTtRQUNyQyxNQUFNLG1CQUFtQixHQUFHLElBQUksbUJBQW1CLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUMsQ0FBQTtRQUMzRixNQUFNLFVBQVUsR0FBZSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxJQUFJLFFBQVEsQ0FBQTtRQUNsRSxNQUFNLFlBQVksR0FBRyxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUMsU0FBUyxJQUFJLGFBQWEsQ0FBQTtRQUNqRSxNQUFNLElBQUksR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksQ0FBQTtRQUNyQyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQTtRQUM1RCxNQUFNLGVBQWUsR0FBYSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQTtRQUVoRSxNQUFNLGtCQUFrQixDQUN0QjtZQUNFLFFBQVEsRUFBRSxHQUFHO1lBQ2IsVUFBVTtZQUNWLG1CQUFtQjtZQUNuQixlQUFlLEVBQUUsZUFBZSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsU0FBUztTQUMxRSxFQUNELFVBQVUsRUFDVixLQUFLLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDZCxNQUFNLGNBQWMsQ0FDbEI7Z0JBQ0UsR0FBRztnQkFDSCxTQUFTLEVBQUUsS0FBSyxDQUFDLElBQUk7Z0JBQ3JCLFVBQVU7Z0JBQ1YsSUFBSSxFQUFFLElBQUk7Z0JBQ1YsWUFBWTtnQkFDWixRQUFRLEVBQUUsQ0FBQyxJQUFJO2dCQUNmLE9BQU87YUFDUixFQUNELFVBQVUsQ0FDWCxDQUFBO1lBQ0QsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsMkNBQTJDLENBQUMsQ0FBQTtRQUN0RSxDQUFDLENBQ0YsQ0FBQTtJQUNILENBQUMsQ0FBQyxDQUFBO0lBRUYsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQTtJQUMvQyxRQUFRLENBQUMsV0FBVyxDQUFDLGtDQUFrQyxDQUFDLENBQUE7SUFDeEQsUUFBUSxDQUFDLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRTtRQUN6QixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsU0FBUyxJQUFJLGFBQWEsQ0FBQTtRQUMzRCxNQUFNLG1CQUFtQixHQUFHLElBQUksbUJBQW1CLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDakUsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUE7UUFDakUsTUFBTSxJQUFJLEdBQUcsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxJQUFJLENBQUE7UUFDckMsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLENBQUE7UUFFNUQsd0NBQXdDO1FBQ3hDLE1BQU0sU0FBUyxHQUFHLE1BQU0sbUJBQW1CLENBQUMsYUFBYSxFQUFFLENBQUE7UUFFM0QsTUFBTSxpQkFBaUIsQ0FDckI7WUFDRSxtQkFBbUI7U0FDcEIsRUFDRCxVQUFVLEVBQ1YsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ2QsTUFBTSxjQUFjLENBQ2xCO2dCQUNFLEdBQUcsRUFBRSxLQUFLLENBQUMsUUFBUTtnQkFDbkIsU0FBUyxFQUFFLEtBQUssQ0FBQyxJQUFJO2dCQUNyQixVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVU7Z0JBQzVCLElBQUksRUFBRSxJQUFJO2dCQUNWLFlBQVk7Z0JBQ1osUUFBUSxFQUFFLENBQUMsSUFBSTtnQkFDZixPQUFPO2dCQUNQLFlBQVk7Z0JBQ1osU0FBUyxFQUFFLFNBQVMsQ0FBQyxTQUFTO2FBQy9CLEVBQ0QsVUFBVSxDQUNYLENBQUE7WUFDRCxVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxDQUFBO1FBQ2pELENBQUMsQ0FDRixDQUFBO0lBQ0gsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQsU0FBUyx3QkFBd0IsQ0FBQyxJQUFhO0lBQzdDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUE7SUFDekMsUUFBUSxDQUFDLFdBQVcsQ0FBQywwQkFBMEIsQ0FBQyxDQUFBO0lBQ2hELFFBQVEsQ0FBQyxNQUFNLENBQUMsdUJBQXVCLEVBQUUscUJBQXFCLEVBQUUsYUFBYSxDQUFDLENBQUE7SUFDOUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyx1QkFBdUIsRUFBRSxlQUFlLENBQUMsQ0FBQTtJQUN6RCxRQUFRLENBQUMsTUFBTSxDQUFDLG9CQUFvQixFQUFFLHNCQUFzQixFQUFFLE9BQU8sQ0FBQyxDQUFBO0lBRXRFLE1BQU0sU0FBUyxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUE7SUFDL0MsU0FBUyxDQUFDLFdBQVcsQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO0lBQ3BELFNBQVMsQ0FBQyxNQUFNLENBQUMsK0JBQStCLEVBQUUsYUFBYSxFQUFFLFFBQVEsQ0FBQyxDQUFBO0lBQzFFLFNBQVMsQ0FBQyxNQUFNLENBQUMseUJBQXlCLEVBQUUsNERBQTRELEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBQ3RILFNBQVMsQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLDRCQUE0QixDQUFDLENBQUE7SUFDekQsU0FBUyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsR0FBVyxFQUFFLEVBQUU7UUFDckMsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxTQUFTLElBQUksYUFBYSxDQUFDLENBQUE7UUFDM0YsTUFBTSxVQUFVLEdBQWUsU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sSUFBSSxRQUFRLENBQUE7UUFDbEUsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUE7UUFDL0QsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDM0MsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLENBQUE7UUFDMUQsTUFBTSxlQUFlLEdBQWEsU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUE7UUFFaEUsTUFBTSxrQkFBa0IsQ0FDdEI7WUFDRSxRQUFRLEVBQUUsR0FBRztZQUNiLFVBQVU7WUFDVixtQkFBbUI7WUFDbkIsZUFBZSxFQUFFLGVBQWUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDMUUsRUFDRCxVQUFVLEVBQ1YsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ2QsTUFBTSxZQUFZLENBQUMsRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLEVBQUUsVUFBVSxDQUFDLENBQUE7WUFDekcsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMseUNBQXlDLENBQUMsQ0FBQTtRQUNwRSxDQUFDLENBQ0YsQ0FBQTtJQUNILENBQUMsQ0FBQyxDQUFBO0lBRUYsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQTtJQUM3QyxRQUFRLENBQUMsV0FBVyxDQUFDLGdDQUFnQyxDQUFDLENBQUE7SUFDdEQsUUFBUSxDQUFDLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRTtRQUN6QixNQUFNLG1CQUFtQixHQUFHLElBQUksbUJBQW1CLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUMsQ0FBQTtRQUMzRixNQUFNLFlBQVksR0FBRyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsU0FBUyxJQUFJLGFBQWEsQ0FBQTtRQUMvRCxNQUFNLE1BQU0sR0FBRyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBQTtRQUMzQyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQTtRQUUxRCxNQUFNLGlCQUFpQixDQUNyQjtZQUNFLG1CQUFtQjtTQUNwQixFQUNELFVBQVUsRUFDVixLQUFLLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDZCxNQUFNLFlBQVksQ0FDaEI7Z0JBQ0UsR0FBRyxFQUFFLEtBQUssQ0FBQyxRQUFRO2dCQUNuQixTQUFTLEVBQUUsS0FBSyxDQUFDLElBQUk7Z0JBQ3JCLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtnQkFDNUIsWUFBWTtnQkFDWixNQUFNO2dCQUNOLE9BQU87YUFDUixFQUNELFVBQVUsQ0FDWCxDQUFBO1lBQ0QsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsb0JBQW9CLENBQUMsQ0FBQTtRQUMvQyxDQUFDLENBQ0YsQ0FBQTtJQUNILENBQUMsQ0FBQyxDQUFBO0FBQ0osQ0FBQztBQUVELFNBQVMscUJBQXFCLENBQUMsSUFBYTtJQUMxQyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ25DLEtBQUssQ0FBQyxNQUFNLENBQUMscUJBQXFCLEVBQUUsWUFBWSxFQUFFLE1BQU0sQ0FBQyxDQUFBO0lBQ3pELEtBQUssQ0FBQyxNQUFNLENBQUMsb0JBQW9CLEVBQUUsb0RBQW9ELEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDNUYsS0FBSyxDQUFDLE1BQU0sQ0FBQyx5QkFBeUIsRUFBRSw0REFBNEQsRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFFbEgsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRTtRQUN0QixNQUFNLG1CQUFtQixHQUFHLElBQUksbUJBQW1CLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUMsQ0FBQTtRQUMzRixNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsQ0FBQTtRQUVoRCxJQUFJLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUN4QixNQUFNLEdBQUcsR0FBRyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxDQUFBO1lBQy9CLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDVCxNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxDQUFDLENBQUE7WUFDMUUsQ0FBQztZQUVELE1BQU0sZUFBZSxHQUFhLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFBO1lBRTVELGVBQWU7WUFDZixNQUFNLGtCQUFrQixDQUN0QjtnQkFDRSxtQkFBbUI7Z0JBQ25CLElBQUk7Z0JBQ0osUUFBUSxFQUFFLEdBQUc7Z0JBQ2IsZUFBZSxFQUFFLGVBQWUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLFNBQVM7YUFDMUUsRUFDRCxVQUFVLEVBQ1YsS0FBSyxJQUFJLEVBQUU7Z0JBQ1QsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsbUNBQW1DLElBQUkseUJBQXlCLENBQUMsQ0FBQTtnQkFFekYseUJBQXlCO2dCQUN6QixPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7b0JBQ25DLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTt3QkFDeEIsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMseUJBQXlCLENBQUMsQ0FBQTt3QkFDbEQsT0FBTyxFQUFFLENBQUE7b0JBQ1gsQ0FBQyxDQUFDLENBQUE7Z0JBQ0osQ0FBQyxDQUFDLENBQUE7WUFDSixDQUFDLENBQ0YsQ0FBQTtRQUNILENBQUM7YUFBTSxDQUFDO1lBQ04sY0FBYztZQUNkLGlEQUFpRDtZQUNqRCxPQUFPLElBQUksRUFBRSxDQUFDO2dCQUNaLE1BQU0saUJBQWlCLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxJQUFJLEVBQUUsRUFBRSxVQUFVLEVBQUUsS0FBSyxJQUFJLEVBQUU7b0JBQzVFLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQTtvQkFDdkUsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFO3dCQUN4QixPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUU7NEJBQ3hCLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQTs0QkFDZixVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFBOzRCQUNqRSxFQUFFLEVBQUUsQ0FBQTt3QkFDTixDQUFDLENBQUMsQ0FBQTtvQkFDSixDQUFDLENBQUMsQ0FBQTtnQkFDSixDQUFDLENBQUMsQ0FBQTtZQUNKLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQsU0FBUyx1QkFBdUIsQ0FBQyxJQUFhO0lBQzVDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUE7SUFDMUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxnRkFBZ0YsQ0FBQyxDQUFBO0lBQ3hHLFVBQVUsQ0FBQyxNQUFNLENBQUMsdUJBQXVCLEVBQUUscUJBQXFCLEVBQUUsYUFBYSxDQUFDLENBQUE7SUFDaEYsVUFBVSxDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsRUFBRSxzQkFBc0IsRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUN4RSxVQUFVLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLHNDQUFzQyxDQUFDLENBQUE7SUFDN0UsVUFBVSxDQUFDLE1BQU0sQ0FBQywwQkFBMEIsRUFBRSw4QkFBOEIsRUFBRSxJQUFJLENBQUMsQ0FBQTtJQUNuRixVQUFVLENBQUMsTUFBTSxDQUFDLHlCQUF5QixFQUFFLDZCQUE2QixFQUFFLElBQUksQ0FBQyxDQUFBO0lBRWpGLFVBQVUsQ0FBQyxNQUFNLENBQUMsS0FBSyxJQUFJLEVBQUU7UUFDM0IsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxTQUFTLElBQUksYUFBYSxDQUFDLENBQUE7UUFDM0YsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLFNBQVMsSUFBSSxhQUFhLENBQUE7UUFDakUsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLENBQUE7UUFDNUQsTUFBTSxXQUFXLEdBQUcsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLE9BQTZCLENBQUE7UUFDbkUsTUFBTSxhQUFhLEdBQUcsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLGFBQWEsSUFBSSxJQUFJLENBQUE7UUFDN0QsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUE7UUFFM0QsTUFBTSxpQkFBaUIsQ0FDckI7WUFDRSxtQkFBbUI7WUFDbkIsWUFBWSxFQUFFLElBQUk7U0FDbkIsRUFDRCxVQUFVLEVBQ1YsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ2QsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsYUFBYSxDQUFDLENBQUE7WUFFekQsTUFBTSxVQUFVLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFBO1lBRXJDLE1BQU0sa0JBQWtCLENBQ3RCO2dCQUNFLEdBQUcsRUFBRSxLQUFLLENBQUMsUUFBUTtnQkFDbkIsU0FBUyxFQUFFLEtBQUssQ0FBQyxJQUFJO2dCQUNyQixNQUFNLEVBQUUsS0FBSyxDQUFDLFVBQVUsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsUUFBUTtnQkFDN0QsTUFBTSxFQUFFLFVBQVU7Z0JBQ2xCLE9BQU87YUFDUixFQUNELFVBQVUsQ0FDWCxDQUFBO1lBQ0QsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsdUJBQXVCLFVBQVUsRUFBRSxDQUFDLENBQUE7WUFFNUQsSUFBSSxXQUFXLEVBQUUsQ0FBQztnQkFDaEIsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQTtnQkFDOUQsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQTtnQkFFN0QsTUFBTSxrQkFBa0IsQ0FDdEI7b0JBQ0UsUUFBUSxFQUFFLFdBQVc7b0JBQ3JCLE9BQU8sRUFBRSxVQUFVO29CQUNuQixNQUFNLEVBQUUsVUFBVTtvQkFDbEIsU0FBUztvQkFDVCxhQUFhO29CQUNiLFlBQVk7aUJBQ2IsRUFDRCxVQUFVLENBQ1gsQ0FBQTtnQkFDRCxVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyx1QkFBdUIsVUFBVSxFQUFFLENBQUMsQ0FBQTtnQkFDNUQsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsd0JBQXdCLFNBQVMsRUFBRSxDQUFDLENBQUE7WUFDOUQsQ0FBQztRQUNILENBQUMsQ0FDRixDQUFBO0lBQ0gsQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDO0FBRUQsMEJBQTBCLENBQUMsSUFBSSxDQUFDLENBQUE7QUFDaEMsd0JBQXdCLENBQUMsSUFBSSxDQUFDLENBQUE7QUFDOUIscUJBQXFCLENBQUMsSUFBSSxDQUFDLENBQUE7QUFDM0IsdUJBQXVCLENBQUMsSUFBSSxDQUFDLENBQUE7QUFFN0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUEifQ==