├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── cli.js ├── index.js ├── package-lock.json └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2017": true, 4 | "node": true, 5 | "browser": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "space-before-function-paren": ["error", { 10 | "anonymous": "always", 11 | "named": "never" 12 | }], 13 | "indent": [ 14 | "error", 15 | 2 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "no-trailing-spaces": [ 22 | "error" 23 | ], 24 | "no-console": [ 25 | "warn" 26 | ], 27 | "quotes": [ 28 | "error", 29 | "single" 30 | ], 31 | "semi": [ 32 | "error", 33 | "always" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please List: 15 | 1. What command you're using (if from the command line), or what function call you're using (if using JavaScript). 16 | 2. [If applicable to a specific website] A website that demonstrates this issue (attaching if necessary). If it's complex, try to simplify it; the more minimal, the easier it is to analyze. 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Attachments and Screenshots** 22 | If applicable to a local, publicly unavailable web site, upload a minimal reproducible example. Also if applicable, add screenshots to help explain your problem. 23 | 24 | **Desktop (please complete the following information):** 25 | - OS: [e.g. Ubuntu LTS 20.04] 26 | - Node Version [e.g. v14.0] 27 | - Timecut Version [e.g. v0.1.4] 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE REQUEST]' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Checklist 2 | - [ ] Code changes are only for the relevant bug fix or feature 3 | - [ ] New code lints (via `npm run lint`) without any errors or warnings 4 | - [ ] The corresponding issue is # 5 | 6 | ### Description -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .gitignore 3 | .npmignore 4 | 5 | # from .gitignore 6 | logs 7 | *.log 8 | npm-debug.log* 9 | node_modules/ 10 | .eslintcache 11 | *.tgz 12 | 13 | # Npm utilities 14 | yalc.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018-2022, Steve Tung 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # timecut 2 | 3 | **timecut** is a Node.js program that records smooth videos of web pages that use JavaScript animations. It uses **[timeweb](https://github.com/tungs/timeweb)**, **[timesnap](https://github.com/tungs/timesnap)**, and [puppeteer](https://github.com/GoogleChrome/puppeteer) to open a web page, overwrite its time-handling functions, take snapshots of the web page, and then passes the results to ffmpeg to encode those frames into a video. This allows for slower-than-realtime and/or virtual high-fps capture of frames, while the resulting video is smooth. 4 | 5 | You can run **timecut** from the command line or as a Node.js library. It requires ffmpeg, Node v8.9.0 or higher, and npm. 6 | 7 | To only record screenshots and save them as pictures, see **[timesnap](https://github.com/tungs/timesnap)**. For using virtual time in browser, see **[timeweb](https://github.com/tungs/timeweb)**. 8 | 9 | ## # **timeweb**, **timecut**, and **timesnap** Limitations 10 | **timeweb** (and **timesnap** and **timecut** by extension) only overwrites JavaScript functions and video playback, so pages where changes occur via other means (e.g. through transitions/animations from CSS rules) will likely not render as intended. 11 | 12 | ## Read Me Contents 13 | 14 | * [From the Command Line](#from-cli) 15 | * [Global Install and Use](#cli-global-install) 16 | * [Local Install and Use](#cli-local-install) 17 | * [Command Line *url*](#cli-url-use) 18 | * [Command Line Examples](#cli-examples) 19 | * [Command Line *options*](#cli-options) 20 | * [From Node.js](#from-node) 21 | * [Node Install](#node-install) 22 | * [Node Examples](#node-examples) 23 | * [Node API](#node-api) 24 | * [timecut Modes](#modes) 25 | * [How it works](#how-it-works) 26 | 27 | ## # From the Command Line 28 | 29 | ### # Global Install and Use 30 | 31 | To install: 32 | 33 | Due to [an issue in puppeteer](https://github.com/GoogleChrome/puppeteer/issues/375) with permissions, timecut is not supported for global installation for root. You can configure `npm` to install global packages for a specific user following this guide: https://docs.npmjs.com/getting-started/fixing-npm-permissions#option-two-change-npms-default-directory 34 | 35 | After configuring, run: 36 | ``` 37 | npm install -g timecut 38 | ``` 39 | 40 | To use: 41 | ``` 42 | timecut "url" [options] 43 | ``` 44 | 45 | ### # Local Install and Use 46 | 47 | To install: 48 | 49 | ``` 50 | cd /path/to/installation/directory 51 | npm install timecut 52 | ``` 53 | 54 | To use: 55 | ``` 56 | node /path/to/installation/directory/node_modules/timecut/cli.js "url" [options] 57 | ``` 58 | 59 | *Alternatively*: 60 | 61 | To install: 62 | 63 | 64 | ``` 65 | cd /path/to/installation/directory 66 | git clone https://github.com/tungs/timecut.git 67 | cd timecut 68 | npm install 69 | ``` 70 | 71 | To use: 72 | ``` 73 | node /path/to/installation/directory/timecut/cli.js "url" [options] 74 | ``` 75 | 76 | ### # Command Line *url* 77 | The url can be a web url (e.g. `https://github.com`) or a file path, with relative paths resolving in the current working directory. If no url is specified, defaults to `index.html`. Remember to enclose urls that contain special characters (like `#` and `&`) with quotes. 78 | 79 | ### # Command Line Examples 80 | 81 | **# Default behavior**: 82 | ``` 83 | timecut 84 | ``` 85 | Opens `index.html` in the current working directory, sets the viewport to 800x600, captures at 60 frames per second for 5 virtual seconds (temporarily saving each frame), and saves `video.mp4` with the `yuv420p` pixel format in the current working directory. The defaults may change in the future, so for long-term scripting, it's a good idea to explicitly pass these options, like in the following example. 86 | 87 | **# Setting viewport size, frames per second, duration, mode, and output**: 88 | ``` 89 | timecut index.html --viewport="800,600" --fps=60 --duration=5 \ 90 | --frame-cache --pix-fmt=yuv420p --output=video.mp4 91 | ``` 92 | Equivalent to the current default `timecut` invocation, but with explicit options. Opens `index.html` in the current working directory, sets the viewport to 800x600, captures at 60 frames per second for 5 virtual seconds (temporarily saving each frame), and saves the resulting video using the pixel format `yuv420p` as `video.mp4`. 93 | 94 | **# Using a selector**: 95 | ``` 96 | timecut drawing.html -S "canvas,svg" 97 | ``` 98 | Opens `drawing.html` in the current working directory, crops each frame to the bounding box of the first canvas or svg element, and captures frames using default settings (5 seconds @ 60fps saving to `video.mp4`). 99 | 100 | **# Using offsets**: 101 | ``` 102 | timecut "https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random" \ 103 | -S "#container" \ 104 | --left=20 --top=40 --right=6 --bottom=30 \ 105 | --duration=20 106 | ``` 107 | Opens https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random (note the quotes in the url and selector are necessary because of the `#` and `&`). Crops each frame to the `#container` element, with an additional crop of 20px, 40px, 6px, and 30px for the left, top, right, and bottom, respectively. Captures frames for 20 virtual seconds at 60fps to `video.mp4` in the current working directory. 108 | 109 | ### # Command Line *options* 110 | * # Output: `-O`, `--output` *name* 111 | * Tells ffmpeg to save the video as *name*. Its file extension determines encoding if not explicitly specified. 112 | * # Frame Rate: `-R`, `--fps` *frame rate* 113 | * Frame rate (in frames per virtual second) of capture (default: `60`). 114 | * # Duration: `-d`, `--duration` *seconds* 115 | * Duration of capture, in *seconds* (default: `5`). 116 | * # Frames: `--frames` *count* 117 | * Number of frames to capture. 118 | * # Selector: `-S`, `--selector` "*selector*" 119 | * Crops each frame to the bounding box of the first item found by the [CSS *selector*][CSS selector]. 120 | * # Viewport: `-V`, `--viewport` *dimensions* 121 | * Viewport dimensions, in pixels, followed by optional keys. For example, `800` (for width), or `"800,600"` (for width and height), or `"800,600,deviceScaleFactor=2"` for (width, height, and deviceScaleFactor). When running in Windows, quotes may be necessary for parsing commas. For a list of optional keys, see [`config.viewport`](#js-config-viewport). 122 | * # Frame Cache: `--frame-cache` *[directory]* 123 | * Saves each frame temporarily to disk before ffmpeg processes it. If *directory* is not specified, temporarily creates one in the current working directory. Enabled by default. See [cache frame mode](#cache-frame-mode). 124 | * # Pipe Mode: `--pipe-mode` 125 | * Experimental. Pipes frames directly to ffmpeg, without saving to disk. See [pipe mode](#pipe-mode). 126 | * # Canvas Mode: `--canvas-capture-mode` *\[format\]* 127 | * Experimental. Captures images from canvas data instead of screenshots. See [canvas capture mode](#canvas-capture-mode). Can provide an optional image format (e.g. `png`), otherwise it uses the saved image's extension, or defaults to `png` if the format is not specified or supported. Can prefix the format with `immediate:` (e.g. `immediate:png`) to immediately capture pixel data after rendering, which is sometimes needed for some WebGL renderers. Specify the canvas [using the `--selector` option](#cli-options-selector), otherwise it defaults to the first canvas in the document. 128 | * # Start: `-s`, `--start` *n seconds* 129 | * Runs code for n virtual seconds before saving any frames (default: `0`). 130 | * # X Offset: `-x`, `--x-offset` *pixels* 131 | * X offset of capture, in pixels (default: `0`). 132 | * # Y Offset: `-y`, `--y-offset` *pixels* 133 | * Y offset of capture, in pixels (default: `0`). 134 | * # Width: `-W`, `--width` *pixels* 135 | * Width of capture, in pixels. 136 | * # Height: `-H`, `--height` *pixels* 137 | * Height of capture, in pixels. 138 | * # No Even Width Rounding: `--no-round-to-even-width` 139 | * Disables automatic rounding of capture width up to the nearest even number. 140 | * # No Even Height Rounding: `--no-round-to-even-height` 141 | * Disables automatic rounding of capture height up to the nearest even number. 142 | * # Transparent Background: `--transparent-background` 143 | * Allows background to be transparent if there is no background styling. Only works if the output video format supports transparency. 144 | * # Left: `-l`, `--left` *pixels* 145 | * Left edge of capture, in pixels. Equivalent to `--x-offset`. 146 | * # Right: `-r`, `--right` *pixels* 147 | * Right edge of capture, in pixels. Ignored if `width` is specified. 148 | * # Top: `-t`, `--top` *pixels* 149 | * Top edge of capture, in pixels. Equivalent to `--y-offset`. 150 | * # Bottom: `-b`, `--bottom` *pixels* 151 | * Bottom edge of capture, in pixels. Ignored if `height` is specified. 152 | * # Unrandomize: `-u`, `--unrandomize` *\[seeds\]* 153 | * Overwrites `Math.random` with a seeded pseudorandom number generator. Can provide optional seeds as up to four comma separated integers (e.g. `--unrandomize 2,3,5,7` or `--unrandomize 42`). If `seeds` is `random-seed` (i.e. `--unrandomize random-seed`), a random seed will be generated, displayed (if not in quiet mode), and used. If `seeds` is not provided, it uses the seeds `10,0,20,0`. 154 | * # Executable Path: `--executable-path` *path* 155 | * Uses the Chromium/Chrome instance at *path* for puppeteer. 156 | * # ffmpeg Path: `--ffmpeg-path` *path* 157 | * Uses the ffmpeg *path* for running ffmpeg. 158 | * # Puppeteer Launch Arguments: `-L`, `--launch-arguments` *arguments* 159 | * Arguments to pass to Puppeteer/Chromium, enclosed in quotes. Example: `--launch-arguments="--single-process"`. A list of arguments can be found [here](https://peter.sh/experiments/chromium-command-line-switches). 160 | * # No Headless: `--no-headless` 161 | * Runs Chromium/Chrome in windowed mode. 162 | * # Screenshot Type: `--screenshot-type` *type* 163 | * Output image format for the screenshots. By default, `png` is used. `jpeg` is also available. 164 | * # Screenshot Quality: `--screenshot-quality` *number* 165 | * Quality level between 0 to 1 for lossy screenshots. Defaults to 0.92 when in [canvas capture mode](#cli-options-canvas-capture-mode) and 0.8 otherwise. 166 | * # Extra input options: `-e`, `--input-options` *options* 167 | * Extra arguments for ffmpeg input, enclosed in quotes. Example: `--input-options="-framerate 30"` 168 | * # Extra output options: `-E`, `--output-options` *options* 169 | * Extra arguments for ffmpeg output, enclosed in quotes. Example: `--output-options="-vf scale=320:240"` 170 | * # Pixel Format: `--pix-fmt` *pixel format* 171 | * Pixel format for output video (default: `yuv420p`). 172 | * # Start Delay: `--start-delay` *n seconds* 173 | * Waits *n real seconds* after loading the page before starting to capture. 174 | * # Keep Frames: `--keep-frames` 175 | * Doesn't delete frames after processing them. Doesn't do anything in pipe mode. 176 | * # Quiet: `-q`, `--quiet` 177 | * Suppresses console logging. 178 | * # Stop Function Name: `--stop-function-name` *function name* 179 | * Creates a function with *function name* that the client web page can call to stop capturing. For instance, `--stop-function-name=stopCapture` could be called in the client, via `stopCapture()`. 180 | * # Version: `-v`, `--version` 181 | * Displays version information. Immediately exits. 182 | * # Help: `-h`, `--help` 183 | * Displays command line options. Immediately exits. 184 | 185 | ## # From Node.js 186 | **timecut** can also be included as a library inside Node.js programs. 187 | 188 | ### # Node Install 189 | ``` 190 | npm install timecut --save 191 | ``` 192 | 193 | ### # Node Examples 194 | 195 | **# Basic Use:** 196 | ```node 197 | const timecut = require('timecut'); 198 | timecut({ 199 | url: 'https://tungs.github.io/amuse/truchet-tiles/#autoplay=true&switchStyle=random', 200 | viewport: { 201 | width: 800, // sets the viewport (window size) to 800x600 202 | height: 600 203 | }, 204 | selector: '#container', // crops each frame to the bounding box of '#container' 205 | left: 20, top: 40, // further crops the left by 20px, and the top by 40px 206 | right: 6, bottom: 30, // and the right by 6px, and the bottom by 30px 207 | fps: 30, // saves 30 frames for each virtual second 208 | duration: 20, // for 20 virtual seconds 209 | output: 'video.mp4' // to video.mp4 of the current working directory 210 | }).then(function () { 211 | console.log('Done!'); 212 | }); 213 | ``` 214 | 215 | **# Multiple pages:** 216 | ```node 217 | const timecut = require('timecut'); 218 | var pages = [ 219 | { 220 | url: 'https://tungs.github.io/amuse/truchet-tiles/#autoplay=true', 221 | output: 'truchet-tiles.mp4', 222 | selector: '#container' 223 | }, { 224 | url: 'https://breathejs.org/examples/Drawing-US-Counties.html', 225 | output: 'counties.mp4', 226 | selector: null // with no selector, it defaults to the viewport dimensions 227 | } 228 | ]; 229 | (async () => { 230 | for (let page of pages) { 231 | await timecut({ 232 | url: page.url, 233 | output: page.output, 234 | selector: page.selector, 235 | viewport: { 236 | width: 800, 237 | height: 600 238 | }, 239 | duration: 20 240 | }); 241 | } 242 | })(); 243 | ``` 244 | 245 | ### # Node API 246 | 247 | The Node API is structured similarly to the command line options, but there are a few options for the Node API that are not accessible through the command line interface: [`config.logToStdErr`](#js-config-log-to-std-err), [`config.navigatePageToURL`](#js-config-navigate-page-to-url), [`config.preparePage`](#js-config-prepare-page), [`config.preparePageForScreenshot`](#js-config-prepare-page-for-screenshot), [`config.outputStream`](#js-config-output-stream), [`config.logger`](#js-config-logger), and certain [`config.viewport`](#js-config-viewport) properties. 248 | 249 | **timecut(config)** 250 | * # `config` <[Object][]> 251 | * # `url` <[string][]> The url to load. It can be a web url, like `https://github.com` or a file path, with relative paths resolving in the current working directory (default: `index.html`). 252 | * # `output` <[string][]> Tells ffmpeg to save the video as *name*. Its file extension determines encoding if not explicitly specified. Default name: `video.mp4`. 253 | * # `fps` <[number][]> frame rate, in frames per virtual second, of capture (default: `60`). 254 | * # `duration` <[number][]> Duration of capture, in seconds (default: `5`). 255 | * # `frames` <[number][]> Number of frames to capture. Overrides default fps or default duration. 256 | * # `selector` <[string][]> Crops each frame to the bounding box of the first item found by the specified [CSS selector][]. 257 | * # `frameCache` <[string][]|[boolean][]> Saves each frame temporarily to disk before ffmpeg processes it. If `config.frameCache` is a string, uses that as the directory to save the temporary files. If `config.frameCache` is a boolean `true`, temporarily creates a directory in the current working directory. See [cache frame mode](#cache-frame-mode). 258 | * # `pipeMode` <[boolean][]> Experimental. If set to `true`, pipes frames directly to ffmpeg, without saving to disk. See [pipe mode](#pipe-mode). 259 | * # `viewport` <[Object][]> 260 | * # `width` <[number][]> Width of viewport, in pixels (default: `800`). 261 | * # `height` <[number][]> Height of viewport, in pixels (default: `600`). 262 | * # `deviceScaleFactor` <[number][]> Device scale factor (default: `1`). 263 | * # `isMobile` <[boolean][]> Specifies whether the `meta viewport` tag should be used (default: `false`). 264 | * # `hasTouch` <[boolean][]> Specifies whether the viewport supports touch (default: `false`). 265 | * # `isLandscape` <[boolean][]> Specifies whether the viewport is in landscape mode (default: `false`). 266 | * # `canvasCaptureMode` <[boolean][] | [string][]> 267 | * Experimental. Captures images from canvas data instead of screenshots. See [canvas capture mode](#canvas-capture-mode). Can provide an optional image format (e.g. `png`), otherwise it uses the saved image's extension, or defaults to `png` if the format is not specified or supported. Can prefix the format with `immediate:` (e.g. `immediate:png`) to immediately capture pixel data after rendering, which is sometimes needed for some WebGL renderers. Specify the canvas by [setting `config.selector`](#js-config-selector), otherwise it defaults to the first canvas in the document. 268 | * # `start` <[number][]> Runs code for `config.start` virtual seconds before saving any frames (default: `0`). 269 | * # `xOffset` <[number][]> X offset of capture, in pixels (default: `0`). 270 | * # `yOffset` <[number][]> Y offset of capture, in pixels (default: `0`). 271 | * # `width` <[number][]> Width of capture, in pixels. 272 | * # `height` <[number][]> Height of capture, in pixels. 273 | * # `transparentBackground` <[boolean][]> Allows background to be transparent if there is no background styling. Only works if the output video format supports transparency. 274 | * # `roundToEvenWidth` <[boolean][]> Rounds capture width up to the nearest even number (default: `true`). 275 | * # `roundToEvenHeight` <[boolean][]> Rounds capture height up to the nearest even number (default: `true`). 276 | * # `left` <[number][]> Left edge of capture, in pixels. Equivalent to `config.xOffset`. 277 | * # `right` <[number][]> Right edge of capture, in pixels. Ignored if `config.width` is specified. 278 | * # `top` <[number][]> Top edge of capture, in pixels. Equivalent to `config.yOffset`. 279 | * # `bottom` <[number][]> Bottom edge of capture, in pixels. Ignored if `config.height` is specified. 280 | * # `unrandomize` <[boolean][] | [string][] | [number][] | [Array][]<[number][]>> Overwrites `Math.random` with a seeded pseudorandom number generator. If it is a number, an array of up to four numbers, or a string of up to four comma separated numbers, then those values are used as the initial seeds. If it is true, then the default seed is used. If it is the string 'random-seed', a random seed will be generated, displayed (if quiet mode is not enabled), and used. 281 | * # `executablePath` <[string][]> Uses the Chromium/Chrome instance at `config.executablePath` for puppeteer. 282 | * # `ffmpegPath` <[string][]> Uses the ffmpeg *path* for running ffmpeg. 283 | * # `launchArguments` <[Array][] <[string][]>> Extra arguments for Puppeteer/Chromium. Example: `['--single-process']`. A list of arguments can be found [here](https://peter.sh/experiments/chromium-command-line-switches). 284 | * # `headless` <[boolean][]> Runs puppeteer in headless (nonwindowed) mode (default: `true`). 285 | * # `screenshotType` <[string][]> Output image format for the screenshots. By default, `'png'` is used. `'jpeg'` is also available. 286 | * # `screenshotQuality` <[number][]> Quality level between 0 to 1 for lossy screenshots. Defaults to 0.92 when in [canvas capture mode](#js-config-canvas-capture-mode) and 0.8 otherwise. 287 | * # `inputOptions` <[Array][] <[string][]>> Extra arguments for ffmpeg input. Example: `['-framerate', '30']` 288 | * # `outputOptions` <[Array][] <[string][]>> Extra arguments for ffmpeg output. Example: `['-vf', 'scale=320:240']` 289 | * # `pixFmt` <[string][]> Pixel format for output video (default: `yuv420p`). 290 | * # `startDelay` <[number][]> Waits `config.startDelay` real seconds after loading before starting (default: `0`). 291 | * # `keepFrames` <[boolean][]> If set to true, doesn't delete frames after processing them. Doesn't do anything in pipe mode. 292 | * # `quiet` <[boolean][]> Suppresses console logging. 293 | * # `logger` <[function][](...[Object][])> Replaces console logging with a particular function. The passed arguments are the same as those to `console.log` (in this case, usually one string). 294 | * # `logToStdErr` <[boolean][]> Logs to stderr instead of stdout. Doesn't do anything if `config.quiet` is set to true. 295 | * # `stopFunctionName` <[string][]> *function name* that the client web page can call to stop capturing. For instance, `'stopCapture'` could be called in the client, via `stopCapture()`. 296 | * # `navigatePageToURL` <[function][]([Object][])> A function that navigates a puppeteer page to a URL, overriding the default navigation to a URL. The function should return a promise that resolves once the page is finished navigating. The function is passed the following object: 297 | * # `page` <[Page][]> the puppeteer page 298 | * # `url` <[string][]> the url to navigate to 299 | * # `preparePage` <[function][]([Page][])> A setup function that will be called one time before taking screenshots. If it returns a promise, capture will be paused until the promise resolves. 300 | * `page` <[Page][]> The puppeteer instance of the page being captured. 301 | * # `preparePageForScreenshot` <[function][]([Page][], [number][], [number][])> A setup function that will be called before each screenshot. If it returns a promise, capture will be paused until the promise resolves. 302 | * `page` <[Page][]> The puppeteer instance of the page being captured. 303 | * `frameNumber` <[number][]> The current frame number (1 based). 304 | * `totalFrames` <[number][]> The total number of frames. 305 | * # `outputStream` <[stream][]()> A node stream to write data to from ffmpeg 306 | * # `outputStreamOptions` <[Object][]> Optional configuration object when using [`config.outputStream`](#js-config-output-stream) 307 | * # `format` <[string][]> Format of piped output. Defaults to `'mp4'` if undefined. 308 | * # `movflags` <[string][]> String representing MOV muxer flags to pass via `-movflags` argument. Defaults to `'frag_keyframe+empty_moov+faststart'` if undefined. 309 | * # returns: <[Promise][]> resolves after all the frames have been captured. 310 | 311 | ## # **timecut** Modes 312 | ### # Capture Modes 313 | **timecut** can capture frames to using one of two modes: 314 | * # **Screenshot capture mode** (default) uses puppeteer's built-in API to take screenshots of Chromium/Chrome windows. It can capture most parts of a webpage (e.g. div, svg, canvas) as they are rendered on the webpage. It can crop images, round to even widths/heights, but it usually runs slower than canvas capture mode. 315 | * # **Canvas capture mode** (experimental) directly copies data from a canvas element and is often faster than using screenshot capture mode. If the background of the canvas is transparent, it may show up as transparent or black depending on the captured image format and the output video format. Configuration options that adjust the crop and round to an even width/height do not currently have an effect. To use this mode, [use the `--canvas-capture-mode` option from the command line](#cli-options-canvas-capture-mode) or [set `config.canvasCaptureMode` from Node.js](#js-config-canvas-capture-mode). Also specify the canvas using a css selector, [using the `--selector` option from the command line](#cli-options-selector) or [setting `config.selector` from Node.js](#js-config-selector), otherwise it uses the first canvas element. 316 | ### # Frame Transfer Modes 317 | **timecut** can pass frames to ffmpeg using one of two modes: 318 | * # **Cache frame mode** stores each frame temporarily before running ffmpeg on all of the images. This mode can use a lot of temporary disk space (hundreds of megabytes per second of recorded time), but takes up less memory and is more stable than [pipe mode](#pipe-mode). This is currently enabled by default, though it may change in the future. To explicitly use this mode, [use the `--frame-cache` option from the command line](#cli-options-frame-cache) or [set `config.frameCache` from Node.js](#js-config-frame-cache) to `true` or to a directory name. 319 | * # **Pipe mode** (experimental) pipes each frame directly to `ffmpeg`, without saving each frame. This takes up less temporary space than [cache frame mode](#cache-frame-mode), but it currently has some observed stability issues. To use this mode, [use the `--pipe-mode` option from the command line](#cli-options-pipe-mode) or [set `config.pipeCache` to `true` from Node.js](#js-config-pipe-mode). If you run into issues, you may want to try [cache frame mode](#cache-frame-mode) or to install and use **timesnap** and [pipe it directly to ffmpeg](https://github.com/tungs/timesnap#cli-example-piping). Both alternative implementations seem more stable than the current pipe mode. 320 | 321 | ## # How it works 322 | **timecut** uses **[timesnap](https://github.com/tungs/timesnap)** to record frames to send to `ffmpeg`. **timesnap** uses puppeteer's `page.evaluateOnNewDocument` feature to automatically overwrite a page's native time-handling JavaScript functions and objects (`new Date()`, `Date.now`, `performance.now`, `requestAnimationFrame`, `setTimeout`, `setInterval`, `cancelAnimationFrame`, `cancelTimeout`, and `cancelInterval`) to custom ones that use a virtual timeline, allowing for JavaScript computation to complete before taking a screenshot. 323 | 324 | This work was inspired by [a talk by Noah Veltman](https://github.com/veltman/d3-unconf), who described altering a document's `Date.now` and `performance.now` functions to refer to a virtual time and using `puppeteer` to change that virtual time and take snapshots. 325 | 326 | [Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object 327 | [Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array 328 | [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 329 | [string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type 330 | [number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type 331 | [boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type 332 | [function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions 333 | [CSS selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors 334 | [Page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page 335 | [stream]: https://nodejs.org/api/stream.html 336 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * BSD 3-Clause License 5 | * 6 | * Copyright (c) 2018-2022, Steve Tung 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright notice, this 13 | * list of conditions and the following disclaimer. 14 | * 15 | * * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * * Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived from 21 | * this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | const commander = require('commander'); 36 | const recorder = require('./index.js'); 37 | const packageInfo = require('./package.json'); 38 | 39 | var errors = []; 40 | commander 41 | .version(packageInfo.version, '-v, --version') 42 | .usage(' [options]') 43 | .option('-O, --output ', 'Name of output (default: video.mp4)') 44 | .option('-R, --fps ', 'Frames per second to capture (default: 60)', parseFloat) 45 | .option('-d, --duration ', 'Duration of capture, in seconds (default: 5)', parseFloat) 46 | .option('-T, --threads ', 'Number of threads to open (default: 1)', parseInt) 47 | .option('--frames ', 'Number of frames to capture', parseInt) 48 | .option('-S, --selector ', 'CSS Selector of item to capture') 49 | .option('-V, --viewport ', 'Viewport dimensions, in pixels (e.g. 800,600)', function (str) { 50 | var viewportComponents = str.split(','); 51 | var dims = viewportComponents.filter(c => !c.includes('=')).map(c => parseInt(c)); 52 | var parsers = { 53 | deviceScaleFactor: JSON.parse, 54 | isMobile: JSON.parse, 55 | hasTouch: JSON.parse, 56 | width: parseInt, 57 | height: parseInt, 58 | isLandscape: JSON.parse 59 | }; 60 | var viewport = { 61 | width: dims[0], 62 | height: dims[1] 63 | }; 64 | viewportComponents.filter(c => c.includes('=')).forEach(c => { 65 | var components = c.split('='); 66 | var key = components[0].trim(); 67 | if (!parsers[key]) { 68 | errors.push('Unknown viewport configuration key ' + key); 69 | } else { 70 | viewport[key] = parsers[key](components[1]); 71 | } 72 | }); 73 | return viewport; 74 | }) 75 | .option('--transparent-background', 'Allow transparent backgrounds (only works for certain encodings)') 76 | .option('--frame-cache [directory]', 'Save frames in a temporary directory before processing') 77 | .option('-e, --input-options ', 'Extra arguments for ffmpeg input', function (str) { 78 | // TODO: make a more sophisticated parser for options that can handle quote marks 79 | return str.split(' '); 80 | }) 81 | .option('-E, --output-options ', 'Extra arguments for ffmpeg output', function (str) { 82 | // TODO: make a more sophisticated parser for options that can handle quote marks 83 | return str.split(' '); 84 | }) 85 | .option('-p, --pix-fmt ', 'Pixel format of output (default: yuv420p)') 86 | .option('-P, --pipe-mode', 'Pipe directly to ffmpeg (experimental)') 87 | .option('-s, --start ', 'Runs code for n virtual seconds before saving any frames.', parseFloat, 0) 88 | .option('-x, --x-offset ', 'X offset of capture, in pixels', parseFloat, 0) 89 | .option('-y, --y-offset ', 'Y offset of capture, in pixels', parseFloat, 0) 90 | .option('-W, --width ', 'Width of capture, in pixels', parseInt) 91 | .option('-H, --height ', 'Height of capture, in pixels', parseInt) 92 | .option('-l, --left ', 'left edge of capture, in pixels. Equivalent to --x-offset', parseInt) 93 | .option('-r, --right ', 'right edge of capture, in pixels', parseInt) 94 | .option('-t, --top ', 'top edge of capture, in pixels. Equivalent to --y-offset', parseInt) 95 | .option('-b, --bottom ', 'bottom edge of capture, in pixels', parseInt) 96 | .option('--start-delay ', 'Wait n real seconds after loading.', parseFloat, 0) 97 | .option('-u, --unrandomize [seed]', 'Overwrite Math.random() with a PRNG with up to 4 optional, comma-separated integer seeds') 98 | .option('--canvas-capture-mode [type]', '(experimental) Switches to canvas mode, capturing the canvas selected by --selector as image type (default: png)') 99 | .option('--no-round-to-even-width', 'Disables automatic rounding of capture width up to the nearest even number.') 100 | .option('--no-round-to-even-height', 'Disables automatic rounding of capture height up to the nearest even number.') 101 | .option('-q, --quiet', 'Suppresses console logging') 102 | .option('--executable-path ', 'Uses Chromium/Chrome application at specified path for puppeteer') 103 | .option('--ffmpeg-path ', 'Uses ffmpeg at specified path') 104 | .option('-L, --launch-arguments ', 'Custom launch arguments for Puppeteer browser', function (str) { 105 | // TODO: make a more sophisticated parser for options that can handle quote marks 106 | return str.split(' '); 107 | }) 108 | .option('--stop-function-name ', 'Allows client page to call function name to stop capture') 109 | .option('--no-headless', 'Chromium/Chrome runs in a window instead of headless mode') 110 | .option('--screenshot-type ', 'Output image format for screenshots, either png or jpeg') 111 | .option('--screenshot-quality ', 'The quality level for lossy screenshots', parseFloat) 112 | .option('--keep-frames', 'Doesn\'t delete frames after processing them. Doesn\'t do anything in pipe mode') 113 | .parse(process.argv); 114 | 115 | commander.url = commander.args[0] || 'index.html'; 116 | 117 | if (errors.length && !commander.quiet) { 118 | errors.forEach(e => { 119 | // eslint-disable-next-line no-console 120 | console.error(e); 121 | }); 122 | } 123 | 124 | recorder(commander); 125 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2018-2022, Steve Tung 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const timesnap = require('timesnap'); 34 | const path = require('path'); 35 | const fs = require('fs'); 36 | const spawn = require('child_process').spawn; 37 | const cpus = require('os').cpus().length; 38 | const defaultFPS = 60; 39 | const defaultThreads = 1; 40 | const defaultDuration = 5; 41 | 42 | const makeFileDirectoryIfNeeded = function (filepath) { 43 | var dir = path.parse(filepath).dir, ind, currDir; 44 | var directories = dir.split(path.sep); 45 | for (ind = 1; ind <= directories.length; ind++) { 46 | currDir = directories.slice(0, ind).join(path.sep); 47 | if (currDir && !fs.existsSync(currDir)) { 48 | fs.mkdirSync(currDir); 49 | } 50 | } 51 | }; 52 | 53 | const deleteFolder = function (dir) { 54 | fs.readdirSync(dir).forEach(function (file) { 55 | fs.unlinkSync(path.join(dir, file)); 56 | }); 57 | fs.rmdirSync(dir); 58 | }; 59 | 60 | const argumentArrayContains = function (args, item) { 61 | return args.reduce(function (accumulator, currentValue) { 62 | return accumulator || 63 | (currentValue === item) || 64 | currentValue.startsWith(item + '='); 65 | }, false); 66 | }; 67 | 68 | module.exports = async function (config) { 69 | config = Object.assign({ 70 | roundToEvenWidth: true, 71 | roundToEvenHeight: true, 72 | url: 'index.html', 73 | pixFmt: 'yuv420p' 74 | }, config || {}); 75 | var output = path.resolve(process.cwd(), config.output || 'video.mp4'); 76 | var ffmpegArgs; 77 | var ffmpegPath = config.ffmpegPath || 'ffmpeg'; 78 | var inputOptions = config.inputOptions || []; 79 | var outputOptions = config.outputOptions || []; 80 | var frameDirectory = config.tempDir || config.frameDir; 81 | var fps; 82 | var threads; 83 | var frameMode = config.frameCache || !config.pipeMode; 84 | var pipeMode = config.pipeMode; 85 | var processError; 86 | var outputPattern; 87 | var convertProcess, processPromise; 88 | var extension; 89 | var screenshotType = (config.screenshotType || 'png'); 90 | if (frameMode) { 91 | if (!frameDirectory) { 92 | frameDirectory = 'timecut-' + (config.keepFrames ? 'frames-' : 'temp-') + (new Date()).getTime(); 93 | } 94 | if (typeof config.frameCache === 'string') { 95 | frameDirectory = path.join(config.frameCache, frameDirectory); 96 | } 97 | frameDirectory = path.resolve(path.parse(output).dir, frameDirectory); 98 | extension = '.' + screenshotType; 99 | outputPattern = path.resolve(frameDirectory, 'image-%09d' + extension); 100 | } else { 101 | outputPattern = ''; 102 | } 103 | var timesnapConfig = Object.assign({}, config, { 104 | output: '', 105 | outputPattern: outputPattern 106 | }); 107 | 108 | if (config.fps) { 109 | fps = config.fps; 110 | } else if (config.frames && config.duration) { 111 | fps = config.frames / config.duration; 112 | } else { 113 | fps = defaultFPS; 114 | } 115 | 116 | threads = config.threads || defaultThreads; 117 | if (threads > cpus) { 118 | threads = cpus; 119 | } 120 | 121 | const log = function () { 122 | if (!config.quiet) { 123 | // eslint-disable-next-line no-console 124 | console.log.apply(this, arguments); 125 | } 126 | }; 127 | 128 | var makeProcessPromise = function () { 129 | makeFileDirectoryIfNeeded(output); 130 | var input; 131 | if (pipeMode) { 132 | input = 'pipe:0'; 133 | } else { 134 | input = outputPattern; 135 | } 136 | ffmpegArgs = inputOptions; 137 | if (!argumentArrayContains(inputOptions, '-framerate')) { 138 | ffmpegArgs = ffmpegArgs.concat(['-framerate', fps]); 139 | } 140 | 141 | if (pipeMode && (screenshotType === 'jpeg' || screenshotType === 'jpg')) { 142 | // piping jpegs with the other method can cause an error 143 | // this is intended to fix that 144 | ffmpegArgs = ffmpegArgs.concat(['-f', 'image2pipe', '-vcodec', 'mjpeg', '-i', '-']); 145 | } else { 146 | ffmpegArgs = ffmpegArgs.concat(['-i', input]); 147 | } 148 | 149 | if (!argumentArrayContains(outputOptions, '-pix_fmt') && config.pixFmt) { 150 | ffmpegArgs = ffmpegArgs.concat(['-pix_fmt', config.pixFmt]); 151 | } 152 | ffmpegArgs = ffmpegArgs.concat(outputOptions); 153 | if (config.outputStream) { 154 | let outputStreamOptions = config.outputStreamOptions || {}; 155 | let outputStreamArgs = ['-f', outputStreamOptions.format || 'mp4']; 156 | let movflags = outputStreamOptions.movflags; 157 | if (movflags === undefined) { 158 | movflags = 'frag_keyframe+empty_moov+faststart'; 159 | } 160 | if (movflags) { 161 | outputStreamArgs = outputStreamArgs.concat(['-movflags', movflags]); 162 | } 163 | ffmpegArgs = ffmpegArgs.concat(outputStreamArgs).concat(['pipe:1']); 164 | } else { 165 | // by default just write out the file 166 | // -y writes over existing files 167 | ffmpegArgs = ffmpegArgs.concat(['-y', output]); 168 | } 169 | 170 | convertProcess = spawn(ffmpegPath, ffmpegArgs); 171 | convertProcess.stderr.setEncoding('utf8'); 172 | convertProcess.stderr.on('data', function (data) { 173 | log(data); 174 | }); 175 | return new Promise(function (resolve, reject) { 176 | convertProcess.on('close', function () { 177 | resolve(); 178 | }); 179 | convertProcess.on('error', function (err) { 180 | processError = err; 181 | reject(err); 182 | }); 183 | convertProcess.stdin.on('error', function (err) { 184 | processError = err; 185 | reject(err); 186 | }); 187 | if (config.outputStream) { 188 | convertProcess.stdout.on('error', function (err) { 189 | processError = err; 190 | reject(err); 191 | }); 192 | convertProcess.stdout.pipe(config.outputStream); 193 | } 194 | }); 195 | }; 196 | 197 | if (pipeMode) { 198 | processPromise = makeProcessPromise(); 199 | timesnapConfig.frameProcessor = function (buffer) { 200 | if (processError) { 201 | throw processError; 202 | } 203 | convertProcess.stdin.write(buffer); 204 | }; 205 | } 206 | 207 | var overallError; 208 | try { 209 | if (threads === 1) { 210 | await timesnap(timesnapConfig); 211 | } else { 212 | var progress = []; 213 | var framesLeft = config.frames || config.duration * fps || defaultDuration * fps; 214 | var startFrame = 0; 215 | while (threads >= 1) { 216 | let frameLength = Math.floor(framesLeft / threads--); 217 | let frameStart = startFrame; 218 | let frameEnd = frameStart + frameLength; 219 | let threadConfig = Object.assign({} , timesnapConfig, { 220 | shouldSkipFrame({ frameCount }) { 221 | // frameCount is 1 based 222 | return frameCount <= frameStart || frameCount > frameEnd; 223 | } 224 | }); 225 | progress.push(timesnap(threadConfig)); 226 | startFrame = frameEnd; 227 | framesLeft -= frameLength; 228 | } 229 | await Promise.all(progress); 230 | } 231 | if (convertProcess) { 232 | convertProcess.stdin.end(); 233 | } 234 | if (processPromise) { 235 | await processPromise; 236 | } else { 237 | await makeProcessPromise(); 238 | } 239 | } catch (err) { 240 | overallError = err; 241 | log(err); 242 | } 243 | if (frameMode && !config.keepFrames) { 244 | deleteFolder(frameDirectory); 245 | } 246 | if (overallError) { 247 | throw overallError; 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timecut", 3 | "version": "0.3.4-prerelease", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.12.11", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", 10 | "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.10.4" 14 | } 15 | }, 16 | "@babel/helper-validator-identifier": { 17 | "version": "7.19.1", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 19 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", 20 | "dev": true 21 | }, 22 | "@babel/highlight": { 23 | "version": "7.18.6", 24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 25 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 26 | "dev": true, 27 | "requires": { 28 | "@babel/helper-validator-identifier": "^7.18.6", 29 | "chalk": "^2.0.0", 30 | "js-tokens": "^4.0.0" 31 | }, 32 | "dependencies": { 33 | "chalk": { 34 | "version": "2.4.2", 35 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 36 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 37 | "dev": true, 38 | "requires": { 39 | "ansi-styles": "^3.2.1", 40 | "escape-string-regexp": "^1.0.5", 41 | "supports-color": "^5.3.0" 42 | } 43 | }, 44 | "escape-string-regexp": { 45 | "version": "1.0.5", 46 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 47 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 48 | "dev": true 49 | } 50 | } 51 | }, 52 | "@eslint/eslintrc": { 53 | "version": "0.4.3", 54 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", 55 | "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", 56 | "dev": true, 57 | "requires": { 58 | "ajv": "^6.12.4", 59 | "debug": "^4.1.1", 60 | "espree": "^7.3.0", 61 | "globals": "^13.9.0", 62 | "ignore": "^4.0.6", 63 | "import-fresh": "^3.2.1", 64 | "js-yaml": "^3.13.1", 65 | "minimatch": "^3.0.4", 66 | "strip-json-comments": "^3.1.1" 67 | } 68 | }, 69 | "@humanwhocodes/config-array": { 70 | "version": "0.5.0", 71 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", 72 | "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", 73 | "dev": true, 74 | "requires": { 75 | "@humanwhocodes/object-schema": "^1.2.0", 76 | "debug": "^4.1.1", 77 | "minimatch": "^3.0.4" 78 | } 79 | }, 80 | "@humanwhocodes/object-schema": { 81 | "version": "1.2.1", 82 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 83 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 84 | "dev": true 85 | }, 86 | "@types/mime-types": { 87 | "version": "2.1.1", 88 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", 89 | "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==" 90 | }, 91 | "acorn": { 92 | "version": "7.4.1", 93 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 94 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 95 | "dev": true 96 | }, 97 | "acorn-jsx": { 98 | "version": "5.3.2", 99 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 100 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 101 | "dev": true 102 | }, 103 | "agent-base": { 104 | "version": "5.1.1", 105 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", 106 | "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" 107 | }, 108 | "ajv": { 109 | "version": "6.12.6", 110 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 111 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 112 | "dev": true, 113 | "requires": { 114 | "fast-deep-equal": "^3.1.1", 115 | "fast-json-stable-stringify": "^2.0.0", 116 | "json-schema-traverse": "^0.4.1", 117 | "uri-js": "^4.2.2" 118 | } 119 | }, 120 | "ansi-colors": { 121 | "version": "4.1.3", 122 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", 123 | "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", 124 | "dev": true 125 | }, 126 | "ansi-regex": { 127 | "version": "5.0.1", 128 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 129 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 130 | "dev": true 131 | }, 132 | "ansi-styles": { 133 | "version": "3.2.1", 134 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 135 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 136 | "dev": true, 137 | "requires": { 138 | "color-convert": "^1.9.0" 139 | } 140 | }, 141 | "argparse": { 142 | "version": "1.0.10", 143 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 144 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 145 | "dev": true, 146 | "requires": { 147 | "sprintf-js": "~1.0.2" 148 | }, 149 | "dependencies": { 150 | "sprintf-js": { 151 | "version": "1.0.3", 152 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 153 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 154 | "dev": true 155 | } 156 | } 157 | }, 158 | "astral-regex": { 159 | "version": "2.0.0", 160 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", 161 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", 162 | "dev": true 163 | }, 164 | "async-limiter": { 165 | "version": "1.0.1", 166 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 167 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 168 | }, 169 | "balanced-match": { 170 | "version": "1.0.2", 171 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 172 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 173 | }, 174 | "brace-expansion": { 175 | "version": "1.1.11", 176 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 177 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 178 | "requires": { 179 | "balanced-match": "^1.0.0", 180 | "concat-map": "0.0.1" 181 | } 182 | }, 183 | "buffer-crc32": { 184 | "version": "0.2.13", 185 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 186 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" 187 | }, 188 | "buffer-from": { 189 | "version": "1.1.2", 190 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 191 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 192 | }, 193 | "callsites": { 194 | "version": "3.1.0", 195 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 196 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 197 | "dev": true 198 | }, 199 | "chalk": { 200 | "version": "4.1.2", 201 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 202 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 203 | "dev": true, 204 | "requires": { 205 | "ansi-styles": "^4.1.0", 206 | "supports-color": "^7.1.0" 207 | }, 208 | "dependencies": { 209 | "ansi-styles": { 210 | "version": "4.3.0", 211 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 212 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 213 | "dev": true, 214 | "requires": { 215 | "color-convert": "^2.0.1" 216 | } 217 | }, 218 | "color-convert": { 219 | "version": "2.0.1", 220 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 221 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 222 | "dev": true, 223 | "requires": { 224 | "color-name": "~1.1.4" 225 | } 226 | }, 227 | "color-name": { 228 | "version": "1.1.4", 229 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 230 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 231 | "dev": true 232 | }, 233 | "has-flag": { 234 | "version": "4.0.0", 235 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 236 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 237 | "dev": true 238 | }, 239 | "supports-color": { 240 | "version": "7.2.0", 241 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 242 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 243 | "dev": true, 244 | "requires": { 245 | "has-flag": "^4.0.0" 246 | } 247 | } 248 | } 249 | }, 250 | "color-convert": { 251 | "version": "1.9.3", 252 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 253 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 254 | "dev": true, 255 | "requires": { 256 | "color-name": "1.1.3" 257 | } 258 | }, 259 | "color-name": { 260 | "version": "1.1.3", 261 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 262 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 263 | "dev": true 264 | }, 265 | "commander": { 266 | "version": "2.20.3", 267 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 268 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 269 | }, 270 | "concat-map": { 271 | "version": "0.0.1", 272 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 273 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 274 | }, 275 | "concat-stream": { 276 | "version": "1.6.2", 277 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 278 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 279 | "requires": { 280 | "buffer-from": "^1.0.0", 281 | "inherits": "^2.0.3", 282 | "readable-stream": "^2.2.2", 283 | "typedarray": "^0.0.6" 284 | } 285 | }, 286 | "core-util-is": { 287 | "version": "1.0.3", 288 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 289 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 290 | }, 291 | "cross-spawn": { 292 | "version": "7.0.3", 293 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 294 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 295 | "dev": true, 296 | "requires": { 297 | "path-key": "^3.1.0", 298 | "shebang-command": "^2.0.0", 299 | "which": "^2.0.1" 300 | } 301 | }, 302 | "debug": { 303 | "version": "4.3.4", 304 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 305 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 306 | "requires": { 307 | "ms": "2.1.2" 308 | } 309 | }, 310 | "deep-is": { 311 | "version": "0.1.4", 312 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 313 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 314 | "dev": true 315 | }, 316 | "doctrine": { 317 | "version": "3.0.0", 318 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 319 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 320 | "dev": true, 321 | "requires": { 322 | "esutils": "^2.0.2" 323 | } 324 | }, 325 | "emoji-regex": { 326 | "version": "8.0.0", 327 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 328 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 329 | "dev": true 330 | }, 331 | "enquirer": { 332 | "version": "2.3.6", 333 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", 334 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", 335 | "dev": true, 336 | "requires": { 337 | "ansi-colors": "^4.1.1" 338 | } 339 | }, 340 | "escape-string-regexp": { 341 | "version": "4.0.0", 342 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 343 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 344 | "dev": true 345 | }, 346 | "eslint": { 347 | "version": "7.32.0", 348 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", 349 | "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", 350 | "dev": true, 351 | "requires": { 352 | "@babel/code-frame": "7.12.11", 353 | "@eslint/eslintrc": "^0.4.3", 354 | "@humanwhocodes/config-array": "^0.5.0", 355 | "ajv": "^6.10.0", 356 | "chalk": "^4.0.0", 357 | "cross-spawn": "^7.0.2", 358 | "debug": "^4.0.1", 359 | "doctrine": "^3.0.0", 360 | "enquirer": "^2.3.5", 361 | "escape-string-regexp": "^4.0.0", 362 | "eslint-scope": "^5.1.1", 363 | "eslint-utils": "^2.1.0", 364 | "eslint-visitor-keys": "^2.0.0", 365 | "espree": "^7.3.1", 366 | "esquery": "^1.4.0", 367 | "esutils": "^2.0.2", 368 | "fast-deep-equal": "^3.1.3", 369 | "file-entry-cache": "^6.0.1", 370 | "functional-red-black-tree": "^1.0.1", 371 | "glob-parent": "^5.1.2", 372 | "globals": "^13.6.0", 373 | "ignore": "^4.0.6", 374 | "import-fresh": "^3.0.0", 375 | "imurmurhash": "^0.1.4", 376 | "is-glob": "^4.0.0", 377 | "js-yaml": "^3.13.1", 378 | "json-stable-stringify-without-jsonify": "^1.0.1", 379 | "levn": "^0.4.1", 380 | "lodash.merge": "^4.6.2", 381 | "minimatch": "^3.0.4", 382 | "natural-compare": "^1.4.0", 383 | "optionator": "^0.9.1", 384 | "progress": "^2.0.0", 385 | "regexpp": "^3.1.0", 386 | "semver": "^7.2.1", 387 | "strip-ansi": "^6.0.0", 388 | "strip-json-comments": "^3.1.0", 389 | "table": "^6.0.9", 390 | "text-table": "^0.2.0", 391 | "v8-compile-cache": "^2.0.3" 392 | } 393 | }, 394 | "eslint-scope": { 395 | "version": "5.1.1", 396 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 397 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 398 | "dev": true, 399 | "requires": { 400 | "esrecurse": "^4.3.0", 401 | "estraverse": "^4.1.1" 402 | } 403 | }, 404 | "eslint-utils": { 405 | "version": "2.1.0", 406 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", 407 | "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", 408 | "dev": true, 409 | "requires": { 410 | "eslint-visitor-keys": "^1.1.0" 411 | }, 412 | "dependencies": { 413 | "eslint-visitor-keys": { 414 | "version": "1.3.0", 415 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 416 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 417 | "dev": true 418 | } 419 | } 420 | }, 421 | "eslint-visitor-keys": { 422 | "version": "2.1.0", 423 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 424 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 425 | "dev": true 426 | }, 427 | "espree": { 428 | "version": "7.3.1", 429 | "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", 430 | "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", 431 | "dev": true, 432 | "requires": { 433 | "acorn": "^7.4.0", 434 | "acorn-jsx": "^5.3.1", 435 | "eslint-visitor-keys": "^1.3.0" 436 | }, 437 | "dependencies": { 438 | "eslint-visitor-keys": { 439 | "version": "1.3.0", 440 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", 441 | "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", 442 | "dev": true 443 | } 444 | } 445 | }, 446 | "esprima": { 447 | "version": "4.0.1", 448 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 449 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 450 | "dev": true 451 | }, 452 | "esquery": { 453 | "version": "1.4.0", 454 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 455 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 456 | "dev": true, 457 | "requires": { 458 | "estraverse": "^5.1.0" 459 | }, 460 | "dependencies": { 461 | "estraverse": { 462 | "version": "5.3.0", 463 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 464 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 465 | "dev": true 466 | } 467 | } 468 | }, 469 | "esrecurse": { 470 | "version": "4.3.0", 471 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 472 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 473 | "dev": true, 474 | "requires": { 475 | "estraverse": "^5.2.0" 476 | }, 477 | "dependencies": { 478 | "estraverse": { 479 | "version": "5.3.0", 480 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 481 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 482 | "dev": true 483 | } 484 | } 485 | }, 486 | "estraverse": { 487 | "version": "4.3.0", 488 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 489 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 490 | "dev": true 491 | }, 492 | "esutils": { 493 | "version": "2.0.3", 494 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 495 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 496 | "dev": true 497 | }, 498 | "extract-zip": { 499 | "version": "1.7.0", 500 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 501 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 502 | "requires": { 503 | "concat-stream": "^1.6.2", 504 | "debug": "^2.6.9", 505 | "mkdirp": "^0.5.4", 506 | "yauzl": "^2.10.0" 507 | }, 508 | "dependencies": { 509 | "debug": { 510 | "version": "2.6.9", 511 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 512 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 513 | "requires": { 514 | "ms": "2.0.0" 515 | } 516 | }, 517 | "ms": { 518 | "version": "2.0.0", 519 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 520 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 521 | } 522 | } 523 | }, 524 | "fast-deep-equal": { 525 | "version": "3.1.3", 526 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 527 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 528 | "dev": true 529 | }, 530 | "fast-json-stable-stringify": { 531 | "version": "2.1.0", 532 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 533 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 534 | "dev": true 535 | }, 536 | "fast-levenshtein": { 537 | "version": "2.0.6", 538 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 539 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 540 | "dev": true 541 | }, 542 | "fd-slicer": { 543 | "version": "1.1.0", 544 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 545 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", 546 | "requires": { 547 | "pend": "~1.2.0" 548 | } 549 | }, 550 | "file-entry-cache": { 551 | "version": "6.0.1", 552 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 553 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 554 | "dev": true, 555 | "requires": { 556 | "flat-cache": "^3.0.4" 557 | } 558 | }, 559 | "flat-cache": { 560 | "version": "3.0.4", 561 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 562 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 563 | "dev": true, 564 | "requires": { 565 | "flatted": "^3.1.0", 566 | "rimraf": "^3.0.2" 567 | }, 568 | "dependencies": { 569 | "rimraf": { 570 | "version": "3.0.2", 571 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 572 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 573 | "dev": true, 574 | "requires": { 575 | "glob": "^7.1.3" 576 | } 577 | } 578 | } 579 | }, 580 | "flatted": { 581 | "version": "3.2.7", 582 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", 583 | "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", 584 | "dev": true 585 | }, 586 | "fs.realpath": { 587 | "version": "1.0.0", 588 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 589 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 590 | }, 591 | "functional-red-black-tree": { 592 | "version": "1.0.1", 593 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 594 | "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", 595 | "dev": true 596 | }, 597 | "glob": { 598 | "version": "7.2.3", 599 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 600 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 601 | "requires": { 602 | "fs.realpath": "^1.0.0", 603 | "inflight": "^1.0.4", 604 | "inherits": "2", 605 | "minimatch": "^3.1.1", 606 | "once": "^1.3.0", 607 | "path-is-absolute": "^1.0.0" 608 | } 609 | }, 610 | "glob-parent": { 611 | "version": "5.1.2", 612 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 613 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 614 | "dev": true, 615 | "requires": { 616 | "is-glob": "^4.0.1" 617 | } 618 | }, 619 | "globals": { 620 | "version": "13.17.0", 621 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", 622 | "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", 623 | "dev": true, 624 | "requires": { 625 | "type-fest": "^0.20.2" 626 | } 627 | }, 628 | "has-flag": { 629 | "version": "3.0.0", 630 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 631 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 632 | "dev": true 633 | }, 634 | "https-proxy-agent": { 635 | "version": "4.0.0", 636 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", 637 | "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", 638 | "requires": { 639 | "agent-base": "5", 640 | "debug": "4" 641 | } 642 | }, 643 | "ignore": { 644 | "version": "4.0.6", 645 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 646 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 647 | "dev": true 648 | }, 649 | "import-fresh": { 650 | "version": "3.3.0", 651 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 652 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 653 | "dev": true, 654 | "requires": { 655 | "parent-module": "^1.0.0", 656 | "resolve-from": "^4.0.0" 657 | } 658 | }, 659 | "imurmurhash": { 660 | "version": "0.1.4", 661 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 662 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 663 | "dev": true 664 | }, 665 | "inflight": { 666 | "version": "1.0.6", 667 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 668 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 669 | "requires": { 670 | "once": "^1.3.0", 671 | "wrappy": "1" 672 | } 673 | }, 674 | "inherits": { 675 | "version": "2.0.4", 676 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 677 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 678 | }, 679 | "is-extglob": { 680 | "version": "2.1.1", 681 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 682 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 683 | "dev": true 684 | }, 685 | "is-fullwidth-code-point": { 686 | "version": "3.0.0", 687 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 688 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 689 | "dev": true 690 | }, 691 | "is-glob": { 692 | "version": "4.0.3", 693 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 694 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 695 | "dev": true, 696 | "requires": { 697 | "is-extglob": "^2.1.1" 698 | } 699 | }, 700 | "isarray": { 701 | "version": "1.0.0", 702 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 703 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 704 | }, 705 | "isexe": { 706 | "version": "2.0.0", 707 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 708 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 709 | "dev": true 710 | }, 711 | "js-tokens": { 712 | "version": "4.0.0", 713 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 714 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 715 | "dev": true 716 | }, 717 | "js-yaml": { 718 | "version": "3.14.1", 719 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 720 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 721 | "dev": true, 722 | "requires": { 723 | "argparse": "^1.0.7", 724 | "esprima": "^4.0.0" 725 | } 726 | }, 727 | "json-schema-traverse": { 728 | "version": "0.4.1", 729 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 730 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 731 | "dev": true 732 | }, 733 | "json-stable-stringify-without-jsonify": { 734 | "version": "1.0.1", 735 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 736 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 737 | "dev": true 738 | }, 739 | "levn": { 740 | "version": "0.4.1", 741 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 742 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 743 | "dev": true, 744 | "requires": { 745 | "prelude-ls": "^1.2.1", 746 | "type-check": "~0.4.0" 747 | } 748 | }, 749 | "lodash.merge": { 750 | "version": "4.6.2", 751 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 752 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 753 | "dev": true 754 | }, 755 | "lodash.truncate": { 756 | "version": "4.4.2", 757 | "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", 758 | "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", 759 | "dev": true 760 | }, 761 | "lru-cache": { 762 | "version": "6.0.0", 763 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 764 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 765 | "dev": true, 766 | "requires": { 767 | "yallist": "^4.0.0" 768 | } 769 | }, 770 | "mime": { 771 | "version": "2.6.0", 772 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", 773 | "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" 774 | }, 775 | "mime-db": { 776 | "version": "1.52.0", 777 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 778 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 779 | }, 780 | "mime-types": { 781 | "version": "2.1.35", 782 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 783 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 784 | "requires": { 785 | "mime-db": "1.52.0" 786 | } 787 | }, 788 | "minimatch": { 789 | "version": "3.1.2", 790 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 791 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 792 | "requires": { 793 | "brace-expansion": "^1.1.7" 794 | } 795 | }, 796 | "minimist": { 797 | "version": "1.2.6", 798 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 799 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 800 | }, 801 | "mkdirp": { 802 | "version": "0.5.6", 803 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 804 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 805 | "requires": { 806 | "minimist": "^1.2.6" 807 | } 808 | }, 809 | "ms": { 810 | "version": "2.1.2", 811 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 812 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 813 | }, 814 | "natural-compare": { 815 | "version": "1.4.0", 816 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 817 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 818 | "dev": true 819 | }, 820 | "once": { 821 | "version": "1.4.0", 822 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 823 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 824 | "requires": { 825 | "wrappy": "1" 826 | } 827 | }, 828 | "optionator": { 829 | "version": "0.9.1", 830 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 831 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 832 | "dev": true, 833 | "requires": { 834 | "deep-is": "^0.1.3", 835 | "fast-levenshtein": "^2.0.6", 836 | "levn": "^0.4.1", 837 | "prelude-ls": "^1.2.1", 838 | "type-check": "^0.4.0", 839 | "word-wrap": "^1.2.3" 840 | } 841 | }, 842 | "parent-module": { 843 | "version": "1.0.1", 844 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 845 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 846 | "dev": true, 847 | "requires": { 848 | "callsites": "^3.0.0" 849 | } 850 | }, 851 | "path-is-absolute": { 852 | "version": "1.0.1", 853 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 854 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 855 | }, 856 | "path-key": { 857 | "version": "3.1.1", 858 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 859 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 860 | "dev": true 861 | }, 862 | "pend": { 863 | "version": "1.2.0", 864 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 865 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" 866 | }, 867 | "prelude-ls": { 868 | "version": "1.2.1", 869 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 870 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 871 | "dev": true 872 | }, 873 | "process-nextick-args": { 874 | "version": "2.0.1", 875 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 876 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 877 | }, 878 | "progress": { 879 | "version": "2.0.3", 880 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 881 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 882 | }, 883 | "proxy-from-env": { 884 | "version": "1.1.0", 885 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 886 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 887 | }, 888 | "punycode": { 889 | "version": "2.1.1", 890 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 891 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 892 | "dev": true 893 | }, 894 | "puppeteer": { 895 | "version": "2.1.1", 896 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.1.1.tgz", 897 | "integrity": "sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==", 898 | "requires": { 899 | "@types/mime-types": "^2.1.0", 900 | "debug": "^4.1.0", 901 | "extract-zip": "^1.6.6", 902 | "https-proxy-agent": "^4.0.0", 903 | "mime": "^2.0.3", 904 | "mime-types": "^2.1.25", 905 | "progress": "^2.0.1", 906 | "proxy-from-env": "^1.0.0", 907 | "rimraf": "^2.6.1", 908 | "ws": "^6.1.0" 909 | } 910 | }, 911 | "readable-stream": { 912 | "version": "2.3.7", 913 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 914 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 915 | "requires": { 916 | "core-util-is": "~1.0.0", 917 | "inherits": "~2.0.3", 918 | "isarray": "~1.0.0", 919 | "process-nextick-args": "~2.0.0", 920 | "safe-buffer": "~5.1.1", 921 | "string_decoder": "~1.1.1", 922 | "util-deprecate": "~1.0.1" 923 | } 924 | }, 925 | "regexpp": { 926 | "version": "3.2.0", 927 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 928 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 929 | "dev": true 930 | }, 931 | "require-from-string": { 932 | "version": "2.0.2", 933 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 934 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 935 | "dev": true 936 | }, 937 | "resolve-from": { 938 | "version": "4.0.0", 939 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 940 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 941 | "dev": true 942 | }, 943 | "rimraf": { 944 | "version": "2.7.1", 945 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 946 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 947 | "requires": { 948 | "glob": "^7.1.3" 949 | } 950 | }, 951 | "safe-buffer": { 952 | "version": "5.1.2", 953 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 954 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 955 | }, 956 | "semver": { 957 | "version": "7.3.7", 958 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", 959 | "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", 960 | "dev": true, 961 | "requires": { 962 | "lru-cache": "^6.0.0" 963 | } 964 | }, 965 | "shebang-command": { 966 | "version": "2.0.0", 967 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 968 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 969 | "dev": true, 970 | "requires": { 971 | "shebang-regex": "^3.0.0" 972 | } 973 | }, 974 | "shebang-regex": { 975 | "version": "3.0.0", 976 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 977 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 978 | "dev": true 979 | }, 980 | "slice-ansi": { 981 | "version": "4.0.0", 982 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", 983 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 984 | "dev": true, 985 | "requires": { 986 | "ansi-styles": "^4.0.0", 987 | "astral-regex": "^2.0.0", 988 | "is-fullwidth-code-point": "^3.0.0" 989 | }, 990 | "dependencies": { 991 | "ansi-styles": { 992 | "version": "4.3.0", 993 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 994 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 995 | "dev": true, 996 | "requires": { 997 | "color-convert": "^2.0.1" 998 | } 999 | }, 1000 | "color-convert": { 1001 | "version": "2.0.1", 1002 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1003 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1004 | "dev": true, 1005 | "requires": { 1006 | "color-name": "~1.1.4" 1007 | } 1008 | }, 1009 | "color-name": { 1010 | "version": "1.1.4", 1011 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1012 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1013 | "dev": true 1014 | } 1015 | } 1016 | }, 1017 | "sprintf-js": { 1018 | "version": "1.1.1", 1019 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", 1020 | "integrity": "sha512-h/U+VScR2Ft+aXDjGTLtguUEIrYuOjTj79BAOElUvdahYMaaa7SNLjJpOIn+Uzt0hsgHfYvlbcno3e9yXOSo8Q==" 1021 | }, 1022 | "string-width": { 1023 | "version": "4.2.3", 1024 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1025 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1026 | "dev": true, 1027 | "requires": { 1028 | "emoji-regex": "^8.0.0", 1029 | "is-fullwidth-code-point": "^3.0.0", 1030 | "strip-ansi": "^6.0.1" 1031 | } 1032 | }, 1033 | "string_decoder": { 1034 | "version": "1.1.1", 1035 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1036 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1037 | "requires": { 1038 | "safe-buffer": "~5.1.0" 1039 | } 1040 | }, 1041 | "strip-ansi": { 1042 | "version": "6.0.1", 1043 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1044 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1045 | "dev": true, 1046 | "requires": { 1047 | "ansi-regex": "^5.0.1" 1048 | } 1049 | }, 1050 | "strip-json-comments": { 1051 | "version": "3.1.1", 1052 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1053 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1054 | "dev": true 1055 | }, 1056 | "supports-color": { 1057 | "version": "5.5.0", 1058 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1059 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1060 | "dev": true, 1061 | "requires": { 1062 | "has-flag": "^3.0.0" 1063 | } 1064 | }, 1065 | "table": { 1066 | "version": "6.8.0", 1067 | "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", 1068 | "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", 1069 | "dev": true, 1070 | "requires": { 1071 | "ajv": "^8.0.1", 1072 | "lodash.truncate": "^4.4.2", 1073 | "slice-ansi": "^4.0.0", 1074 | "string-width": "^4.2.3", 1075 | "strip-ansi": "^6.0.1" 1076 | }, 1077 | "dependencies": { 1078 | "ajv": { 1079 | "version": "8.11.0", 1080 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", 1081 | "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", 1082 | "dev": true, 1083 | "requires": { 1084 | "fast-deep-equal": "^3.1.1", 1085 | "json-schema-traverse": "^1.0.0", 1086 | "require-from-string": "^2.0.2", 1087 | "uri-js": "^4.2.2" 1088 | } 1089 | }, 1090 | "json-schema-traverse": { 1091 | "version": "1.0.0", 1092 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1093 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1094 | "dev": true 1095 | } 1096 | } 1097 | }, 1098 | "text-table": { 1099 | "version": "0.2.0", 1100 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1101 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 1102 | "dev": true 1103 | }, 1104 | "timesnap": { 1105 | "version": "0.3.3", 1106 | "resolved": "https://registry.npmjs.org/timesnap/-/timesnap-0.3.3.tgz", 1107 | "integrity": "sha512-Y+KqywtlXbY/RbvfLRm9MofCnISoGpDj4OZJn8NT7IgAIqnVQ8ujKYcdNG7yEYQZUg/5eAx0av1IlWYLrct6xw==", 1108 | "requires": { 1109 | "commander": "^2.11.0", 1110 | "puppeteer": "^2.1.1", 1111 | "sprintf-js": "1.1.1", 1112 | "timeweb": "0.3.1", 1113 | "unrandomize": "0.1.0" 1114 | } 1115 | }, 1116 | "timeweb": { 1117 | "version": "0.3.1", 1118 | "resolved": "https://registry.npmjs.org/timeweb/-/timeweb-0.3.1.tgz", 1119 | "integrity": "sha512-3ZaDlwv6AecqoJ6QPRoOxdpWf9TwPbHxIxU0uCYVi5Re0JZ/yQ41C5CqAuDmcYvbKkGRngG29CPzAWlae9s8pw==" 1120 | }, 1121 | "type-check": { 1122 | "version": "0.4.0", 1123 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1124 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1125 | "dev": true, 1126 | "requires": { 1127 | "prelude-ls": "^1.2.1" 1128 | } 1129 | }, 1130 | "type-fest": { 1131 | "version": "0.20.2", 1132 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1133 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1134 | "dev": true 1135 | }, 1136 | "typedarray": { 1137 | "version": "0.0.6", 1138 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1139 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 1140 | }, 1141 | "unrandomize": { 1142 | "version": "0.1.0", 1143 | "resolved": "https://registry.npmjs.org/unrandomize/-/unrandomize-0.1.0.tgz", 1144 | "integrity": "sha512-ps3jw749uVwC0RrRS193HU1pAqQeoZMQtKcwBabmZ4o14/dBC8KXoqR/tPX6Yg79d8CUcb4uI/IopVphhrKp3w==" 1145 | }, 1146 | "uri-js": { 1147 | "version": "4.4.1", 1148 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1149 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1150 | "dev": true, 1151 | "requires": { 1152 | "punycode": "^2.1.0" 1153 | } 1154 | }, 1155 | "util-deprecate": { 1156 | "version": "1.0.2", 1157 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1158 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1159 | }, 1160 | "v8-compile-cache": { 1161 | "version": "2.3.0", 1162 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", 1163 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", 1164 | "dev": true 1165 | }, 1166 | "which": { 1167 | "version": "2.0.2", 1168 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1169 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1170 | "dev": true, 1171 | "requires": { 1172 | "isexe": "^2.0.0" 1173 | } 1174 | }, 1175 | "word-wrap": { 1176 | "version": "1.2.3", 1177 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1178 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1179 | "dev": true 1180 | }, 1181 | "wrappy": { 1182 | "version": "1.0.2", 1183 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1184 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1185 | }, 1186 | "ws": { 1187 | "version": "6.2.2", 1188 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", 1189 | "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", 1190 | "requires": { 1191 | "async-limiter": "~1.0.0" 1192 | } 1193 | }, 1194 | "yallist": { 1195 | "version": "4.0.0", 1196 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1197 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1198 | "dev": true 1199 | }, 1200 | "yauzl": { 1201 | "version": "2.10.0", 1202 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 1203 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", 1204 | "requires": { 1205 | "buffer-crc32": "~0.2.3", 1206 | "fd-slicer": "~1.1.0" 1207 | } 1208 | } 1209 | } 1210 | } 1211 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timecut", 3 | "version": "0.3.4-prerelease", 4 | "description": "Record smooth movies of web pages", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tungs/timecut.git" 8 | }, 9 | "author": { 10 | "name": "Steve Tung" 11 | }, 12 | "engines": { 13 | "node": ">=8.9.0" 14 | }, 15 | "main": "index.js", 16 | "scripts": { 17 | "lint": "eslint *.js" 18 | }, 19 | "bin": { 20 | "timecut": "./cli.js" 21 | }, 22 | "license": "BSD-3-Clause", 23 | "dependencies": { 24 | "commander": "^2.11.0", 25 | "timesnap": "0.3.3" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^7.32.0" 29 | } 30 | } 31 | --------------------------------------------------------------------------------