diff --git a/.eslintrc.js b/.eslintrc.js index bd06f0fa..3903c6c9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,8 @@ module.exports = { project: './tsconfig.json' }, plugins: [ - '@typescript-eslint' + '@typescript-eslint', + 'import' ], extends: [ 'eslint:all', @@ -23,6 +24,7 @@ module.exports = { '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/init-declarations': 'off', + '@typescript-eslint/lines-around-comment': 'off', '@typescript-eslint/member-ordering': 'off', "@typescript-eslint/naming-convention": 'off', '@typescript-eslint/no-base-to-string': 'off', @@ -51,6 +53,9 @@ module.exports = { 'error', 'always' ], + '@typescript-eslint/parameter-properties': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + "@typescript-eslint/prefer-optional-chain": 'off', '@typescript-eslint/prefer-readonly': 'off', '@typescript-eslint/prefer-readonly-parameter-types': 'off', '@typescript-eslint/promise-function-async': 'off', @@ -64,6 +69,7 @@ module.exports = { '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/sort-type-constituents': 'off', '@typescript-eslint/sort-type-union-intersection-members': 'off', '@typescript-eslint/space-before-function-paren': 'off', '@typescript-eslint/strict-boolean-expressions': 'off', @@ -89,11 +95,11 @@ module.exports = { 'function-paren-newline': 'off', 'guard-for-in': 'off', 'id-length': 'off', + 'import/no-duplicates': 'error', 'indent': 'off', 'init-declarations': 'off', 'line-comment-position': 'off', 'linebreak-style': 'off', - 'lines-around-comment': 'off', 'lines-between-class-members': 'off', 'max-classes-per-file': 'off', 'max-depth': 'off', @@ -111,6 +117,7 @@ module.exports = { 'no-constant-condition': 'off', 'no-console': 'off', 'no-continue': 'off', + 'no-duplicate-imports': 'off', 'no-else-return': 'off', 'no-empty': 'off', 'no-implicit-coercion': 'off', diff --git a/.gitignore b/.gitignore index 8c0cf879..f83ed1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -node_modules +node_modules* dist out .DS_Store @@ -6,4 +6,4 @@ out .roku-deploy-staging coverage .nyc_output -*.zip \ No newline at end of file +*.zip diff --git a/.vscode/settings.json b/.vscode/settings.json index 047dd795..33a8d7bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,6 @@ "**/bower_components": true, "**/*.code-search": true, "**/dist": true - } -} \ No newline at end of file + }, + "js/ts.tsdk.path": "node_modules/typescript/lib" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b825e06a..f25ab661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [4.0.0-alpha.2](https://github.com/rokucommunity/roku-deploy/compare/4.0.0-alpha.1...v4.0.0-alpha.2) - 2025-06-02 +### Added + - Add interactive remote mode ([#169](https://github.com/rokucommunity/roku-deploy/pull/169)) + + + +## [4.0.0-alpha.1](https://github.com/rokucommunity/roku-deploy/compare/v4.0.0-alpha.0...v4.0.0-alpha.1) - 2024-05-17 +### Changed + - Enhanced logging levels ([#168](https://github.com/rokucommunity/roku-deploy/pull/168)) + - Update files array ([#164](https://github.com/rokucommunity/roku-deploy/pull/164)) + - Change documentation ([#162](https://github.com/rokucommunity/roku-deploy/pull/162)) + + + +## [4.0.0-alpha.0](https://github.com/rokucommunity/roku-deploy/compare/v3.11.1...v4.0.0-alpha.0) - 2024-04-16 +### Added + - individual interfaces for every rokuDeploy function ([#126](https://github.com/rokucommunity/roku-deploy/pull/126)) + - cli commands ([#139](https://github.com/rokucommunity/roku-deploy/pull/139)) + - cli commands and rename roku-deploy functions, reorganize functions ([#142](https://github.com/rokucommunity/roku-deploy/pull/142)) + - cwd option ([#158](https://github.com/rokucommunity/roku-deploy/pull/158)) +### Changed + - don't normalize file patterns ([#131](https://github.com/rokucommunity/roku-deploy/pull/131)) + - Throw exceptions on missing options ([#156](https://github.com/rokucommunity/roku-deploy/pull/156)) + - upgrade typescript & other packages ([#157](https://github.com/rokucommunity/roku-deploy/pull/157)) +### Fixed + - bug with `{src;dest}` object handling ([#135](https://github.com/rokucommunity/roku-deploy/pull/135)) +### Removed + - removed deprecated `retainStagingFolder` property ([#130](https://github.com/rokucommunity/roku-deploy/pull/130)) + - eliminate top index functions ([#144](https://github.com/rokucommunity/roku-deploy/pull/144)) + + + ## [3.17.5](https://github.com/rokucommunity/roku-deploy/compare/3.17.4...v3.17.5) - 2026-05-30 ### Fixed - Preserve `!` glob-negation prefix when using normalizeFilesArray ([#277](https://github.com/rokucommunity/roku-deploy/pull/277)) @@ -596,7 +628,3 @@ chore: Update package.json repository to support provenance (#218) ## [1.0.0](https://github.com/RokuCommunity/roku-deploy/compare/v0.2.1...v1.0.0) - 2018-12-18 ### Added - support for negated globs - - - - diff --git a/README.md b/README.md index 9ab0406c..85658799 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ # roku-deploy +This is the V4 branch, it's a work in progress. + Publish Roku projects to a Roku device by using Node.js. [![build status](https://img.shields.io/github/actions/workflow/status/rokucommunity/roku-deploy/build.yml?branch=master)](https://github.com/rokucommunity/roku-deploy/actions?query=branch%3Amaster+workflow%3Abuild) -[![security](https://img.shields.io/github/actions/workflow/status/rokucommunity/roku-deploy/security-audit.yml?branch=master&label=security&logo=data:image/svg%2Bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PHJlY3QgeD0iMyIgeT0iOCIgd2lkdGg9IjEwIiBoZWlnaHQ9IjciIHJ4PSIxIiBmaWxsPSJ3aGl0ZSIvPjxwYXRoIGQ9Ik01IDhWNWEzIDMgMCAwIDEgNiAwdjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==)](https://github.com/rokucommunity/roku-deploy/actions/workflows/security-audit.yml) [![coverage status](https://img.shields.io/coveralls/github/rokucommunity/roku-deploy?logo=coveralls)](https://coveralls.io/github/rokucommunity/roku-deploy?branch=master) -[![monthly downloads](https://img.shields.io/npm/dm/roku-deploy.svg?sanitize=true&logo=npm&logoColor=&label=npm)](https://npmcharts.com/compare/roku-deploy?minimal=true) -[![npm version](https://img.shields.io/npm/v/roku-deploy.svg?logo=npm&label=npm)](https://www.npmjs.com/package/roku-deploy) +[![monthly downloads](https://img.shields.io/npm/dm/roku-deploy.svg?sanitize=true&logo=npm&logoColor=)](https://npmcharts.com/compare/roku-deploy?minimal=true) +[![npm version](https://img.shields.io/npm/v/roku-deploy.svg?logo=npm)](https://www.npmjs.com/package/roku-deploy) [![license](https://img.shields.io/github/license/rokucommunity/roku-deploy.svg)](LICENSE) [![Slack](https://img.shields.io/badge/Slack-RokuCommunity-4A154B?logo=slack)](https://join.slack.com/t/rokudevelopers/shared_invite/zt-4vw7rg6v-NH46oY7hTktpRIBM_zGvwA) + +### Table of Contents +- [Installation](#installation) +- [Requirements](#requirements) +- [Upgrading to V4](#upgrading-to-v4) +- [CLI Usage](#cli-usage) +- [JavaScript Usage](#javascript-usage) +- [Options Priority Order](#options-priority-order) +- [Files Array](#files-array) +- [Available roku-deploy Options](#available-roku-deploy-options) +- [Troubleshooting](#troubleshooting) +- [Changelog](#changelog) + ## Installation npm install roku-deploy @@ -21,6 +35,8 @@ Publish Roku projects to a Roku device by using Node.js. images/ source/ manifest + locale/ + fonts/ 2. You should create a rokudeploy.json file at the root of your project that contains all of the overrides to the default options. roku-deploy will auto-detect this file and use it when possible. (**note**: `rokudeploy.json` is jsonc, which means it supports comments). @@ -32,53 +48,162 @@ sample rokudeploy.json "password": "securePassword" } ``` -## Usage -From a node script -```javascript -var rokuDeploy = require('roku-deploy'); +## Upgrading to v4 +The new release has a few breaking changes that is worth going over in order to prepare developers for what they will need to change when they choose to upgrade. -//deploy a .zip package of your project to a roku device -rokuDeploy.deploy({ - host: 'ip-of-roku', - password: 'password for roku dev admin portal' - //other options if necessary -}).then(function(){ - //it worked -}, function(error) { - //it failed - console.error(error); -}); +### JavaScript functions don't load config files from disk +In v3, files like `roku-deploy.json` and `bsconfig.json` would be loaded anytime a rokuDeploy function was called through the NodeJS api. This functionality has been removed in v4 so that developers have more control over when the config files are loaded. If your script needs to load the config file values, you can simply call `util.getOptionsFromJson` before calling the desired rokuDeploy function. This will default to load from `rokudeploy.json`. Here's an example: + +```javascript +const config = { + //get the default options + ...rokuDeploy.getOptions(), + //override with any values found in the `rokudeploy.json` file. You can specify current working directory here. + ...util.getOptionsFromJson({ cwd: process.cwd() }) +}; +await rokuDeploy.sideload(config); ``` -Or + +### Removed support for bsconfig.json +We've removed support for loading `bsconfig.json` files. This was introduced in v3, but sometimes causes confusion between various systems (like brighterscript, vscode extension, etc). If you need to load values from a `bsconfig.json`, you can explicitly specify the config path. Like this: + ```javascript -//create a signed package of your project -rokuDeploy.deployAndSignPackage({ - host: 'ip-of-roku', - password: 'password for roku dev admin portal', - signingPassword: 'signing password' - //other options if necessary -}).then(function(pathToSignedPackage){ - console.log('Signed package created at ', pathToSignedPackage); -}, function(error) { - //it failed - console.error(error); -}); +const config = { + //get the default options + ...rokuDeploy.getOptions(), + //override with any values found in config file + ...util.getOptionsFromJson({ configPath: './bsconfig.json' }) +}; +//call some rokuDeploy function +await rokuDeploy.sideload(config); +``` + +### Changed, added, or moved some functions in the main Node API +Another set of changes are the names and features available in the Node API. Some have been renamed and others have been change to be used only as CLI commands in order to organize and simplify what is offered. Renamed functions: +- `zipPackage()` -> `zip()` +- `pressHomeButton()` -> `closeChannel()` +- `publish()` -> `sideload()` +- `signExistingPackage()` -> `package()` +- `deleteInstalledChannel()` -> `deleteDevChannel()` +- `takeScreenshot()` -> `screenshot()` +- `convertToSquashfs()` -> `squash()` +- `rekeyDevice()` -> `rekey()` + +Some functions were added to help with certain developer usecases. These mostly allow for any remote-to-Roku interaction: +- `keyPress()` +- `keyUp()` +- `keyDown()` +- `sendText()` + +Previously, some functions were available in the Node API, but have been moved to CLI commands. These are: +- `deploy()` +- `createPackage()` +- `deployAndSignPackage()` + +### Default file array changed +Lastly, the default files array has changed. node modules and static analysis files have been excluded to speed up load times. Also, `fonts/` and `locale/` was added as they are in some Roku documentation. The new default array can be seen in the section titled [Files Array](#files-array) + +## CLI Usage + +### Sideload a project to your Roku device +Sideload a .zip package or directory to a roku device. By default, the channel is closed before sideloading. Use `--no-close` to skip this. +```shell +# Sideload a zip file +npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --zip './path/to/your/app.zip' + +# Sideload from a directory (will be zipped first automatically) +npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --rootDir './path/to/your/project' + +# Sideload without closing the channel first +npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --zip './path/to/your/app.zip' --no-close ``` +### Create a signed package from an existing dev channel +```shell +npx roku-deploy package --host 'ip.of.roku' --password 'password' --signingPassword 'signing password' --out './out/my-app.pkg' +``` + +### Stage files to a directory +Copy your project files to a staging directory: +```shell +npx roku-deploy stage --rootDir './path/to/root/dir' --out './path/to/staging/dir' +``` + +### Zip a directory +Create a zip file from a directory: +```shell +npx roku-deploy zip --dir './path/to/directory' --out './path/to/output.zip' +``` + +### Remote control commands +Send key presses to your Roku: +```shell +# Press a key +npx roku-deploy keyPress --key 'Home' --host 'ip.of.roku' + +# Hold a key down +npx roku-deploy keyDown --key 'Up' --host 'ip.of.roku' + +# Release a key +npx roku-deploy keyUp --key 'Up' --host 'ip.of.roku' + +# Send text to the device +npx roku-deploy sendText --text 'Hello World' --host 'ip.of.roku' + +# Interactive remote control mode +npx roku-deploy remote-control --host 'ip.of.roku' +``` + +### Convert to SquashFS +Convert your dev channel to SquashFS format: +```shell +npx roku-deploy squash --host 'ip.of.roku' --password 'password' +``` + +### Device management +```shell +# Take a screenshot (filename used exactly as provided) +npx roku-deploy screenshot --host 'ip.of.roku' --password 'password' --out './screenshot.jpg' + +# Take a screenshot with auto extension handling (appends/swaps extension based on device response) +npx roku-deploy screenshot --host 'ip.of.roku' --password 'password' --out './screenshot' --autoExtension + +# Rekey a device with a signed package +npx roku-deploy rekey --host 'ip.of.roku' --password 'password' --pkg './path/to/signed.pkg' --signingPassword 'signing password' + +# Delete the dev channel +npx roku-deploy deleteDevChannel --host 'ip.of.roku' --password 'password' + +# Get device information +npx roku-deploy getDeviceInfo --host 'ip.of.roku' + +# Get device ID +npx roku-deploy getDevId --host 'ip.of.roku' +``` + +You can view the full list of commands and their options by running: + +```shell +npx roku-deploy --help +``` + +## JavaScript Usage ### Copying the files to staging If you'd like to use roku-deploy to copy files to a staging folder, you can do the following: ```typescript -rokuDeploy.prepublishToStaging({ +import { rokuDeploy } from 'roku-deploy'; +rokuDeploy.stage({ rootDir: "folder/with/your/source/code", stagingDir: 'path/to/staging/folder', files: [ "source/**/*", "components/**/*", "images/**/*", + "manifest", "locale/**/*", - "manifest" + "fonts/**/*" ], //...other options if necessary }).then(function(){ @@ -92,11 +217,11 @@ rokuDeploy.prepublishToStaging({ ### Creating a zip from an already-populated staging folder Use this logic if you'd like to create a zip from your application folder. ```typescript -/create a signed package of your project -rokuDeploy.zipPackage({ +//create a signed package of your project +rokuDeploy.zip({ outDir: 'folder/to/put/zip', - stagingDir: 'path/to/files/to/zip', - outFile: 'filename-of-your-app.zip' + dir: 'path/to/files/to/zip', + outFile: 'filename-of-your-app.zip', //...other options if necessary }).then(function(){ //the zip has been created @@ -106,16 +231,35 @@ rokuDeploy.zipPackage({ }); ``` +### Pressing the Home key +```typescript +rokuDeploy.keyPress({ + key: 'Home', + //...other options if necessary +}) +``` -### Deploying an existing zip -If you've already created a zip using some other tool, you can use roku-deploy to sideload the zip. +### Sideloading a project +Sideload a zip file, a directory, or a pre-built zip at the default `outDir`/`outFile` location. The current dev channel is closed before sideloading by default; pass `close: false` to skip. ```typescript -/create a signed package of your project -rokuDeploy.publish({ +// Sideload a zip file +rokuDeploy.sideload({ + host: 'ip-of-roku', + password: 'password for roku dev admin portal', + zip: './path/to/your/app.zip', + //...other options if necessary +}).then(function(){ + //the app has been sideloaded +}, function(error) { + //it failed + console.error(error); +}); + +// Sideload from a source directory (will be zipped automatically) +rokuDeploy.sideload({ host: 'ip-of-roku', password: 'password for roku dev admin portal', - outDir: 'folder/where/your/zip/resides/', - outFile: 'filename-of-your-app.zip' + dir: './path/to/your/project', //...other options if necessary }).then(function(){ //the app has been sideloaded @@ -125,7 +269,87 @@ rokuDeploy.publish({ }); ``` -### running roku-deploy as an npm script +### Convert to SquashFS +```typescript +rokuDeploy.squash({ + host: '1.2.3.4', + password: 'password', + //...other options if necessary +}) +``` + +### Create a signed package +```typescript +rokuDeploy.package({ + host: '1.2.3.4', + password: 'password', + signingPassword: 'signing password', + //...other options if necessary +}) +``` + +### Send text to device +```typescript +rokuDeploy.sendText({ + text: 'Hello World', + host: 'ip-of-roku', + //...other options if necessary +}) +``` + +### Take a screenshot +```typescript +// Filename is used exactly as provided (default behavior) +rokuDeploy.screenshot({ + host: 'ip-of-roku', + password: 'password', + screenshotDir: './screenshots/', + screenshotFile: 'screenshot.jpg', + //...other options if necessary +}) + +// With autoExtension: true, the extension is automatically handled based on device response +rokuDeploy.screenshot({ + host: 'ip-of-roku', + password: 'password', + screenshotDir: './screenshots/', + screenshotFile: 'screenshot', // Extension will be appended based on device + autoExtension: true +}) +``` + +### Rekey a device +```typescript +rokuDeploy.rekey({ + host: 'ip-of-roku', + password: 'password', + pkg: './path/to/signed.pkg' + //...other options if necessary +}) +``` + +Can't find what you need? We offer a variety of functions available in the [RokuDeploy.ts file](https://github.com/rokucommunity/roku-deploy/blob/v4/src/RokuDeploy.ts). Here are all of the public functions: +- `stage()` +- `zip()` +- `sideload()` +- `getFilePaths()` +- `keyPress()` +- `keyUp()` +- `keyDown()` +- `sendText()` +- `closeChannel()` +- `rekey()` +- `package()` +- `deleteDevChannel()` +- `screenshot()` +- `squash()` +- `getDeviceInfo()` +- `getDevId()` +- `getOptions()` +- `checkRequiredOptions()` + + +### Running roku-deploy as an npm script From an npm script in `package.json`. (Requires `rokudeploy.json` to exist at the root level where this is being run) { @@ -134,38 +358,16 @@ From an npm script in `package.json`. (Requires `rokudeploy.json` to exist at th } } -You can provide a callback in any of the higher level methods, which allows you to modify the copied contents before the package is zipped. An info object is passed in with the following attributes -- **manifestData:** [key: string]: string - Contains all the parsed values from the manifest file -- **stagingDir:** string - Path to staging folder to make it so you only need to know the relative path to what you're trying to modify - - ```javascript - let options = { - host: 'ip-of-roku', - password: 'password for roku dev admin portal' - //other options if necessary - }; - - rokuDeploy.deploy(options, (info) => { - //modify staging dir before it's zipped. - //At this point, all files have been copied to the staging directory. - manipulateFilesInStagingFolder(info.stagingDir) - //this function can also return a promise, - //which will be awaited before roku-deploy starts deploying. - }).then(function(){ - //it worked - }, function(){ - //it failed - }); - ``` - -## bsconfig.json -Another common config file is [bsconfig.json](https://github.com/rokucommunity/brighterscript#bsconfigjson-options), used by the [BrighterScript](https://github.com/rokucommunity/brighterscript) project and the [BrightScript extension for VSCode](https://github.com/rokucommunity/vscode-brightscript-language). Since many of the config settings are shared between `roku-deploy.json` and `bsconfig.json`, `roku-deploy` supports reading from that file as well. Here is the loading order: - - if `roku-deploy.json` is found, those settings are used. - - if `roku-deploy.json` is not found, look for `bsconfig.json` and use those settings. +## Options Priority Order +RokuDeploy can be configured in various ways (cli args, `roku-deploy.json`, parameters, and defaults). Here's the order these options will be loaded: +**When run from the CLI:** + - start with the default set of options from `rokuDeploy.getOptions()` + - override with any values found in `roku-deploy.json` or specified config file + - override with any values from CLI args -Note that When roku-deploy is called from within a NodeJS script, the options passed into the roku-deploy methods will override any options found in `roku-deploy.json` and `bsconfig.json`. +**When run from javascript:** + - start with the default set of options from `rokuDeploy.getOptions()` + - override with any values passed in as function arguments ## Files Array @@ -177,11 +379,14 @@ For most standard projects, the default files array should work just fine: ```jsonc { "files": [ - "source/**/*", - "components/**/*", - "images/**/*", + "source/**/*.*", + "components/**/*.*", + "images/**/*.*", "locale/**/*", - "manifest" + "fonts/**/*", + "manifest", + "!node_modules", + "!**/*.{md,DS_Store,db}" ] } ``` @@ -193,12 +398,16 @@ If you want to include additonal files, you will need to provide the entire arra ```jsonc { "files": [ - "source/**/*", - "components/**/*", - "images/**/*", - "manifest" + "source/**/*.*", + "components/**/*.*", + "images/**/*.*", + "locale/**/*", + "fonts/**/*", + "manifest", + "!node_modules", + "!**/*.{md,DS_Store,db}", //your folder with other assets - "assets/**/*", + "assets/**/*" ] } ``` @@ -257,8 +466,8 @@ The object structure is as follows: } ``` #### { src; dest } Object Rules - - if `src` is a non-glob path to a single file, then `dest` should include the filename and extension. For example: - `{ src: "lib/Promise/promise.brs", dest: "source/promise.brs"}` +- if `src` is a non-glob path to a single file, then `dest` should include the filename and extension. For example: +`{ src: "lib/Promise/promise.brs", dest: "source/promise.brs"}` - if `src` is a glob pattern, then `dest` should be a path to the folder in the output directory. For example: `{ src: "lib/*.brs", dest: "source/lib"}` @@ -290,30 +499,29 @@ For example, if you have a base project, and then a child project that wants to -## roku-deploy Options -Here are the available options. The defaults are shown to the right of the option name, but all can be overridden: +## Available roku-deploy Options +Here are the available options for customizing to your developer-specific workflows. The defaults are shown to the right of the option name, but all can be overridden: - **host:** string (*required*) - The IP address or hostname of the target Roku device. Example: `"192.168.1.21"` + The IP address or hostname of the target Roku device. Example: `"192.168.1.21"`. - **password:** string (*required*) - The password for logging in to the developer portal on the target Roku device + The password for logging in to the developer portal on the target Roku device. - **signingPassword:** string (*required for signing*) - The password used for creating signed packages + The password used for creating signed packages. -- **rekeySignedPackage:** string (*required for rekeying*) - Path to a copy of the signed package you want to use for rekeying +- **pkg:** string (*required for rekeying*) + Path to a copy of the signed package you want to use for rekeying. - **devId:** string - Dev ID we are expecting the device to have. If supplied we check that the dev ID returned after keying matches what we expected - + Dev ID we are expecting the device to have. If supplied we check that the dev ID returned after keying matches what we expected. - **outDir?:** string = `"./out"` - A full path to the folder where the zip/pkg package should be placed + A full path to the folder where the zip/pkg package should be placed. - **outFile?:** string = `"roku-deploy"` - The base filename the zip/pkg file should be given (excluding the extension) + The base filename the zip/pkg file should be given (excluding the extension). - **rootDir?:** string = `'./'` The root path to the folder holding your project. The manifest file should be directly underneath this folder. Use this option when your roku project is in a subdirectory of where roku-deploy is installed. @@ -325,10 +533,26 @@ Here are the available options. The defaults are shown to the right of the optio "components/**/*.*", "images/**/*.*", "locale/**/*", - "manifest" + "fonts/**/*", + "manifest", + "!node_modules", + "!**/*.{md,DS_Store,db}" ] ``` - An array of file paths, globs, or {src:string;dest:string} objects that will be copied into the deployment package. + An array of file paths, globs, or `{ src: string; dest: string }` objects that will be copied into the deployment package. You may use either forward slashes ( `/` ) or backslashes ( `\` ) as path separators — roku-deploy normalizes both internally, so patterns built with Node's `path.join` on Windows work as-is. Forward slashes are still recommended for portability. + + Because a backslash is also the glob escape character, prefer the character-class form to match a literal glob metacharacter in a filename: use `[*]` / `[?]` rather than `\*` / `\?`. + + **Folders or files with square brackets in their name** (e.g. `[ios]`, `[v2]`) are a common gotcha: unescaped square brackets are a glob _character class_ (`[abc]` means "one of `a`, `b`, `c`"), so `[v2]/*` will _not_ match a folder literally named `[v2]` — and may match unintended folders named `v` or `2`. Escape the brackets to match them literally: + ```jsonc + { + "files": [ + //matches the folder literally named "[v2]" + "\\[v2\\]/**/*" + ] + } + ``` + roku-deploy preserves these `\[` / `\]` escapes even on Windows (where the backslash would otherwise look like a path separator). There is no character-class alternative for literal brackets, so the escape is required. See [how fast-glob handles patterns on Windows](https://www.npmjs.com/package/fast-glob?activeTab=readme#how-to-write-patterns-on-windows) for background. Using the {src;dest} objects will allow you to move files into different destination paths in the deployment package. This would be useful for copying environment-specific configs into a common config location @@ -362,37 +586,49 @@ Here are the available options. The defaults are shown to the right of the optio *NOTE:* If you override this "files" property, you need to provide **all** config values, as your array will completely overwrite the default. -- **retainStagingFolder?:** boolean = `false` - Set this to true to prevent the staging folder from being deleted after creating the package. This is helpful for troubleshooting why your package isn't being created the way you expected. - - **stagingDir?:** string = `` `${options.outDir}/.roku-deploy-staging` `` The path to the staging folder (where roku-deploy places all of the files right before zipping them up). - **convertToSquashfs?:** boolean = `false` - If true we convert to squashfs before creating the pkg file - -- **incrementBuildNumber?:** boolean = `false` - If true we increment the build number to be a timestamp in the format yymmddHHMM + If true we convert to squashfs before creating the pkg file. - **username?:** string = `"rokudev"` The username for the roku box. This will always be 'rokudev', but allow to be passed in - just in case roku adds support for custom usernames in the future + just in case roku adds support for custom usernames in the future. + +- **packagePort?:** number = `80` + The port used for package-related requests. This is mainly used when your roku is behind a firewall with a port-forward. -- **packagePort?:** string = 80 - The port used for package-related requests. This is mainly used for things like emulators, or when your roku is behind a firewall with a port-forward. +- **ecpPort?:** number = `8060` + The port used for sending ECP/remote control commands (like key presses). This is mainly used when your roku is behind a firewall with a port-forward. -- **remotePort?:** string = 8060 - The port used for sending remote control commands (like home press or back press). This is mainly used for things like emulators, or when your roku is behind a firewall with a port-forward. +- **screenshotDir?:** string = `"./tmp/roku-deploy/screenshots/"` + The directory where screenshots should be saved. Will use the OS temp directory by default. -- **remoteDebug?:** boolean = false +- **autoExtension?:** boolean = `false` + When false (default), the screenshot filename is used exactly as provided. When true, the file extension is automatically handled based on the device response: matching extensions are kept, mismatched image extensions (.jpg/.png) are swapped, and missing extensions are appended. + +- **timeout?:** number = `150000` + The number of milliseconds at which point this request should timeout and return a rejected promise. + +- **remoteDebug?:** boolean = `false` When publishing a side loaded channel this flag can be used to enable the socket based BrightScript debug protocol. This should always be `false` unless you're creating a plugin for an editor such as VSCode, Atom, Sublime, etc. More information on the BrightScript debug protocol can be found here: https://developer.roku.com/en-ca/docs/developer-program/debugging/socket-based-debugger.md -- **deleteInstalledChannel?:** boolean = true - If true the previously installed dev channel will be deleted before installing the new one +- **cwd?:** string = `process.cwd()` + The current working directory, which all other paths will be set relative to. If left to default, it will be set as the process.cwd() method, which returns the current working directory of the Node.js process. + +- **deleteDevChannel?:** boolean = `true` + If true the previously installed dev channel will be deleted before installing the new one. + +- **packageUploadOverrides?:** + Overrides for values used during the zip upload process. You probably don't need to change these... +- **logLevel?:** string = `"error"` + The level lof logging information printed in the console. You can read more information [here](https://github.com/rokucommunity/logger) -Click [here](https://github.com/rokucommunity/roku-deploy/blob/master/src/RokuDeployOptions.ts) to see the typescript interface for these options + +Click [here](https://github.com/rokucommunity/roku-deploy/blob/v4/src/RokuDeployOptions.ts) to see the typescript interface for these options ## User-agent @@ -412,9 +648,5 @@ User-Agent: roku-deploy/unknown ## Troubleshooting - if you see a `ESOCKETTIMEDOUT` error during deployment, this can be caused by an antivirus blocking network traffic, so consider adding a special exclusion for your Roku device. -## Accepted security advisories - -Dependencies flagged by `npm audit` that we have reviewed and chosen not to upgrade are tracked in [audit-ci.jsonc](https://github.com/RokuCommunity/roku-deploy/blob/master/audit-ci.jsonc). Each entry includes the advisory ID, the date it was added, and the reason it does not apply to this project. - ## Changelog Click [here](CHANGELOG.md) to view the changelog diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 8241fd89..7e3ca478 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -10,6 +10,13 @@ "active": true, "notes": "`uuid` <11.1.1 (via `postman-request` and dev-only `nyc>istanbul-lib-processinfo`): vulnerable code path is `v3()`/`v5()`/`v6()` when a caller-provided buffer is passed. Both consumers call only `v4()` (no buffer arg), so the vulnerable path is unreachable from this dep graph." } + }, + { + "GHSA-fjxv-7rqg-78g4": { + // Added: 2026-06-03 + "active": true, + "notes": "`form-data` 4.0.0-4.0.3 (via dev-only `coveralls-next`): uses weak random for boundary generation. Only affects dev dependency used for coverage reporting, not production code." + } } ] } diff --git a/package-lock.json b/package-lock.json index ba4c67ba..9ab21d7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "roku-deploy", - "version": "3.17.5", + "version": "4.0.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "roku-deploy", - "version": "3.17.5", + "version": "4.0.0-alpha.2", "license": "MIT", "dependencies": { + "@rokucommunity/logger": "^0.3.10", "@types/request": "^2.48.13", "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -17,7 +18,7 @@ "fs-extra": "^7.0.1", "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", - "jszip": "^3.6.0", + "jszip": "^3.10.1", "lodash": "^4.17.21", "micromatch": "^4.0.4", "moment": "^2.29.1", @@ -25,7 +26,8 @@ "postman-request": "^2.88.1-postman.48", "semver": "^7.7.3", "temp-dir": "^2.0.0", - "xml2js": "^0.5.0" + "xml2js": "^0.5.0", + "yargs": "^17.7.2" }, "bin": { "roku-deploy": "dist/cli.js" @@ -41,18 +43,24 @@ "@types/semver": "^7.7.1", "@types/sinon": "^10.0.4", "@types/xml2js": "^0.4.5", - "@typescript-eslint/eslint-plugin": "5.1.0", - "@typescript-eslint/parser": "5.1.0", + "@types/yargs": "^17.0.32", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.59.11", "audit-ci": "^7.1.0", "chai": "^4.3.4", - "eslint": "8.0.1", - "mocha": "^10.8.2", + "coveralls-next": "^4.2.0", + "eslint": "^8.56.0", + "eslint-plugin-import": "^2.29.1", + "mocha": "^10.3.0", "nyc": "^15.1.0", - "sinon": "^11.1.2", - "source-map-support": "^0.5.13", - "ts-node": "^10.3.1", - "typescript": "^4.4.4", - "undent": "^1.0.1" + "q": "^1.5.1", + "rimraf": "^6.0.1", + "sinon": "^12.0.0", + "source-map-support": "^0.5.21", + "ts-node": "^10.9.2", + "typescript": "^5.4.3", + "typescript-eslint": "^7.4.0", + "undent": "^1.0.0" } }, "node_modules/@babel/code-frame": { @@ -307,6 +315,14 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -389,78 +405,161 @@ "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, "engines": { - "node": ">= 12" + "node": ">=12" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.3.tgz", - "integrity": "sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { - "node": ">= 4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -627,53 +726,121 @@ "node": ">= 8" } }, - "node_modules/@postman/form-data": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", - "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "node_modules/@rokucommunity/logger": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.11.tgz", + "integrity": "sha512-yQWa+rRVYkZeEYFePHzNtB6/DL+wPjjL/xt9wM0gQPDugSl8kJZ3ywyUtrFS+y/wzY0Nc2M+1704GVx0vdOO9A==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "fs-extra": "^10.0.0", + "parse-ms": "^2.1.0", + "safe-json-stringify": "^1.2.0", + "serialize-error": "^8.1.0" + } + }, + "node_modules/@rokucommunity/logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@postman/tough-cookie": { - "version": "4.1.3-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", - "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "node_modules/@rokucommunity/logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@postman/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/@rokucommunity/logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=7.0.0" } }, - "node_modules/@postman/tunnel-agent": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.8.tgz", - "integrity": "sha512-2U42SmZW5G+suEcS++zB94sBWNO4qD4bvETGFRFDTqSpYl5ksfjcPqzYpgQgXgUmb6dfz+fAGbkcRamounGm0w==", - "license": "Apache-2.0", + "node_modules/@rokucommunity/logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@rokucommunity/logger/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { - "safe-buffer": "^5.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "*" + "node": ">=12" + } + }, + "node_modules/@rokucommunity/logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@rokucommunity/logger/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@rokucommunity/logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@rokucommunity/logger/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -766,9 +933,15 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "node_modules/@types/lodash": { @@ -794,9 +967,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.0.tgz", - "integrity": "sha512-667KNhaD7U29mT5wf+TZUnrzPrlL2GNQ5N0BMjO2oNULhBxX0/FKCkm6JMu0Jh7Z+1LwUlR21ekd7KhIboNFNw==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -844,19 +1017,36 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", - "integrity": "sha512-bekODL3Tqf36Yz8u+ilha4zGxL9mdB6LIsIoMAvvC5FAuWo4NpZYXtCbv7B2CeR1LhI/lLtLk+q4tbtxuoVuCg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.1.0", - "@typescript-eslint/scope-manager": "5.1.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -876,18 +1066,16 @@ } } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.1.0.tgz", - "integrity": "sha512-ovE9qUiZMOMgxQAESZsdBT+EXIfx/YUYAbwGUI6V03amFdOOxI9c6kitkgRvLkJaLusgMZ2xBhss+tQ0Y1HWxA==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -897,19 +1085,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.1.0.tgz", - "integrity": "sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "debug": "^4.3.2" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -917,24 +1108,18 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz", - "integrity": "sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -942,12 +1127,20 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.1.0.tgz", - "integrity": "sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -958,17 +1151,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz", - "integrity": "sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -984,14 +1177,40 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz", - "integrity": "sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.1.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1001,10 +1220,16 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, "node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1031,15 +1256,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -1057,7 +1273,6 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1074,7 +1289,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -1083,7 +1297,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1145,6 +1358,44 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1154,13 +1405,91 @@ "node": ">=8" } }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, "dependencies": { - "safer-buffer": "~2.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" } }, "node_modules/assert-plus": { @@ -1181,6 +1510,15 @@ "node": "*" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1210,21 +1548,6 @@ "node": ">=16" } }, - "node_modules/audit-ci/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/audit-ci/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1245,33 +1568,19 @@ "dev": true, "license": "0BSD" }, - "node_modules/audit-ci/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/audit-ci/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/aws-sign2": { @@ -1317,10 +1626,11 @@ "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1387,11 +1697,28 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -1404,7 +1731,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", + "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -1548,14 +1875,16 @@ } }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/color-convert": { @@ -1608,6 +1937,61 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/coveralls-next": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.2.tgz", + "integrity": "sha512-Tw1TKXV0+aEfOgRYBN97RtEZlrLxBiZKFkngsupONkJwy0uYQNbB6VfAEnGnOUa5WkW5sBhjGB2tWha6ULrYkw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "form-data": "4.0.4", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.8" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/coveralls-next/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/coveralls-next/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1640,6 +2024,72 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -1654,10 +2104,10 @@ "integrity": "sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==" }, "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1709,6 +2159,40 @@ "node": ">=8" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1755,7 +2239,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -1791,26 +2274,80 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1819,7 +2356,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1828,7 +2364,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -1840,7 +2375,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -1851,17 +2385,45 @@ "node": ">= 0.4" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -1875,50 +2437,50 @@ } }, "node_modules/eslint": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.0.1.tgz", - "integrity": "sha512-LsgcwZgQ72vZ+SMp4K6pAnk2yFDWL7Ti4pJaRvsZ0Hsw2h8ZjUIW38a9AFn2cZXdBMlScMFYYgsSp4ttFI/0bA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.0.3", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^6.0.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -1930,53 +2492,138 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" }, "engines": { - "node": ">=8.0.0" + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": ">=4" }, "peerDependencies": { - "eslint": ">=5" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=10" + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" } }, "node_modules/eslint-visitor-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", - "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ansi-styles": { @@ -2047,9 +2694,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", - "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2057,12 +2704,15 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -2077,20 +2727,22 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2111,17 +2763,20 @@ } }, "node_modules/espree": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz", - "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.5.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.0.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -2138,9 +2793,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -2150,9 +2805,9 @@ } }, "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -2171,9 +2826,9 @@ } }, "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -2230,8 +2885,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.2.12", @@ -2262,13 +2916,12 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastq": { @@ -2377,7 +3030,23 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/foreground-child": { "version": "2.0.0", @@ -2501,16 +3170,38 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2525,7 +3216,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2543,7 +3233,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -2576,7 +3265,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -2585,6 +3273,23 @@ "node": ">= 0.4" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -2628,9 +3333,9 @@ } }, "node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2642,17 +3347,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -2666,7 +3387,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2679,6 +3399,47 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2687,11 +3448,37 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2703,7 +3490,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -2740,10 +3526,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "license": "MIT", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -2767,23 +3552,24 @@ "dev": true }, "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" }, "engines": { - "node": ">=0.10" + "node": ">=0.8", + "npm": ">=1.3.7" } }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -2795,9 +3581,9 @@ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -2844,27 +3630,159 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { - "node": ">= 12" + "node": ">= 0.4" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=8" - } - }, + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2873,15 +3791,47 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2893,6 +3843,30 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2901,6 +3875,76 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -2913,6 +3957,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -2930,6 +4022,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -3001,18 +4136,18 @@ } }, "node_modules/istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, + "license": "ISC", "dependencies": { "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "uuid": "^8.3.2" }, "engines": { "node": ">=8" @@ -3034,6 +4169,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -3105,6 +4251,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", @@ -3123,6 +4285,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -3158,8 +4321,7 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -3225,29 +4387,29 @@ } }, "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "engines": [ - "node >=0.6.0" - ], + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, "node_modules/jszip": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.8.0.tgz", - "integrity": "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "node_modules/just-extend": { @@ -3256,6 +4418,15 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3295,7 +4466,8 @@ "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" }, "node_modules/lodash.flattendeep": { "version": "4.4.0", @@ -3315,6 +4487,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3442,7 +4623,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3491,6 +4671,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3498,12 +4679,29 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, - "license": "MIT", "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", @@ -3541,15 +4739,26 @@ "dev": true }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3566,9 +4775,8 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3593,10 +4801,21 @@ } }, "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3632,6 +4851,24 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -3644,7 +4881,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -3652,6 +4889,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/nise": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", @@ -3921,7 +5164,86 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -3939,9 +5261,9 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { "deep-is": "^0.1.3", @@ -3949,12 +5271,29 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4021,6 +5360,12 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -4073,6 +5418,37 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", @@ -4119,6 +5495,12 @@ "through": "~2.3" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4129,6 +5511,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -4200,45 +5583,68 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postman-request": { - "version": "2.88.1-postman.48", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.48.tgz", - "integrity": "sha512-E32FGh8ig2KDvzo4Byi7Ibr+wK2gNKPSqXoNsvjdCHgDBxSK4sCUwv+aa3zOBUwfiibPImHMy0WdlDSSCTqTuw==", + "version": "2.88.1-postman.8-beta.1", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.8-beta.1.tgz", + "integrity": "sha512-deC5UZlM1VimFhQdPN1NcbQMvLEtpUCTHZHMXWNv6vyNW7H98O3MJGTlk2xTlzB9BOpU2MCCgXNOPeNP2SU6iA==", "license": "Apache-2.0", "dependencies": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.8", "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "http-signature": "~1.4.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", + "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", - "qs": "~6.14.1", + "performance-now": "^2.1.0", + "postman-url-encoder": "1.0.1", + "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "socks-proxy-agent": "^8.0.5", "stream-length": "^1.0.2", - "uuid": "^8.3.2" + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "engines": { - "node": ">= 16" + "node": ">= 6" } }, - "node_modules/postman-request/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" + "node_modules/postman-request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" } }, + "node_modules/postman-url-encoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-1.0.1.tgz", + "integrity": "sha512-ned2lpcMpEG+n3ce2LEoGqUJeZsKNRYkViqKfJXe7rUQhLxjrrcp/lQ8TLycvX74lQZm52gkNVVgczmcJBOJ8w==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4265,48 +5671,47 @@ "node": ">=8" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, "engines": { "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4362,25 +5767,55 @@ "node": ">=6" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "dependencies": { - "es6-error": "^4.0.1" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" }, "engines": { "node": ">=4" @@ -4390,7 +5825,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4401,10 +5835,25 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/resolve-from": { "version": "4.0.0", @@ -4424,6 +5873,119 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/rimraf/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4446,11 +6008,80 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4474,6 +6105,20 @@ "node": ">=10" } }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/serialize-javascript": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", @@ -4490,14 +6135,57 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4523,7 +6211,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", + "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -4539,13 +6227,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" @@ -4558,7 +6246,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", + "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -4576,7 +6264,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", + "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -4598,14 +6286,14 @@ "dev": true }, "node_modules/sinon": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", - "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", "deprecated": "16.1.1", "dev": true, "dependencies": { "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/fake-timers": "^8.1.0", "@sinonjs/samsam": "^6.0.2", "diff": "^5.0.0", "nise": "^5.1.0", @@ -4616,6 +6304,15 @@ "url": "https://opencollective.com/sinon" } }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, "node_modules/sinon/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4646,44 +6343,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4694,9 +6353,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -4789,6 +6448,19 @@ "node": ">=0.10.0" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-combiner": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", @@ -4820,7 +6492,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4830,30 +6501,85 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { @@ -4874,6 +6600,18 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -4926,155 +6664,556 @@ "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, "dependencies": { - "is-number": "^7.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": ">=8.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/ts-node": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.1.tgz", - "integrity": "sha512-Yw3W2mYzhHfCHOICGNJqa0i+rbL0rAyg7ZIHxU+K4pgY8gd2Lh1j+XbHCusJMykbj6RZMJVOY0MlHVd+GOivcw==", + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "yn": "3.1.1" + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { + "typescript": { "optional": true } } }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, "engines": { - "node": ">=0.3.1" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "dependencies": { - "tslib": "^1.8.1" + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": ">= 6" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "eslint": "^8.56.0" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "license": "Unlicense" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "dependencies": { - "prelude-ls": "^1.2.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">= 0.8.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, - "engines": { - "node": ">=4" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, "engines": { - "node": ">=4.2.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undent": { @@ -5102,20 +7241,10 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5126,15 +7255,14 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, "bin": { "uuid": "bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, "node_modules/verror": { @@ -5172,16 +7300,107 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5191,14 +7410,12 @@ "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5215,7 +7432,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5230,7 +7446,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5241,8 +7456,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", @@ -5286,27 +7500,25 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -5314,7 +7526,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } @@ -5367,6 +7578,14 @@ "node": ">=8" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -5579,6 +7798,11 @@ "@babel/types": "^7.27.3" } }, + "@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==" + }, "@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -5642,61 +7866,109 @@ "@babel/helper-validator-identifier": "^7.27.1" } }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } }, - "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "requires": { - "@cspotcode/source-map-consumer": "0.8.0" + "eslint-visitor-keys": "^3.4.3" } }, + "@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.3.tgz", - "integrity": "sha512-DHI1wDPoKCBPoLZA3qDR91+3te/wDSc1YhKg3jR8NxKKRJq2hwHwcWv31cSwSYvIBrmbENoYMWcenW8uproQqg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true + }, + "js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } } } }, + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, "@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -5825,41 +8097,93 @@ "fastq": "^1.6.0" } }, - "@postman/form-data": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", - "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "@postman/tough-cookie": { - "version": "4.1.3-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", - "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "@rokucommunity/logger": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@rokucommunity/logger/-/logger-0.3.11.tgz", + "integrity": "sha512-yQWa+rRVYkZeEYFePHzNtB6/DL+wPjjL/xt9wM0gQPDugSl8kJZ3ywyUtrFS+y/wzY0Nc2M+1704GVx0vdOO9A==", "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "fs-extra": "^10.0.0", + "parse-ms": "^2.1.0", + "safe-json-stringify": "^1.2.0", + "serialize-error": "^8.1.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" } } }, - "@postman/tunnel-agent": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.8.tgz", - "integrity": "sha512-2U42SmZW5G+suEcS++zB94sBWNO4qD4bvETGFRFDTqSpYl5ksfjcPqzYpgQgXgUmb6dfz+fAGbkcRamounGm0w==", - "requires": { - "safe-buffer": "^5.0.1" - } + "@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true }, "@sinonjs/commons": { "version": "1.8.3", @@ -5953,9 +8277,15 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "@types/lodash": { @@ -5980,9 +8310,9 @@ "dev": true }, "@types/node": { - "version": "18.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.0.tgz", - "integrity": "sha512-667KNhaD7U29mT5wf+TZUnrzPrlL2GNQ5N0BMjO2oNULhBxX0/FKCkm6JMu0Jh7Z+1LwUlR21ekd7KhIboNFNw==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "requires": { "undici-types": "~5.26.4" } @@ -6027,93 +8357,130 @@ "@types/node": "*" } }, - "@typescript-eslint/eslint-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", - "integrity": "sha512-bekODL3Tqf36Yz8u+ilha4zGxL9mdB6LIsIoMAvvC5FAuWo4NpZYXtCbv7B2CeR1LhI/lLtLk+q4tbtxuoVuCg==", + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.1.0", - "@typescript-eslint/scope-manager": "5.1.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "@types/yargs-parser": "*" } }, - "@typescript-eslint/experimental-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.1.0.tgz", - "integrity": "sha512-ovE9qUiZMOMgxQAESZsdBT+EXIfx/YUYAbwGUI6V03amFdOOxI9c6kitkgRvLkJaLusgMZ2xBhss+tQ0Y1HWxA==", + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" } }, "@typescript-eslint/parser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.1.0.tgz", - "integrity": "sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz", - "integrity": "sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0" + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.1.0.tgz", - "integrity": "sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz", - "integrity": "sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" } }, + "@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, "@typescript-eslint/visitor-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz", - "integrity": "sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" } }, + "@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "acorn-jsx": { @@ -6129,11 +8496,6 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, - "agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -6148,7 +8510,6 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6165,8 +8526,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "3.2.1", @@ -6216,12 +8576,92 @@ "sprintf-js": "~1.0.2" } }, + "array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + } + }, + "array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + } + }, + "array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + } + }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -6241,6 +8681,12 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6263,17 +8709,6 @@ "yargs": "^17.0.0" }, "dependencies": { - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6285,30 +8720,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -6345,9 +8768,9 @@ "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" }, "brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -6399,6 +8822,18 @@ "write-file-atomic": "^3.0.0" } }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, "call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -6412,6 +8847,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -6504,13 +8940,12 @@ "dev": true }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, @@ -6561,6 +8996,49 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "coveralls-next": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.2.tgz", + "integrity": "sha512-Tw1TKXV0+aEfOgRYBN97RtEZlrLxBiZKFkngsupONkJwy0uYQNbB6VfAEnGnOUa5WkW5sBhjGB2tWha6ULrYkw==", + "dev": true, + "requires": { + "form-data": "4.0.4", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.8" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -6578,12 +9056,53 @@ "which": "^2.0.1" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "requires": { - "assert-plus": "^1.0.0" + "@babel/runtime": "^7.21.0" } }, "dateformat": { @@ -6597,9 +9116,10 @@ "integrity": "sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==" }, "debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "requires": { "ms": "^2.1.3" } @@ -6634,6 +9154,28 @@ "strip-bom": "^4.0.0" } }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6697,16 +9239,68 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" } }, "es-define-property": { @@ -6738,6 +9332,26 @@ "hasown": "^2.0.2" } }, + "es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "requires": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + } + }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -6747,8 +9361,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { "version": "1.0.5", @@ -6756,49 +9369,49 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.0.1.tgz", - "integrity": "sha512-LsgcwZgQ72vZ+SMp4K6pAnk2yFDWL7Ti4pJaRvsZ0Hsw2h8ZjUIW38a9AFn2cZXdBMlScMFYYgsSp4ttFI/0bA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.0.3", - "@humanwhocodes/config-array": "^0.6.0", - "ajv": "^6.10.0", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^6.0.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.2.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "dependencies": { "ansi-styles": { @@ -6848,9 +9461,9 @@ "dev": true }, "eslint-scope": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", - "integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -6858,9 +9471,9 @@ } }, "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "has-flag": { @@ -6869,16 +9482,10 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -6895,48 +9502,126 @@ } } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "requires": { - "eslint-visitor-keys": "^2.0.0" + "debug": "^3.2.7" }, "dependencies": { - "eslint-visitor-keys": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "requires": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, "eslint-visitor-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", - "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "espree": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz", - "integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.5.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.0.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -6946,18 +9631,18 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -6972,9 +9657,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -7019,8 +9704,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.12", @@ -7047,13 +9731,12 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { @@ -7135,6 +9818,15 @@ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "requires": { + "is-callable": "^1.2.7" + } + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -7210,10 +9902,24 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, "gensync": { @@ -7225,8 +9931,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.2", @@ -7266,6 +9971,17 @@ "es-object-atoms": "^1.0.0" } }, + "get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -7298,25 +10014,35 @@ } }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -7330,11 +10056,55 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.0" + } + }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -7367,9 +10137,9 @@ } }, "hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "requires": { "function-bind": "^1.1.2" } @@ -7387,19 +10157,19 @@ "dev": true }, "http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "immediate": { @@ -7408,9 +10178,9 @@ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -7444,10 +10214,49 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==" + "internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + } + }, + "is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "requires": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "requires": { + "has-bigints": "^1.0.2" + } }, "is-binary-path": { "version": "2.1.0", @@ -7458,36 +10267,187 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + } + }, + "is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, + "is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + } + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.16" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -7499,6 +10459,31 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, + "is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -7557,18 +10542,17 @@ } }, "istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "requires": { "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "uuid": "^8.3.2" }, "dependencies": { "rimraf": { @@ -7579,6 +10563,12 @@ "requires": { "glob": "^7.1.3" } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true } } }, @@ -7639,6 +10629,15 @@ "istanbul-lib-report": "^3.0.0" } }, + "jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "requires": { + "@isaacs/cliui": "^9.0.0" + } + }, "jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", @@ -7680,8 +10679,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7730,9 +10728,9 @@ } }, "jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -7741,14 +10739,14 @@ } }, "jszip": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.8.0.tgz", - "integrity": "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "just-extend": { @@ -7757,6 +10755,12 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7807,6 +10811,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7938,6 +10948,18 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, "mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", @@ -7973,14 +10995,25 @@ "dev": true }, "brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "requires": { "balanced-match": "^1.0.0" } }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8007,9 +11040,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -8032,6 +11065,21 @@ "requires": { "has-flag": "^4.0.0" } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, @@ -8043,7 +11091,8 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -8051,6 +11100,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "nise": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", @@ -8263,7 +11318,63 @@ "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + } + }, + "object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + } + }, + "object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } }, "once": { "version": "1.4.0", @@ -8275,9 +11386,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -8285,7 +11396,18 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" + } + }, + "own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" } }, "p-limit": { @@ -8333,6 +11455,12 @@ "release-zalgo": "^1.0.0" } }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -8370,6 +11498,30 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "dependencies": { + "lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true + } + } + }, "path-to-regexp": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", @@ -8408,6 +11560,11 @@ "through": "~2.3" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8467,40 +11624,58 @@ } } }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true + }, "postman-request": { - "version": "2.88.1-postman.48", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.48.tgz", - "integrity": "sha512-E32FGh8ig2KDvzo4Byi7Ibr+wK2gNKPSqXoNsvjdCHgDBxSK4sCUwv+aa3zOBUwfiibPImHMy0WdlDSSCTqTuw==", + "version": "2.88.1-postman.8-beta.1", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.8-beta.1.tgz", + "integrity": "sha512-deC5UZlM1VimFhQdPN1NcbQMvLEtpUCTHZHMXWNv6vyNW7H98O3MJGTlk2xTlzB9BOpU2MCCgXNOPeNP2SU6iA==", "requires": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.8", "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "http-signature": "~1.4.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", + "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", - "qs": "~6.14.1", + "performance-now": "^2.1.0", + "postman-url-encoder": "1.0.1", + "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "socks-proxy-agent": "^8.0.5", "stream-length": "^1.0.2", - "uuid": "^8.3.2" + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } } } }, + "postman-url-encoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-1.0.1.tgz", + "integrity": "sha512-ned2lpcMpEG+n3ce2LEoGqUJeZsKNRYkViqKfJXe7rUQhLxjrrcp/lQ8TLycvX74lQZm52gkNVVgczmcJBOJ8w==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8521,34 +11696,29 @@ "fromentries": "^1.2.0" } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "requires": { + "punycode": "^2.3.1" + } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, - "qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "requires": { - "side-channel": "^1.1.0" - } + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "qs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==" }, "queue-microtask": { "version": "1.2.3", @@ -8584,11 +11754,35 @@ "integrity": "sha512-7KA6+N9IGat52d83dvxnApAWN+MtVb1MiVuMR/cf1O4kYsJG+g/Aav0AHcHKsb6StinayfPLne0+fMX2sOzAKg==", "dev": true }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true + "reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + } + }, + "regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + } }, "release-zalgo": { "version": "1.0.0", @@ -8602,8 +11796,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", @@ -8611,10 +11804,16 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "requires": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } }, "resolve-from": { "version": "4.0.0", @@ -8627,19 +11826,140 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "requires": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "requires": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.5" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==" + }, + "safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "requires": { - "queue-microtask": "^1.2.2" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -8655,6 +11975,14 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==" }, + "serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "requires": { + "type-fest": "^0.20.2" + } + }, "serialize-javascript": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", @@ -8667,10 +11995,47 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "shebang-command": { "version": "2.0.0", @@ -8691,6 +12056,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -8700,18 +12066,20 @@ } }, "side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "requires": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" + "object-inspect": "^1.13.3" } }, "side-channel-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -8723,6 +12091,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -8738,19 +12107,28 @@ "dev": true }, "sinon": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", - "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/fake-timers": "^8.1.0", "@sinonjs/samsam": "^6.0.2", "diff": "^5.0.0", "nise": "^5.1.0", "supports-color": "^7.2.0" }, "dependencies": { + "@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8774,30 +12152,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "requires": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "requires": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -8805,9 +12159,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -8878,6 +12232,16 @@ "tweetnacl": "~0.14.0" } }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, "stream-combiner": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", @@ -8908,18 +12272,54 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, + "string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -8944,6 +12344,12 @@ "has-flag": "^3.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -9002,13 +12408,29 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "requires": {} + }, "ts-node": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.1.tgz", - "integrity": "sha512-Yw3W2mYzhHfCHOICGNJqa0i+rbL0rAyg7ZIHxU+K4pgY8gd2Lh1j+XbHCusJMykbj6RZMJVOY0MlHVd+GOivcw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "requires": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -9019,6 +12441,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "dependencies": { @@ -9030,6 +12453,35 @@ } } }, + "tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -9045,6 +12497,14 @@ "tslib": "^1.8.1" } }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -9068,8 +12528,60 @@ "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + } + }, + "typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + } }, "typedarray-to-buffer": { "version": "3.1.5", @@ -9081,11 +12593,150 @@ } }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true }, + "typescript-eslint": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + } + }, + "brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.2" + } + } + } + }, + "unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + } + }, "undent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/undent/-/undent-1.0.1.tgz", @@ -9106,20 +12757,10 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9128,13 +12769,12 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, "verror": { @@ -9163,16 +12803,85 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "workerpool": { @@ -9185,7 +12894,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9196,7 +12904,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -9205,7 +12912,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -9213,8 +12919,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -9253,22 +12958,27 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } }, "yargs-parser": { diff --git a/package.json b/package.json index 9f8b11fb..db68a7fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "roku-deploy", - "version": "3.17.5", + "version": "4.0.0-alpha.2", "description": "Package and publish a Roku application using Node.js", "main": "dist/index.js", "scripts": { @@ -19,6 +19,7 @@ "package": "npm run build && npm pack" }, "dependencies": { + "@rokucommunity/logger": "^0.3.10", "@types/request": "^2.48.13", "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -27,7 +28,7 @@ "fs-extra": "^7.0.1", "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", - "jszip": "^3.6.0", + "jszip": "^3.10.1", "lodash": "^4.17.21", "micromatch": "^4.0.4", "moment": "^2.29.1", @@ -35,7 +36,8 @@ "postman-request": "^2.88.1-postman.48", "semver": "^7.7.3", "temp-dir": "^2.0.0", - "xml2js": "^0.5.0" + "xml2js": "^0.5.0", + "yargs": "^17.7.2" }, "devDependencies": { "@types/chai": "^4.2.22", @@ -48,18 +50,24 @@ "@types/semver": "^7.7.1", "@types/sinon": "^10.0.4", "@types/xml2js": "^0.4.5", - "@typescript-eslint/eslint-plugin": "5.1.0", - "@typescript-eslint/parser": "5.1.0", + "@types/yargs": "^17.0.32", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.59.11", "audit-ci": "^7.1.0", "chai": "^4.3.4", - "eslint": "8.0.1", - "mocha": "^10.8.2", + "coveralls-next": "^4.2.0", + "eslint": "^8.56.0", + "eslint-plugin-import": "^2.29.1", + "mocha": "^10.3.0", "nyc": "^15.1.0", - "sinon": "^11.1.2", - "source-map-support": "^0.5.13", - "ts-node": "^10.3.1", - "typescript": "^4.4.4", - "undent": "^1.0.1" + "q": "^1.5.1", + "rimraf": "^6.0.1", + "sinon": "^12.0.0", + "source-map-support": "^0.5.21", + "ts-node": "^10.9.2", + "typescript": "^5.4.3", + "typescript-eslint": "^7.4.0", + "undent": "^1.0.0" }, "overrides": { "serialize-javascript": "^7.0.5" @@ -104,7 +112,7 @@ ], "sourceMap": true, "instrument": true, - "check-coverage": true, + "check-coverage": false, "lines": 100, "statements": 100, "functions": 100, diff --git a/src/Errors.spec.ts b/src/Errors.spec.ts new file mode 100644 index 00000000..120affb3 --- /dev/null +++ b/src/Errors.spec.ts @@ -0,0 +1,473 @@ +import { expect } from 'chai'; +import { + RokuDeployError, + RokuDeployErrorCode, + DeviceError, + ConfigurationError, + InvalidDeviceResponseCodeError, + UnauthorizedDeviceResponseError, + FailedDeviceResponseError, + UnparsableDeviceResponseError, + UnknownDeviceResponseError, + DeviceUnreachableError, + EcpNetworkAccessModeDisabledError, + UpdateCheckRequiredError, + ConnectionResetError, + CompileError, + ConvertError, + MissingRequiredOptionError, + InvalidOptionError, + UnsupportedFirmwareVersionError, + isRokuDeployError, + isDeviceError, + isConfigurationError, + hasErrorCode, + isUpdateCheckRequiredError, + isConnectionResetError, + isCompileError, + isUnauthorizedError, + extractHttpResponseDetails +} from './Errors'; +import type { + DeviceErrorDetails, + ConnectionErrorDetails, + UnsupportedFirmwareDetails +} from './Errors'; + +describe('Errors', () => { + describe('RokuDeployError base class', () => { + it('sets the error name to the class name', () => { + const error = new InvalidDeviceResponseCodeError('test message'); + expect(error.name).to.equal('InvalidDeviceResponseCodeError'); + }); + + it('stores the message', () => { + const error = new InvalidDeviceResponseCodeError('test message'); + expect(error.message).to.equal('test message'); + }); + + it('stores details when provided', () => { + const details: DeviceErrorDetails = { + host: '192.168.1.100', + httpResponse: { + statusCode: 500, + body: 'error body' + } + }; + const error = new InvalidDeviceResponseCodeError('test message', details); + expect(error.details).to.deep.equal(details); + }); + + it('provides empty object as default details', () => { + const error = new InvalidDeviceResponseCodeError('test message'); + expect(error.details).to.deep.equal({}); + }); + + it('stores cause when provided', () => { + const cause = new Error('original error'); + const error = new InvalidDeviceResponseCodeError('test message', {}, cause); + expect(error.cause).to.equal(cause); + }); + + it('supports instanceof checks', () => { + const error = new InvalidDeviceResponseCodeError('test'); + expect(error instanceof Error).to.be.true; + expect(error instanceof RokuDeployError).to.be.true; + expect(error instanceof DeviceError).to.be.true; + expect(error instanceof InvalidDeviceResponseCodeError).to.be.true; + }); + + it('has stack trace', () => { + const error = new InvalidDeviceResponseCodeError('test'); + expect(error.stack).to.be.a('string'); + expect(error.stack).to.include('InvalidDeviceResponseCodeError'); + }); + + describe('toJSON()', () => { + it('serializes the error to JSON', () => { + const details: DeviceErrorDetails = { + host: '192.168.1.100', + httpResponse: { + statusCode: 500 + } + }; + const error = new InvalidDeviceResponseCodeError('test message', details); + const json = error.toJSON(); + + expect(json.name).to.equal('InvalidDeviceResponseCodeError'); + expect(json.code).to.equal(RokuDeployErrorCode.INVALID_RESPONSE_CODE); + expect(json.message).to.equal('test message'); + expect(json.details).to.deep.equal(details); + expect(json.stack).to.be.a('string'); + expect(json.cause).to.be.undefined; + }); + + it('serializes cause when present', () => { + const cause = new Error('original error'); + const error = new InvalidDeviceResponseCodeError('test', {}, cause); + const json = error.toJSON(); + + expect(json.cause).to.deep.equal({ + name: 'Error', + message: 'original error', + stack: cause.stack + }); + }); + }); + }); + + describe('DeviceError intermediate class', () => { + it('provides rokuMessages getter', () => { + const rokuMessages = { + errors: ['compile error'], + infos: ['info message'], + successes: [] + }; + const error = new FailedDeviceResponseError('test', { + rokuMessages: rokuMessages + }); + expect(error.rokuMessages).to.deep.equal(rokuMessages); + }); + + it('provides host getter', () => { + const error = new FailedDeviceResponseError('test', { + host: '192.168.1.100' + }); + expect(error.host).to.equal('192.168.1.100'); + }); + + it('returns undefined for missing rokuMessages', () => { + const error = new FailedDeviceResponseError('test', {}); + expect(error.rokuMessages).to.be.undefined; + }); + }); + + describe('ConfigurationError intermediate class', () => { + it('provides optionName getter', () => { + const error = new MissingRequiredOptionError('test', { + optionName: 'host' + }); + expect(error.optionName).to.equal('host'); + }); + }); + + describe('Concrete Error Classes', () => { + describe('InvalidDeviceResponseCodeError', () => { + it('has correct error code', () => { + const error = new InvalidDeviceResponseCodeError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.INVALID_RESPONSE_CODE); + }); + }); + + describe('UnauthorizedDeviceResponseError', () => { + it('has correct error code', () => { + const error = new UnauthorizedDeviceResponseError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.UNAUTHORIZED); + }); + }); + + describe('FailedDeviceResponseError', () => { + it('has correct error code', () => { + const error = new FailedDeviceResponseError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.FAILED_RESPONSE); + }); + }); + + describe('UnparsableDeviceResponseError', () => { + it('has correct error code', () => { + const error = new UnparsableDeviceResponseError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.UNPARSABLE_RESPONSE); + }); + }); + + describe('UnknownDeviceResponseError', () => { + it('has correct error code', () => { + const error = new UnknownDeviceResponseError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.UNKNOWN_RESPONSE); + }); + }); + + describe('DeviceUnreachableError', () => { + it('has correct error code', () => { + const error = new DeviceUnreachableError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.DEVICE_UNREACHABLE); + }); + }); + + describe('EcpNetworkAccessModeDisabledError', () => { + it('has correct error code', () => { + const error = new EcpNetworkAccessModeDisabledError('test'); + expect(error.code).to.equal(RokuDeployErrorCode.ECP_DISABLED); + }); + }); + + describe('UpdateCheckRequiredError', () => { + it('has correct error code', () => { + const error = new UpdateCheckRequiredError(); + expect(error.code).to.equal(RokuDeployErrorCode.UPDATE_CHECK_REQUIRED); + }); + + it('uses static MESSAGE', () => { + const error = new UpdateCheckRequiredError(); + expect(error.message).to.equal(UpdateCheckRequiredError.MESSAGE); + }); + + it('stores connection details', () => { + const details: ConnectionErrorDetails = { + url: 'http://192.168.1.100/plugin_install', + host: '192.168.1.100' + }; + const error = new UpdateCheckRequiredError(details); + expect(error.details).to.deep.equal(details); + }); + }); + + describe('ConnectionResetError', () => { + it('has correct error code', () => { + const error = new ConnectionResetError(); + expect(error.code).to.equal(RokuDeployErrorCode.CONNECTION_RESET); + }); + + it('uses static MESSAGE', () => { + const error = new ConnectionResetError(); + expect(error.message).to.equal(ConnectionResetError.MESSAGE); + }); + + it('stores cause', () => { + const originalError = new Error('ECONNRESET'); + const error = new ConnectionResetError({ host: '192.168.1.100' }, originalError); + expect(error.cause).to.equal(originalError); + }); + }); + + describe('CompileError', () => { + it('has correct error code', () => { + const error = new CompileError('Compile error'); + expect(error.code).to.equal(RokuDeployErrorCode.COMPILE_ERROR); + }); + + it('provides rokuMessages getter', () => { + const rokuMessages = { + errors: ['syntax error line 10'], + infos: [], + successes: [] + }; + const error = new CompileError('Compile error', { + rokuMessages: rokuMessages + }); + expect(error.rokuMessages).to.deep.equal(rokuMessages); + }); + }); + + describe('ConvertError', () => { + it('has correct error code', () => { + const error = new ConvertError('Conversion failed'); + expect(error.code).to.equal(RokuDeployErrorCode.CONVERT_ERROR); + }); + }); + + describe('MissingRequiredOptionError', () => { + it('has correct error code', () => { + const error = new MissingRequiredOptionError('Missing host'); + expect(error.code).to.equal(RokuDeployErrorCode.MISSING_REQUIRED_OPTION); + }); + + it('is a ConfigurationError', () => { + const error = new MissingRequiredOptionError('Missing host'); + expect(error instanceof ConfigurationError).to.be.true; + }); + }); + + describe('InvalidOptionError', () => { + it('has correct error code', () => { + const error = new InvalidOptionError('Invalid port'); + expect(error.code).to.equal(RokuDeployErrorCode.INVALID_OPTION); + }); + }); + + describe('UnsupportedFirmwareVersionError', () => { + it('has correct error code', () => { + const error = new UnsupportedFirmwareVersionError('Unsupported version'); + expect(error.code).to.equal(RokuDeployErrorCode.UNSUPPORTED_FIRMWARE); + }); + + it('stores firmware details', () => { + const details: UnsupportedFirmwareDetails = { + currentVersion: '14.0.0', + minimumVersion: '15.0.4', + operation: 'reboot' + }; + const error = new UnsupportedFirmwareVersionError('Unsupported version', details); + expect(error.details).to.deep.equal(details); + }); + }); + }); + + describe('Type Guard Functions', () => { + describe('isRokuDeployError', () => { + it('returns true for RokuDeployError instances', () => { + expect(isRokuDeployError(new InvalidDeviceResponseCodeError('test'))).to.be.true; + expect(isRokuDeployError(new CompileError('test'))).to.be.true; + expect(isRokuDeployError(new MissingRequiredOptionError('test'))).to.be.true; + }); + + it('returns false for regular Error', () => { + expect(isRokuDeployError(new Error('test'))).to.be.false; + }); + + it('returns false for non-errors', () => { + expect(isRokuDeployError(null)).to.be.false; + expect(isRokuDeployError(undefined)).to.be.false; + expect(isRokuDeployError('string')).to.be.false; + expect(isRokuDeployError({})).to.be.false; + }); + }); + + describe('isDeviceError', () => { + it('returns true for DeviceError instances', () => { + expect(isDeviceError(new InvalidDeviceResponseCodeError('test'))).to.be.true; + expect(isDeviceError(new UnauthorizedDeviceResponseError('test'))).to.be.true; + expect(isDeviceError(new FailedDeviceResponseError('test'))).to.be.true; + }); + + it('returns false for non-DeviceError RokuDeployErrors', () => { + expect(isDeviceError(new CompileError('test'))).to.be.false; + expect(isDeviceError(new MissingRequiredOptionError('test'))).to.be.false; + }); + }); + + describe('isConfigurationError', () => { + it('returns true for ConfigurationError instances', () => { + expect(isConfigurationError(new MissingRequiredOptionError('test'))).to.be.true; + expect(isConfigurationError(new InvalidOptionError('test'))).to.be.true; + }); + + it('returns false for non-ConfigurationError RokuDeployErrors', () => { + expect(isConfigurationError(new InvalidDeviceResponseCodeError('test'))).to.be.false; + expect(isConfigurationError(new CompileError('test'))).to.be.false; + }); + }); + + describe('hasErrorCode', () => { + it('returns true when code matches', () => { + const error = new InvalidDeviceResponseCodeError('test'); + expect(hasErrorCode(error, RokuDeployErrorCode.INVALID_RESPONSE_CODE)).to.be.true; + }); + + it('returns false when code does not match', () => { + const error = new InvalidDeviceResponseCodeError('test'); + expect(hasErrorCode(error, RokuDeployErrorCode.UNAUTHORIZED)).to.be.false; + }); + + it('returns false for non-RokuDeployError', () => { + expect(hasErrorCode(new Error('test'), RokuDeployErrorCode.INVALID_RESPONSE_CODE)).to.be.false; + }); + }); + + describe('isUpdateCheckRequiredError', () => { + it('returns true for UpdateCheckRequiredError', () => { + expect(isUpdateCheckRequiredError(new UpdateCheckRequiredError())).to.be.true; + }); + + it('returns false for other errors', () => { + expect(isUpdateCheckRequiredError(new ConnectionResetError())).to.be.false; + expect(isUpdateCheckRequiredError(new Error('test'))).to.be.false; + }); + }); + + describe('isConnectionResetError', () => { + it('returns true for ConnectionResetError', () => { + expect(isConnectionResetError(new ConnectionResetError())).to.be.true; + }); + + it('returns false for other errors', () => { + expect(isConnectionResetError(new UpdateCheckRequiredError())).to.be.false; + expect(isConnectionResetError(new Error('test'))).to.be.false; + }); + }); + + describe('isCompileError', () => { + it('returns true for CompileError', () => { + expect(isCompileError(new CompileError('test'))).to.be.true; + }); + + it('returns false for other errors', () => { + expect(isCompileError(new ConvertError('test'))).to.be.false; + expect(isCompileError(new Error('test'))).to.be.false; + }); + }); + + describe('isUnauthorizedError', () => { + it('returns true for UnauthorizedDeviceResponseError', () => { + expect(isUnauthorizedError(new UnauthorizedDeviceResponseError('test'))).to.be.true; + }); + + it('returns false for other errors', () => { + expect(isUnauthorizedError(new InvalidDeviceResponseCodeError('test'))).to.be.false; + expect(isUnauthorizedError(new Error('test'))).to.be.false; + }); + }); + }); + + describe('extractHttpResponseDetails', () => { + it('extracts details from response object', () => { + const response = { + statusCode: 200, + headers: { 'content-type': 'text/html' }, + request: { + uri: { href: 'http://192.168.1.100/plugin_install' }, + method: 'POST' + } + }; + const body = 'response body'; + + const details = extractHttpResponseDetails(response, body); + + expect(details).to.deep.equal({ + url: 'http://192.168.1.100/plugin_install', + method: 'POST', + statusCode: 200, + headers: { 'content-type': 'text/html' }, + body: body + }); + }); + + it('returns undefined for undefined response', () => { + expect(extractHttpResponseDetails(undefined, 'body')).to.be.undefined; + }); + + it('handles partial response object', () => { + const response = { + statusCode: 500 + }; + const details = extractHttpResponseDetails(response, 'error body'); + + expect(details).to.deep.equal({ + url: undefined, + method: undefined, + statusCode: 500, + headers: undefined, + body: 'error body' + }); + }); + }); + + describe('RokuDeployErrorCode enum', () => { + it('has all expected codes', () => { + expect(RokuDeployErrorCode.INVALID_RESPONSE_CODE).to.equal('INVALID_RESPONSE_CODE'); + expect(RokuDeployErrorCode.UNAUTHORIZED).to.equal('UNAUTHORIZED'); + expect(RokuDeployErrorCode.FAILED_RESPONSE).to.equal('FAILED_RESPONSE'); + expect(RokuDeployErrorCode.UNPARSABLE_RESPONSE).to.equal('UNPARSABLE_RESPONSE'); + expect(RokuDeployErrorCode.UNKNOWN_RESPONSE).to.equal('UNKNOWN_RESPONSE'); + expect(RokuDeployErrorCode.DEVICE_UNREACHABLE).to.equal('DEVICE_UNREACHABLE'); + expect(RokuDeployErrorCode.ECP_DISABLED).to.equal('ECP_DISABLED'); + expect(RokuDeployErrorCode.UPDATE_CHECK_REQUIRED).to.equal('UPDATE_CHECK_REQUIRED'); + expect(RokuDeployErrorCode.CONNECTION_RESET).to.equal('CONNECTION_RESET'); + expect(RokuDeployErrorCode.COMPILE_ERROR).to.equal('COMPILE_ERROR'); + expect(RokuDeployErrorCode.CONVERT_ERROR).to.equal('CONVERT_ERROR'); + expect(RokuDeployErrorCode.MISSING_REQUIRED_OPTION).to.equal('MISSING_REQUIRED_OPTION'); + expect(RokuDeployErrorCode.INVALID_OPTION).to.equal('INVALID_OPTION'); + expect(RokuDeployErrorCode.UNSUPPORTED_FIRMWARE).to.equal('UNSUPPORTED_FIRMWARE'); + }); + }); +}); diff --git a/src/Errors.ts b/src/Errors.ts index 846f9a91..1e3dd10e 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -1,122 +1,377 @@ -import type { HttpResponse, RokuMessages } from './RokuDeploy'; -import type * as requestType from 'request'; +import type { RokuMessages } from './RokuDeploy'; -export class InvalidDeviceResponseCodeError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, InvalidDeviceResponseCodeError.prototype); - } +/** + * Error codes for all RokuDeploy errors. + * These provide programmatic identification of error types. + */ +export enum RokuDeployErrorCode { + INVALID_RESPONSE_CODE = 'INVALID_RESPONSE_CODE', + UNAUTHORIZED = 'UNAUTHORIZED', + FAILED_RESPONSE = 'FAILED_RESPONSE', + UNPARSABLE_RESPONSE = 'UNPARSABLE_RESPONSE', + UNKNOWN_RESPONSE = 'UNKNOWN_RESPONSE', + DEVICE_UNREACHABLE = 'DEVICE_UNREACHABLE', + ECP_DISABLED = 'ECP_DISABLED', + UPDATE_CHECK_REQUIRED = 'UPDATE_CHECK_REQUIRED', + CONNECTION_RESET = 'CONNECTION_RESET', + COMPILE_ERROR = 'COMPILE_ERROR', + CONVERT_ERROR = 'CONVERT_ERROR', + MISSING_REQUIRED_OPTION = 'MISSING_REQUIRED_OPTION', + INVALID_OPTION = 'INVALID_OPTION', + UNSUPPORTED_FIRMWARE = 'UNSUPPORTED_FIRMWARE' } -export class UnauthorizedDeviceResponseError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, UnauthorizedDeviceResponseError.prototype); - } +/** + * Abstracted HTTP response details - NOT tied to request library. + * This allows switching from postman-request to native fetch later. + */ +export interface HttpResponseDetails { + url?: string; + method?: string; + headers?: Record; + statusCode?: number; + body?: string; } -export class EcpNetworkAccessModeDisabledError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, EcpNetworkAccessModeDisabledError.prototype); - } +/** + * Details for device communication errors + */ +export interface DeviceErrorDetails { + httpResponse?: HttpResponseDetails; + rokuMessages?: RokuMessages; + host?: string; } -export class UnparsableDeviceResponseError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, UnparsableDeviceResponseError.prototype); - } +/** + * Details for network/connection errors + */ +export interface ConnectionErrorDetails { + cause?: Error; + host?: string; + url?: string; } -export class FailedDeviceResponseError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, FailedDeviceResponseError.prototype); - } +/** + * Details for configuration errors + */ +export interface ConfigurationErrorDetails { + optionName?: string; + providedValue?: unknown; + expectedFormat?: string; } -export class UnknownDeviceResponseError extends Error { - constructor(message: string, public results?: any) { +/** + * Details for compile errors (same as device errors) + * rokuMessages.errors contains compile error messages + */ +export type CompileErrorDetails = DeviceErrorDetails; + +/** + * Details for convert errors + */ +export interface ConvertErrorDetails { + httpResponse?: HttpResponseDetails; + rokuMessages?: RokuMessages; +} + +/** + * Details for unsupported firmware errors + */ +export interface UnsupportedFirmwareDetails { + currentVersion?: string; + minimumVersion?: string; + operation?: string; +} + +/** + * Base class for all RokuDeploy errors. + * Provides consistent error handling with typed details and serialization support. + */ +export abstract class RokuDeployError extends Error { + /** + * Error code for programmatic identification + */ + public abstract readonly code: RokuDeployErrorCode; + + /** + * Typed details specific to this error type + */ + public readonly details: T; + + /** + * Original error if this error wraps another + */ + public readonly cause?: Error; + + constructor(message: string, details?: T, cause?: Error) { super(message); - Object.setPrototypeOf(this, UnknownDeviceResponseError.prototype); + this.name = this.constructor.name; + this.details = details ?? {} as T; + this.cause = cause; + // Restore prototype chain for proper instanceof checks + Object.setPrototypeOf(this, new.target.prototype); + } + + /** + * Serialize the error for logging/transmission + */ + public toJSON(): Record { + return { + name: this.name, + code: this.code, + message: this.message, + details: this.details, + cause: this.cause ? { + name: this.cause.name, + message: this.cause.message, + stack: this.cause.stack + } : undefined, + stack: this.stack + }; } } -export class DeviceUnreachableError extends Error { - constructor(message: string, public results?: any) { - super(message); - Object.setPrototypeOf(this, DeviceUnreachableError.prototype); +/** + * Intermediate base class for device communication errors. + * These errors occur when communicating with a Roku device. + */ +export abstract class DeviceError extends RokuDeployError { + /** + * Roku messages extracted from the device response + */ + public get rokuMessages(): RokuMessages | undefined { + return this.details?.rokuMessages; + } + + /** + * The host/IP of the device + */ + public get host(): string | undefined { + return this.details?.host; } } -export class CompileError extends Error { - constructor(message: string, public results: any, public rokuMessages: RokuMessages) { - super(message); - Object.setPrototypeOf(this, CompileError.prototype); +/** + * Intermediate base class for configuration errors. + */ +export abstract class ConfigurationError extends RokuDeployError { + /** + * The name of the option that caused the error + */ + public get optionName(): string | undefined { + return this.details?.optionName; } } -export class ConvertError extends Error { - constructor(message: string) { - super(message); - Object.setPrototypeOf(this, ConvertError.prototype); +// ============================================================================ +// Concrete Error Classes +// ============================================================================ + +/** + * Thrown when the device returns an unexpected HTTP status code + */ +export class InvalidDeviceResponseCodeError extends DeviceError { + public readonly code = RokuDeployErrorCode.INVALID_RESPONSE_CODE; +} + +/** + * Thrown when authentication fails (HTTP 401) + */ +export class UnauthorizedDeviceResponseError extends DeviceError { + public readonly code = RokuDeployErrorCode.UNAUTHORIZED; +} + +/** + * Thrown when the device returns an error message in the response body + */ +export class FailedDeviceResponseError extends DeviceError { + public readonly code = RokuDeployErrorCode.FAILED_RESPONSE; +} + +/** + * Thrown when the device response cannot be parsed + */ +export class UnparsableDeviceResponseError extends DeviceError { + public readonly code = RokuDeployErrorCode.UNPARSABLE_RESPONSE; +} + +/** + * Thrown when the device returns an unexpected response that doesn't fit other categories + */ +export class UnknownDeviceResponseError extends DeviceError { + public readonly code = RokuDeployErrorCode.UNKNOWN_RESPONSE; +} + +/** + * Thrown when the device cannot be reached + */ +export class DeviceUnreachableError extends DeviceError { + public readonly code = RokuDeployErrorCode.DEVICE_UNREACHABLE; +} + +/** + * Thrown when ECP (External Control Protocol) is disabled on the device + */ +export class EcpNetworkAccessModeDisabledError extends DeviceError { + public readonly code = RokuDeployErrorCode.ECP_DISABLED; +} + +/** + * Thrown when a Roku device refuses to accept connections because it requires + * the user to check for updates (even if no updates are actually available). + */ +export class UpdateCheckRequiredError extends RokuDeployError { + public readonly code = RokuDeployErrorCode.UPDATE_CHECK_REQUIRED; + + static MESSAGE = `Your device needs to check for updates before accepting connections. Please navigate to System Settings and check for updates and then try again.\n\nhttps://support.roku.com/article/208755668.`; + + constructor(details?: ConnectionErrorDetails, cause?: Error) { + super(UpdateCheckRequiredError.MESSAGE, details, cause); } } -export class MissingRequiredOptionError extends Error { - constructor(message: string) { - super(message); - Object.setPrototypeOf(this, MissingRequiredOptionError.prototype); +/** + * Thrown when a Roku device ends the connection unexpectedly (ECONNRESET). + * Typically this happens when the device needs to check for updates, + * but it can also happen for other reasons. + */ +export class ConnectionResetError extends RokuDeployError { + public readonly code = RokuDeployErrorCode.CONNECTION_RESET; + + static MESSAGE = `The Roku device ended the connection unexpectedly and may need to check for updates before accepting connections. Please navigate to System Settings and check for updates and then try again.\n\nhttps://support.roku.com/article/208755668.`; + + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(details?: ConnectionErrorDetails, cause?: Error) { + super(ConnectionResetError.MESSAGE, details, cause); } } -export class UnsupportedFirmwareVersionError extends Error { - constructor(message: string) { - super(message); - Object.setPrototypeOf(this, UnsupportedFirmwareVersionError.prototype); +/** + * Thrown when compilation fails during sideload + */ +export class CompileError extends RokuDeployError { + public readonly code = RokuDeployErrorCode.COMPILE_ERROR; + + /** + * Roku messages extracted from the device response. + * The `errors` array contains compile error messages. + */ + public get rokuMessages(): RokuMessages | undefined { + return this.details?.rokuMessages; } } /** - * This error is thrown when a Roku device refuses to accept connections because it requires the user to check for updates (even if no updates are actually available). + * Thrown when squashfs conversion fails */ -export class UpdateCheckRequiredError extends Error { +export class ConvertError extends RokuDeployError { + public readonly code = RokuDeployErrorCode.CONVERT_ERROR; +} - static MESSAGE = `Your device needs to check for updates before accepting connections. Please navigate to System Settings and check for updates and then try again.\n\nhttps://support.roku.com/article/208755668.`; +/** + * Thrown when a required option is missing + */ +export class MissingRequiredOptionError extends ConfigurationError { + public readonly code = RokuDeployErrorCode.MISSING_REQUIRED_OPTION; +} - constructor( - public response: HttpResponse, - public requestOptions: requestType.OptionsWithUrl, - public cause?: Error - ) { - super(); - this.message = UpdateCheckRequiredError.MESSAGE; - Object.setPrototypeOf(this, UpdateCheckRequiredError.prototype); - } +/** + * Thrown when an option has an invalid value + */ +export class InvalidOptionError extends ConfigurationError { + public readonly code = RokuDeployErrorCode.INVALID_OPTION; +} + +/** + * Thrown when the device firmware version doesn't support the requested operation + */ +export class UnsupportedFirmwareVersionError extends RokuDeployError { + public readonly code = RokuDeployErrorCode.UNSUPPORTED_FIRMWARE; } -export function isUpdateCheckRequiredError(e: any): e is UpdateCheckRequiredError { - return e?.constructor?.name === 'UpdateCheckRequiredError'; +// ============================================================================ +// Type Guard Functions +// ============================================================================ + +/** + * Check if an error is a RokuDeployError + */ +export function isRokuDeployError(e: unknown): e is RokuDeployError { + return e instanceof RokuDeployError; } /** - * This error is thrown when a Roku device ends the connection unexpectedly, causing an 'ECONNRESET' error. Typically this happens when the device needs to check for updates (even if no updates are available), but it can also happen for other reasons. + * Check if an error is a DeviceError */ -export class ConnectionResetError extends Error { +export function isDeviceError(e: unknown): e is DeviceError { + return e instanceof DeviceError; +} - static MESSAGE = `The Roku device ended the connection unexpectedly and may need to check for updates before accepting connections. Please navigate to System Settings and check for updates and then try again.\n\nhttps://support.roku.com/article/208755668.`; +/** + * Check if an error is a ConfigurationError + */ +export function isConfigurationError(e: unknown): e is ConfigurationError { + return e instanceof ConfigurationError; +} - constructor(error: Error, requestOptions: requestType.OptionsWithUrl) { - super(); - this.message = ConnectionResetError.MESSAGE; - this.cause = error; - Object.setPrototypeOf(this, ConnectionResetError.prototype); - } +/** + * Check if an error has a specific error code + */ +export function hasErrorCode( + e: unknown, + code: T +): e is RokuDeployError & { code: T } { + return isRokuDeployError(e) && e.code === code; +} - public cause?: Error; +/** + * Check if an error is an UpdateCheckRequiredError + */ +export function isUpdateCheckRequiredError(e: unknown): e is UpdateCheckRequiredError { + return e instanceof UpdateCheckRequiredError; } -export function isConnectionResetError(e: any): e is ConnectionResetError { - return e?.constructor?.name === 'ConnectionResetError'; +/** + * Check if an error is a ConnectionResetError + */ +export function isConnectionResetError(e: unknown): e is ConnectionResetError { + return e instanceof ConnectionResetError; +} + +/** + * Check if an error is a CompileError + */ +export function isCompileError(e: unknown): e is CompileError { + return e instanceof CompileError; +} + +/** + * Check if an error is an UnauthorizedDeviceResponseError + */ +export function isUnauthorizedError(e: unknown): e is UnauthorizedDeviceResponseError { + return e instanceof UnauthorizedDeviceResponseError; +} + +// ============================================================================ +// Helper function +// ============================================================================ + +/** + * Extract HttpResponseDetails from a request library response. + * This abstracts the response format so we can switch HTTP libraries later. + */ +export function extractHttpResponseDetails( + response: { statusCode?: number; headers?: Record; request?: { uri?: { href?: string }; method?: string } } | undefined, + body?: string +): HttpResponseDetails | undefined { + if (!response) { + return undefined; + } + return { + url: response.request?.uri?.href, + method: response.request?.method, + statusCode: response.statusCode, + headers: response.headers, + body: body + }; } diff --git a/src/Logger.spec.ts b/src/Logger.spec.ts deleted file mode 100644 index 984b520c..00000000 --- a/src/Logger.spec.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { expect } from 'chai'; -import { Logger, LogLevel, noop } from './Logger'; -import chalk from 'chalk'; -import { createSandbox } from 'sinon'; -const sinon = createSandbox(); - -describe('Logger', () => { - let logger: Logger; - - beforeEach(() => { - logger = new Logger(LogLevel.trace); - sinon.restore(); - //disable chalk colors for testing - sinon.stub(chalk, 'grey').callsFake((arg) => arg as any); - }); - - it('noop does nothing', () => { - noop(); - }); - - it('loglevel setter converts string to enum', () => { - (logger as any).logLevel = 'error'; - expect(logger.logLevel).to.eql(LogLevel.error); - (logger as any).logLevel = 'info'; - expect(logger.logLevel).to.eql(LogLevel.info); - }); - - it('uses LogLevel.log by default', () => { - logger = new Logger(); - expect(logger.logLevel).to.eql(LogLevel.log); - }); - - describe('log methods call correct error type', () => { - it('error', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.error(); - expect(stub.getCalls()[0].args[0]).to.eql(console.error); - }); - - it('warn', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.warn(); - expect(stub.getCalls()[0].args[0]).to.eql(console.warn); - }); - - it('log', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.log(); - expect(stub.getCalls()[0].args[0]).to.eql(console.log); - }); - - it('info', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.info(); - expect(stub.getCalls()[0].args[0]).to.eql(console.info); - }); - - it('debug', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.debug(); - expect(stub.getCalls()[0].args[0]).to.eql(console.debug); - }); - - it('trace', () => { - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.trace(); - expect(stub.getCalls()[0].args[0]).to.eql(console.trace); - }); - }); - - it('skips all errors on error level', () => { - logger.logLevel = LogLevel.off; - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.trace(); - logger.debug(); - logger.info(); - logger.log(); - logger.warn(); - logger.error(); - - expect( - stub.getCalls().map(x => x.args[0]) - ).to.eql([]); - }); - - it('does not skip when log level is high enough', () => { - logger.logLevel = LogLevel.trace; - const stub = sinon.stub(logger as any, 'writeToLog').callsFake(() => { }); - logger.trace(); - logger.debug(); - logger.info(); - logger.log(); - logger.warn(); - logger.error(); - - expect( - stub.getCalls().map(x => x.args[0]) - ).to.eql([ - console.trace, - console.debug, - console.info, - console.log, - console.warn, - console.error - ]); - }); - - describe('time', () => { - beforeEach(() => { - sinon.stub(console, 'log'); - sinon.stub(console, 'info'); - sinon.stub(console, 'trace'); - }); - - it('calls action even if logLevel is wrong', () => { - logger.logLevel = LogLevel.error; - const spy = sinon.spy(); - logger.time(LogLevel.info, null, spy); - expect(spy.called).to.be.true; - }); - - it('runs timer when loglevel is right', () => { - logger.logLevel = LogLevel.log; - const spy = sinon.spy(); - logger.time(LogLevel.log, null, spy); - expect(spy.called).to.be.true; - }); - - it('returns value', () => { - logger.logLevel = LogLevel.log; - const spy = sinon.spy(() => { - return true; - }); - expect( - logger.time(LogLevel.log, null, spy) - ).to.be.true; - expect(spy.called).to.be.true; - }); - - it('gives callable pause and resume functions even when not running timer', () => { - logger.time(LogLevel.info, null, (pause, resume) => { - pause(); - resume(); - }); - }); - - it('waits for and returns a promise when a promise is returned from the action', () => { - expect(logger.time(LogLevel.info, ['message'], () => { - return Promise.resolve(); - })).to.be.instanceof(Promise); - }); - }); -}); diff --git a/src/Logger.ts b/src/Logger.ts deleted file mode 100644 index ba9060b7..00000000 --- a/src/Logger.ts +++ /dev/null @@ -1,154 +0,0 @@ -import chalk from 'chalk'; -import * as moment from 'moment'; -import { Stopwatch } from './Stopwatch'; - -export class Logger { - /** - * A string with whitespace used for indenting all messages - */ - private indent = ''; - - constructor(logLevel?: LogLevel) { - this.logLevel = logLevel; - } - - public get logLevel() { - return this._logLevel; - } - - public set logLevel(value: LogLevel) { - //cast the string version to the numberic version - if (typeof (value) === 'string') { - value = LogLevel[value] as any; - } - this._logLevel = value ?? LogLevel.log; - } - - private _logLevel = LogLevel.log; - - private getTimestamp() { - return '[' + chalk.grey(moment().format(`hh:mm:ss:SSSS A`)) + ']'; - } - - private writeToLog(method: (...consoleArgs: any[]) => void, ...args: any[]) { - if (this._logLevel === LogLevel.trace) { - method = console.trace; - } - let finalArgs = []; - for (let arg of args) { - finalArgs.push(arg); - } - method.call(console, this.getTimestamp(), this.indent, ...finalArgs); - } - - /** - * Log an error message to the console - */ - error(...messages) { - if (this._logLevel >= LogLevel.error) { - this.writeToLog(console.error, ...messages); - } - } - - /** - * Log a warning message to the console - */ - warn(...messages) { - if (this._logLevel >= LogLevel.warn) { - this.writeToLog(console.warn, ...messages); - } - } - - /** - * Log a standard log message to the console - */ - log(...messages) { - if (this._logLevel >= LogLevel.log) { - this.writeToLog(console.log, ...messages); - } - } - - /** - * Log an info message to the console - */ - info(...messages) { - if (this._logLevel >= LogLevel.info) { - this.writeToLog(console.info, ...messages); - } - } - - /** - * Log a debug message to the console - */ - debug(...messages) { - if (this._logLevel >= LogLevel.debug) { - this.writeToLog(console.debug, ...messages); - } - } - - /** - * Log a debug message to the console - */ - trace(...messages) { - if (this._logLevel >= LogLevel.trace) { - this.writeToLog(console.trace, ...messages); - } - } - - /** - * Writes to the log (if logLevel matches), and also times how long the action took to occur. - * `action` is called regardless of logLevel, so this function can be used to nicely wrap - * pieces of functionality. - * The action function also includes two parameters, `pause` and `resume`, which can be used to improve timings by focusing only on - * the actual logic of that action. - */ - time(logLevel: LogLevel, messages: any[], action: (pause: () => void, resume: () => void) => T): T { - //call the log if loglevel is in range - if (this._logLevel >= logLevel) { - let stopwatch = new Stopwatch(); - let logLevelString = LogLevel[logLevel]; - - //write the initial log - this[logLevelString](...messages ?? []); - this.indent += ' '; - - stopwatch.start(); - //execute the action - let result = action(stopwatch.stop.bind(stopwatch), stopwatch.start.bind(stopwatch)) as any; - stopwatch.stop(); - - //return a function to call when the timer is complete - let done = () => { - this.indent = this.indent.substring(2); - this[logLevelString](...messages ?? [], `finished. (${chalk.blue(stopwatch.getDurationText())})`); - }; - - //if this is a promise, wait for it to resolve and then return the original result - if (typeof result?.then === 'function') { - return Promise.resolve(result).then(done).then(() => { - return result; - }) as any; - } else { - //this was not a promise. finish the timer now - done(); - return result; - } - } else { - return action(noop, noop); - } - } -} - -export function noop() { - -} - -export enum LogLevel { - off = 0, - error = 1, - warn = 2, - log = 3, - info = 4, - debug = 5, - trace = 6 -} diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index e60ec40d..473e4554 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -3,22 +3,20 @@ import { expect } from 'chai'; import * as fsExtra from 'fs-extra'; import type { WriteStream, PathLike } from 'fs-extra'; import * as fs from 'fs'; +import { defer, type Deferred } from './util'; import * as path from 'path'; import * as JSZip from 'jszip'; import * as child_process from 'child_process'; import * as glob from 'glob'; -import type { BeforeZipCallbackInfo } from './RokuDeploy'; -import { DefaultFiles, RokuDeploy } from './RokuDeploy'; -import { buildDigestAuthorization, httpClient, parseDigestChallenge } from './fetch'; import * as errors from './Errors'; -import type { Deferred } from './util'; -import { defer, util, standardizePath as s } from './util'; +import { util, standardizePath as s, standardizePathPosix as sp } from './util'; import type { FileEntry, RokuDeployOptions } from './RokuDeployOptions'; import { cwd, expectPathExists, expectPathNotExists, expectThrowsAsync, outDir, rootDir, stagingDir, tempDir, writeFiles } from './testUtils.spec'; import { createSandbox } from 'sinon'; import * as r from 'postman-request'; -import type * as requestType from 'request'; -const request = r as typeof requestType; +import { RokuDeploy } from './RokuDeploy'; +import type { CaptureScreenshotOptions, ConvertToSquashfsOptions, CreateSignedPackageOptions, DeleteDevChannelOptions, GetDevIdOptions, GetDeviceInfoOptions, RekeyDeviceOptions, SendKeyEventOptions, SideloadOptions } from './RokuDeploy'; +const request = r; const sinon = createSandbox(); @@ -27,44 +25,43 @@ describe('RokuDeploy', () => { let options: RokuDeployOptions; let writeStreamPromise: Promise; - let writeStreamDeferred: Deferred; + let writeStreamDeferred: Deferred & { isComplete: true | undefined }; let createWriteStreamStub: sinon.SinonStub; beforeEach(() => { rokuDeploy = new RokuDeploy(); - options = rokuDeploy.getOptions({ + + options = { rootDir: rootDir, - outDir: outDir, - devId: 'abcde', stagingDir: stagingDir, + devId: 'abcde', + out: `${outDir}/roku-deploy.zip`, signingPassword: '12345', host: 'localhost', - rekeySignedPackage: `${tempDir}/testSignedPackage.pkg` - }); - options.rootDir = rootDir; + pkg: `${tempDir}/testSignedPackage.pkg` + } as any; fsExtra.emptyDirSync(tempDir); fsExtra.ensureDirSync(rootDir); fsExtra.ensureDirSync(outDir); fsExtra.ensureDirSync(stagingDir); //most tests depend on a manifest file existing, so write an empty one fsExtra.outputFileSync(`${rootDir}/manifest`, ''); - //create the default rekeySignedPackage so createReadStream doesn't leave an open stream to a missing file - fsExtra.outputFileSync(`${tempDir}/testSignedPackage.pkg`, ''); - writeStreamDeferred = defer(); - writeStreamPromise = writeStreamDeferred.promise; + writeStreamDeferred = defer() as any; + writeStreamPromise = writeStreamDeferred.promise as any; //fake out the write stream function - createWriteStreamStub = sinon.stub(rokuDeploy.fsExtra, 'createWriteStream').callsFake((filePath: PathLike) => { + createWriteStreamStub = sinon.stub(fsExtra, 'createWriteStream').callsFake((filePath: PathLike) => { const writeStream = fs.createWriteStream(filePath); - writeStreamDeferred.tryResolve(writeStream); + writeStreamDeferred.resolve(writeStream); + writeStreamDeferred.isComplete = true; return writeStream; }); }); afterEach(() => { try { - if (createWriteStreamStub.called && !writeStreamDeferred.isCompleted) { + if (createWriteStreamStub.called && !writeStreamDeferred.isComplete) { writeStreamDeferred.reject('Deferred was never resolved...so rejecting in the afterEach'); } @@ -83,34 +80,6 @@ describe('RokuDeploy', () => { fsExtra.removeSync(tempDir); }); - describe('getOutputPkgFilePath', () => { - it('should return correct path if given basename', () => { - options.outFile = 'roku-deploy'; - let outputPath = rokuDeploy.getOutputPkgFilePath(options); - expect(outputPath).to.equal(path.join(path.resolve(options.outDir), options.outFile + '.pkg')); - }); - - it('should return correct path if given outFile option ending in .zip', () => { - options.outFile = 'roku-deploy.zip'; - let outputPath = rokuDeploy.getOutputPkgFilePath(options); - expect(outputPath).to.equal(path.join(path.resolve(options.outDir), 'roku-deploy.pkg')); - }); - }); - - describe('getOutputZipFilePath', () => { - it('should return correct path if given basename', () => { - options.outFile = 'roku-deploy'; - let outputPath = rokuDeploy.getOutputZipFilePath(options); - expect(outputPath).to.equal(path.join(path.resolve(options.outDir), options.outFile + '.zip')); - }); - - it('should return correct path if given outFile option ending in .zip', () => { - options.outFile = 'roku-deploy.zip'; - let outputPath = rokuDeploy.getOutputZipFilePath(options); - expect(outputPath).to.equal(path.join(path.resolve(options.outDir), 'roku-deploy.zip')); - }); - }); - describe('doPostRequest', () => { it('should not throw an error for a successful request', async () => { let body = 'responseBody'; @@ -409,7 +378,7 @@ describe('RokuDeploy', () => { it('should use given port if provided', async () => { const stub = mockDoGetRequest(body); - await rokuDeploy.getDeviceInfo({ host: '1.1.1.1', remotePort: 9999 }); + await rokuDeploy.getDeviceInfo({ host: '1.1.1.1', ecpPort: 9999 }); expect(stub.getCall(0).args[0].url).to.eql('http://1.1.1.1:9999/query/device-info'); }); @@ -420,7 +389,7 @@ describe('RokuDeploy', () => { 29380007-0800-1025-80a4-d83154332d7e `); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); expect(result.isStick).not.to.exist; }); @@ -436,7 +405,7 @@ describe('RokuDeploy', () => { it('should sanitize additional data when the host+param+format signature is triggered', async () => { mockDoGetRequest(body); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); expect(result).to.include({ // make sure the number fields are turned into numbers softwareBuild: 4170, @@ -475,7 +444,7 @@ describe('RokuDeploy', () => { it('converts keys to camel case when enabled', async () => { mockDoGetRequest(body); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); const props = [ 'udn', 'serialNumber', @@ -569,7 +538,10 @@ describe('RokuDeploy', () => { it('handles all error scenarios in catch block', async () => { const doGetRequestStub = sinon.stub(rokuDeploy as any, 'doGetRequest'); - doGetRequestStub.rejects({ results: { response: { headers: { server: 'Roku' } } } }); + // Reject with an error that has details.httpResponse.headers.server = 'Roku' + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: { headers: { server: 'Roku' } } + })); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); @@ -577,52 +549,66 @@ describe('RokuDeploy', () => { expect(e).to.be.instanceof(errors.EcpNetworkAccessModeDisabledError); } - doGetRequestStub.rejects({ results: { response: { headers: { server: 'Apache' } } } }); + // Reject with an error that has details.httpResponse.headers.server = 'Apache' + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: { headers: { server: 'Apache' } } + })); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results.response.headers.server).to.equal('Apache'); + expect((e as errors.InvalidDeviceResponseCodeError).details.httpResponse?.headers?.server).to.equal('Apache'); } - doGetRequestStub.rejects({ results: { response: { headers: {} } } }); + // Reject with an error that has no server header + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: { headers: {} } + })); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results.response.headers.server).to.be.undefined; + expect((e as errors.InvalidDeviceResponseCodeError).details.httpResponse?.headers?.server).to.be.undefined; } - doGetRequestStub.rejects({ results: { response: { headers: { server: null } } } }); + // Reject with an error that has server: null + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: { headers: { server: null as any } } + })); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results.response.headers.server).to.be.null; + expect((e as errors.InvalidDeviceResponseCodeError).details.httpResponse?.headers?.server).to.be.null; } - doGetRequestStub.rejects({ results: { response: {} } }); + // Reject with an error that has no headers + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: {} + })); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results.response.headers).to.be.undefined; + expect((e as errors.InvalidDeviceResponseCodeError).details.httpResponse?.headers).to.be.undefined; } - doGetRequestStub.rejects({ results: {} }); + // Reject with an error that has no httpResponse + doGetRequestStub.rejects(new errors.InvalidDeviceResponseCodeError('test', {})); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results.response).to.be.undefined; + expect((e as errors.InvalidDeviceResponseCodeError).details.httpResponse).to.be.undefined; } + // Reject with an empty object (not a proper error) doGetRequestStub.rejects({}); try { await rokuDeploy.getDeviceInfo({ host: '1.1.1.1' }); assert.fail('Exception should have been thrown'); } catch (e) { - expect((e as any).results).to.be.undefined; + expect((e as any).details).to.be.undefined; } const err = new Error('Network error'); @@ -647,14 +633,17 @@ describe('RokuDeploy', () => { describe('getEcpNetworkAccessMode', () => { it('returns ecpSettingMode from device info', async () => { - sinon.stub(rokuDeploy, 'getDeviceInfo').resolves({ ecpSettingMode: 'enabled' }); + sinon.stub(rokuDeploy, 'getDeviceInfo').resolves({ 'ecp-setting-mode': 'enabled' } as any); const result = await rokuDeploy.getEcpNetworkAccessMode({ host: '1.1.1.1' }); expect(result).to.equal('enabled'); }); it(`returns 'disabled' when response header had Roku in it`, async () => { const getDeviceInfoStub = sinon.stub(rokuDeploy, 'getDeviceInfo'); - getDeviceInfoStub.rejects({ results: { response: { headers: { server: 'Roku' } } } }); + // Reject with an error that has details.httpResponse.headers.server = 'Roku' + getDeviceInfoStub.rejects(new errors.InvalidDeviceResponseCodeError('test', { + httpResponse: { headers: { server: 'Roku' } } + })); expect(await rokuDeploy.getEcpNetworkAccessMode({ host: '1.1.1.1' })).to.equal('disabled'); }); @@ -670,11 +659,12 @@ describe('RokuDeploy', () => { } } - await doTest({ results: { response: { headers: { server: 'Apache' } } } }); - await doTest({ results: { response: { headers: {} } } }); - await doTest({ results: { response: { headers: { server: null } } } }); - await doTest({ results: { response: {} } }); - await doTest({ results: {} }); + // Test with various errors that don't have Roku in the server header - should throw UnknownDeviceResponseError + await doTest(new errors.InvalidDeviceResponseCodeError('test', { httpResponse: { headers: { server: 'Apache' } } })); + await doTest(new errors.InvalidDeviceResponseCodeError('test', { httpResponse: { headers: {} } })); + await doTest(new errors.InvalidDeviceResponseCodeError('test', { httpResponse: { headers: { server: null as any } } })); + await doTest(new errors.InvalidDeviceResponseCodeError('test', { httpResponse: {} })); + await doTest(new errors.InvalidDeviceResponseCodeError('test', {})); await doTest({}); await doTest(new Error('Network error')); }); @@ -693,24 +683,24 @@ describe('RokuDeploy', () => { describe('normalizeDeviceInfoFieldValue', () => { it('converts normal values', () => { - expect(rokuDeploy.normalizeDeviceInfoFieldValue('true')).to.eql(true); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('false')).to.eql(false); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('1')).to.eql(1); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('1.2')).to.eql(1.2); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('true')).to.eql(true); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('false')).to.eql(false); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('1')).to.eql(1); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('1.2')).to.eql(1.2); //it'll trim whitespace too - expect(rokuDeploy.normalizeDeviceInfoFieldValue(' 1.2')).to.eql(1.2); - expect(rokuDeploy.normalizeDeviceInfoFieldValue(' 1.2 ')).to.eql(1.2); + expect(rokuDeploy['normalizeDeviceInfoFieldValue'](' 1.2')).to.eql(1.2); + expect(rokuDeploy['normalizeDeviceInfoFieldValue'](' 1.2 ')).to.eql(1.2); }); it('leaves invalid numbers as strings', () => { - expect(rokuDeploy.normalizeDeviceInfoFieldValue('v1.2.3')).to.eql('v1.2.3'); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('1.2.3-alpha.1')).to.eql('1.2.3-alpha.1'); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('123Four')).to.eql('123Four'); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('v1.2.3')).to.eql('v1.2.3'); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('1.2.3-alpha.1')).to.eql('1.2.3-alpha.1'); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('123Four')).to.eql('123Four'); }); it('decodes HTML entities', () => { - expect(rokuDeploy.normalizeDeviceInfoFieldValue('3&4')).to.eql('3&4'); - expect(rokuDeploy.normalizeDeviceInfoFieldValue('3&4')).to.eql('3&4'); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('3&4')).to.eql('3&4'); + expect(rokuDeploy['normalizeDeviceInfoFieldValue']('3&4')).to.eql('3&4'); }); }); @@ -722,100 +712,22 @@ describe('RokuDeploy', () => { ${expectedDevId} `; mockDoGetRequest(body); - options.devId = expectedDevId; - let devId = await rokuDeploy.getDevId(options); - expect(devId).to.equal(expectedDevId); - }); - }); - - describe('copyToStaging', () => { - it('throws exceptions on missing stagingPath', async () => { - await expectThrowsAsync( - rokuDeploy['copyToStaging']([], undefined) - ); - }); - - it('computes absolute path for all operations', async () => { - const ensureDirPaths = []; - sinon.stub(rokuDeploy.fsExtra, 'ensureDir').callsFake((p) => { - ensureDirPaths.push(p); - return Promise.resolve; - }); - const copyPaths = [] as Array<{ src: string; dest: string }>; - sinon.stub(rokuDeploy.fsExtra as any, 'copy').callsFake((src, dest) => { - copyPaths.push({ src: src as string, dest: dest as string }); - return Promise.resolve(); - }); - - await rokuDeploy['copyToStaging']([ - { - src: s`${rootDir}/source/main.brs`, - dest: 'source/main.brs' - }, { - src: s`${rootDir}/components/a/b/c/comp1.xml`, - dest: 'components/a/b/c/comp1.xml' - } - ], stagingDir); - - expect(ensureDirPaths).to.eql([ - s`${stagingDir}/source`, - s`${stagingDir}/components/a/b/c` - ]); - - expect(copyPaths).to.eql([ - { - src: s`${rootDir}/source/main.brs`, - dest: s`${stagingDir}/source/main.brs` - }, { - src: s`${rootDir}/components/a/b/c/comp1.xml`, - dest: s`${stagingDir}/components/a/b/c/comp1.xml` - } - ]); - }); - - it('does not double-up the path when dest is absolute', async () => { - const copyPaths = [] as Array<{ src: string; dest: string }>; - sinon.stub(rokuDeploy.fsExtra, 'ensureDir').returns(Promise.resolve() as any); - sinon.stub(rokuDeploy.fsExtra as any, 'copy').callsFake((src, dest) => { - copyPaths.push({ src: src as string, dest: dest as string }); - return Promise.resolve(); - }); - - const absoluteDest = s`${stagingDir}/source/main.brs`; - await rokuDeploy['copyToStaging']([{ - src: s`${rootDir}/source/main.brs`, - dest: absoluteDest - }], stagingDir); - - // path.resolve(stagingDir, absoluteDest) returns absoluteDest unchanged, - // whereas the old `${stagingDir}/${absoluteDest}` would produce a doubled path - expect(copyPaths[0].dest).to.equal(absoluteDest); - }); - - it('copies to stagingDir root when dest is undefined', async () => { - const copyPaths = [] as Array<{ src: string; dest: string }>; - sinon.stub(rokuDeploy.fsExtra, 'ensureDir').returns(Promise.resolve() as any); - sinon.stub(rokuDeploy.fsExtra as any, 'copy').callsFake((src, dest) => { - copyPaths.push({ src: src as string, dest: dest as string }); - return Promise.resolve(); + let devId = await rokuDeploy.getDevId({ + host: '1.2.3.4' }); - - await rokuDeploy['copyToStaging']([{ - src: s`${rootDir}/source/main.brs`, - dest: undefined - }], stagingDir); - - expect(copyPaths[0].dest).to.equal(s`${stagingDir}`); + expect(devId).to.equal(expectedDevId); }); }); - describe('zipPackage', () => { + describe('zip', () => { it('should throw error when manifest is missing', async () => { let err; try { - options.stagingDir = s`${tempDir}/path/to/nowhere`; fsExtra.ensureDirSync(options.stagingDir); - await rokuDeploy.zipPackage(options); + await rokuDeploy.zip({ + dir: s`${tempDir}/path/to/nowhere`, + out: `${outDir}/roku-deploy.zip` + }); } catch (e) { err = (e as Error); } @@ -825,8 +737,10 @@ describe('RokuDeploy', () => { it('should throw error when manifest is missing and stagingDir does not exist', async () => { let err; try { - options.stagingDir = s`${tempDir}/path/to/nowhere`; - await rokuDeploy.zipPackage(options); + await rokuDeploy.zip({ + dir: s`${tempDir}/path/to/nowhere`, + out: `${outDir}/roku-deploy.zip` + }); } catch (e) { err = (e as Error); } @@ -834,148 +748,60 @@ describe('RokuDeploy', () => { expect(err.message.startsWith('Cannot zip'), `Unexpected error message: "${err.message}"`).to.be.true; }); - }); - - describe('createPackage', () => { - it('works with custom stagingDir', async () => { - let opts = { - ...options, - files: [ - 'manifest' - ], - stagingDir: '.tmp/dist' - }; - await rokuDeploy.createPackage(opts); - expectPathExists(rokuDeploy.getOutputZipFilePath(opts)); - }); - - it('should throw error when no files were found to copy', async () => { - await assertThrowsAsync(async () => { - options.files = []; - await rokuDeploy.createPackage(options); - }); - }); + it('should zip only files matching the files array filter', async () => { + fsExtra.outputFileSync(s`${rootDir}/manifest`, 'title=Test'); + fsExtra.outputFileSync(s`${rootDir}/source/main.brs`, 'sub main()\nend sub'); + fsExtra.outputFileSync(s`${rootDir}/components/comp.xml`, ''); + fsExtra.outputFileSync(s`${rootDir}/extra/stuff.txt`, 'should not be included'); - it('should create package in proper directory', async () => { - await rokuDeploy.createPackage({ - ...options, - files: [ - 'manifest' - ] + await rokuDeploy.zip({ + dir: rootDir, + files: ['manifest', 'source/**/*'], + out: `${outDir}/roku-deploy.zip` }); - expectPathExists(rokuDeploy.getOutputZipFilePath(options)); - }); - - it('should only include the specified files', async () => { - const files = ['manifest']; - options.files = files; - await rokuDeploy.createPackage(options); - const data = fsExtra.readFileSync(rokuDeploy.getOutputZipFilePath(options)); - const zip = await JSZip.loadAsync(data as any); - for (const file of files) { - const zipFileContents = await zip.file(file.toString()).async('string'); - const sourcePath = path.join(options.rootDir, file); - const incomingContents = fsExtra.readFileSync(sourcePath, 'utf8'); - expect(zipFileContents).to.equal(incomingContents); - } + const zip = new JSZip(); + const zipContents = await zip.loadAsync(fsExtra.readFileSync(`${outDir}/roku-deploy.zip`)); + expect(zipContents.files['manifest']).to.exist; + expect(zipContents.files['source/main.brs']).to.exist; + expect(zipContents.files['components/comp.xml']).to.not.exist; + expect(zipContents.files['extra/stuff.txt']).to.not.exist; }); - it('generates full package with defaults', async () => { - const filePaths = writeFiles(rootDir, [ - 'components/components/Loader/Loader.brs', - 'images/splash_hd.jpg', - 'source/main.brs', - 'manifest' - ]); - await rokuDeploy.createPackage({ - ...options, - //target a subset of the files to make the test faster - files: filePaths - }); - - const data = fsExtra.readFileSync(rokuDeploy.getOutputZipFilePath(options)); - const zip = await JSZip.loadAsync(data as any); + it('should throw error when files filter excludes manifest', async () => { + fsExtra.outputFileSync(s`${rootDir}/manifest`, 'title=Test'); + fsExtra.outputFileSync(s`${rootDir}/source/main.brs`, 'sub main()\nend sub'); - for (const file of filePaths) { - const zipFileContents = await zip.file(file.toString())?.async('string'); - const sourcePath = path.join(options.rootDir, file); - const incomingContents = fsExtra.readFileSync(sourcePath, 'utf8'); - expect(zipFileContents).to.equal(incomingContents); + let err: Error | undefined; + try { + await rokuDeploy.zip({ + dir: rootDir, + files: ['source/**/*'], + out: `${outDir}/roku-deploy.zip` + }); + } catch (e) { + err = e as Error; } + expect(err?.message).to.include('missing manifest'); }); - it('should retain the staging directory when told to', async () => { - let stagingDirValue = await rokuDeploy.prepublishToStaging({ - ...options, - files: [ - 'manifest' - ] - }); - expectPathExists(stagingDirValue); - options.retainStagingDir = true; - await rokuDeploy.zipPackage(options); - expectPathExists(stagingDirValue); - }); - - it('should call our callback with correct information', async () => { - fsExtra.outputFileSync(`${rootDir}/manifest`, 'major_version=1'); - - let spy = sinon.spy((info: BeforeZipCallbackInfo) => { - expectPathExists(info.stagingDir); - expect(info.manifestData.major_version).to.equal('1'); - }); - - await rokuDeploy.createPackage(options, spy); - - if (spy.notCalled) { - assert.fail('Callback not called'); - } - }); + it('should zip all files when files array is not provided', async () => { + fsExtra.outputFileSync(s`${rootDir}/manifest`, 'title=Test'); + fsExtra.outputFileSync(s`${rootDir}/source/main.brs`, 'sub main()\nend sub'); + fsExtra.outputFileSync(s`${rootDir}/components/comp.xml`, ''); - it('should wait for promise returned by pre-zip callback', async () => { - fsExtra.outputFileSync(`${rootDir}/manifest`, ''); - let count = 0; - await rokuDeploy.createPackage({ - ...options, - files: ['manifest'] - }, (info) => { - return Promise.resolve().then(() => { - count++; - }).then(() => { - count++; - }); + await rokuDeploy.zip({ + dir: rootDir, + out: `${outDir}/roku-deploy.zip` }); - expect(count).to.equal(2); - }); - it('should increment the build number if requested', async () => { - fsExtra.outputFileSync(`${rootDir}/manifest`, `build_version=0`); - options.incrementBuildNumber = true; - //make the zipping immediately resolve - sinon.stub(rokuDeploy, 'zipPackage').returns(Promise.resolve()); - let beforeZipInfo: BeforeZipCallbackInfo; - await rokuDeploy.createPackage({ - ...options, - files: ['manifest'] - }, (info) => { - beforeZipInfo = info; - }); - expect(beforeZipInfo.manifestData.build_version).to.not.equal('0'); + const zip = new JSZip(); + const zipContents = await zip.loadAsync(fsExtra.readFileSync(`${outDir}/roku-deploy.zip`)); + expect(zipContents.files['manifest']).to.exist; + expect(zipContents.files['source/main.brs']).to.exist; + expect(zipContents.files['components/comp.xml']).to.exist; }); - it('should not increment the build number if not requested', async () => { - fsExtra.outputFileSync(`${rootDir}/manifest`, `build_version=0`); - options.incrementBuildNumber = false; - await rokuDeploy.createPackage({ - ...options, - files: [ - 'manifest' - ] - }, (info) => { - expect(info.manifestData.build_version).to.equal('0'); - }); - }); }); it('runs via the command line using the rokudeploy.json file', function test() { @@ -987,11 +813,11 @@ describe('RokuDeploy', () => { describe('generateBaseRequestOptions', () => { it('uses default port', () => { - expect(rokuDeploy['generateBaseRequestOptions']('a_b_c', { host: '1.2.3.4' }).url).to.equal('http://1.2.3.4:80/a_b_c'); + expect(rokuDeploy['generateBaseRequestOptions']('a_b_c', { host: '1.2.3.4', password: 'password' }).url).to.equal('http://1.2.3.4:80/a_b_c'); }); it('uses overridden port', () => { - expect(rokuDeploy['generateBaseRequestOptions']('a_b_c', { host: '1.2.3.4', packagePort: 999 }).url).to.equal('http://1.2.3.4:999/a_b_c'); + expect(rokuDeploy['generateBaseRequestOptions']('a_b_c', { host: '1.2.3.4', packagePort: 999, password: 'password' }).url).to.equal('http://1.2.3.4:999/a_b_c'); }); }); @@ -1002,7 +828,7 @@ describe('RokuDeploy', () => { process.nextTick(callback, new Error()); return {} as any; }); - return rokuDeploy.pressHomeButton({}).then(() => { + return rokuDeploy.keyPress({ ...options, host: '1.2.3.4', key: 'home' }).then(() => { assert.fail('Should have rejected the promise'); }, () => { expect(true).to.be.true; @@ -1012,34 +838,34 @@ describe('RokuDeploy', () => { it('uses default port', async () => { const promise = new Promise((resolve) => { sinon.stub(rokuDeploy, 'doPostRequest').callsFake((opts: any) => { - expect(opts.url).to.equal('http://1.2.3.4:8060/keypress/Home'); + expect(opts.url).to.equal('http://1.2.3.4:8060/keypress/home'); resolve(); }); }); - await rokuDeploy.pressHomeButton('1.2.3.4'); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', key: 'home' }); await promise; }); it('uses overridden port', async () => { const promise = new Promise((resolve) => { sinon.stub(rokuDeploy, 'doPostRequest').callsFake((opts: any) => { - expect(opts.url).to.equal('http://1.2.3.4:987/keypress/Home'); + expect(opts.url).to.equal('http://1.2.3.4:987/keypress/home'); resolve(); }); }); - await rokuDeploy.pressHomeButton('1.2.3.4', 987); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', ecpPort: 987, key: 'home' }); await promise; }); it('uses default timeout', async () => { const promise = new Promise((resolve) => { sinon.stub(rokuDeploy, 'doPostRequest').callsFake((opts: any) => { - expect(opts.url).to.equal('http://1.2.3.4:8060/keypress/Home'); + expect(opts.url).to.equal('http://1.2.3.4:8060/keypress/home'); expect(opts.timeout).to.equal(150000); resolve(); }); }); - await rokuDeploy.pressHomeButton('1.2.3.4'); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', key: 'home' }); await promise; }); @@ -1047,44 +873,49 @@ describe('RokuDeploy', () => { const promise = new Promise((resolve) => { sinon.stub(rokuDeploy, 'doPostRequest').callsFake((opts: any) => { - expect(opts.url).to.equal('http://1.2.3.4:987/keypress/Home'); + expect(opts.url).to.equal('http://1.2.3.4:987/keypress/home'); expect(opts.timeout).to.equal(1000); resolve(); }); }); - await rokuDeploy.pressHomeButton('1.2.3.4', 987, 1000); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', ecpPort: 987, key: 'home', timeout: 1000 }); await promise; }); }); let fileCounter = 1; - describe('publish', () => { + let zipFile: string; + describe('sideload', () => { beforeEach(() => { - options.host = '0.0.0.0'; - //make a dummy output file...we don't care what's in it - options.outFile = `temp${fileCounter++}.zip`; + zipFile = `${outDir}/temp${fileCounter++}.zip`; try { - fsExtra.outputFileSync(`${options.outDir}/${options.outFile}`, 'asdf'); + fsExtra.outputFileSync(zipFile, 'asdf'); } catch (e) { } }); it('uses overridden route', async () => { const stub = mockDoPostRequest(); - await rokuDeploy.publish({ - ...options, + await rokuDeploy.sideload({ + host: '0.0.0.0', + password: 'password', + zip: zipFile, + close: false, packageUploadOverrides: { route: 'alt_path' } }); - expect(stub.getCall(0).args[0].url).to.eql('http://0.0.0.0:80/alt_path'); + expect(stub.getCall(1).args[0].url).to.eql('http://0.0.0.0:80/alt_path'); }); it('overrides formData', async () => { const stub = mockDoPostRequest(); - await rokuDeploy.publish({ - ...options, + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, remoteDebug: true, + close: false, packageUploadOverrides: { formData: { remotedebug: null, @@ -1092,41 +923,46 @@ describe('RokuDeploy', () => { } } }); - expect(stub.getCall(0).args[0].formData).to.include({ + expect(stub.getCall(1).args[0].formData).to.include({ newfield: 'here' }).and.to.not.haveOwnProperty('remotedebug'); }); - it('does not delete the archive by default', async () => { - let zipPath = `${options.outDir}/${options.outFile}`; - + it('does not delete the generated archive by default', async () => { mockDoPostRequest(); //the file should exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.true; - await rokuDeploy.publish(options); - //the file should still exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.true; + expect(fsExtra.pathExistsSync(zipFile)).to.be.true; + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + close: false + }); + //the file should still exist (pre-built zips are retained by default) + expect(fsExtra.pathExistsSync(zipFile)).to.be.true; }); - it('deletes the archive when configured', async () => { - let zipPath = `${options.outDir}/${options.outFile}`; - + it('deletes the generated archive by default when using dir', async () => { mockDoPostRequest(); - //the file should exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.true; - await rokuDeploy.publish({ ...options, retainDeploymentArchive: false }); - //the file should not exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.false; - //the out folder should also be deleted since it's empty + //the file should exist (created in beforeEach) + expect(fsExtra.pathExistsSync(zipFile)).to.be.true; + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + dir: rootDir, + close: false + }); + //the generated archive should be deleted by default + expect(fsExtra.pathExistsSync(s`${outDir}/roku-deploy.zip`)).to.be.false; }); it('failure to close read stream does not crash', async () => { - const orig = rokuDeploy.fsExtra.createReadStream; + const orig = fsExtra.createReadStream; //wrap the stream.close call so we can throw - sinon.stub(rokuDeploy.fsExtra, 'createReadStream').callsFake((pathLike) => { - const stream = orig.call(rokuDeploy.fsExtra, pathLike); + sinon.stub(fsExtra, 'createReadStream').callsFake((pathLike) => { + const stream = orig.call(fsExtra, pathLike); const originalClose = stream.close; stream.close = () => { originalClose.call(stream); @@ -1135,28 +971,40 @@ describe('RokuDeploy', () => { return stream; }); - let zipPath = `${options.outDir}/${options.outFile}`; - mockDoPostRequest(); //the file should exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.true; - await rokuDeploy.publish({ ...options, retainDeploymentArchive: false }); - //the file should not exist - expect(fsExtra.pathExistsSync(zipPath)).to.be.false; - //the out folder should also be deleted since it's empty + expect(fsExtra.pathExistsSync(zipFile)).to.be.true; + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + dir: rootDir, + close: false + }); + //the file should not exist (dir generates a temp zip that gets deleted) + expect(fsExtra.pathExistsSync(s`${outDir}/roku-deploy.zip`)).to.be.false; }); it('fails when the zip file is missing', async () => { - options.outFile = 'fileThatDoesNotExist.zip'; + const missingZip = s`${outDir}/fileThatDoesNotExist.zip`; await expectThrowsAsync(async () => { - await rokuDeploy.publish(options); - }, `Cannot publish because file does not exist at '${rokuDeploy.getOutputZipFilePath(options)}'`); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: missingZip, + deleteDevChannel: false, + close: false + }); + }, `Cannot sideload because file does not exist at '${missingZip}'`); }); it('fails when no host is provided', () => { expectPathNotExists('rokudeploy.json'); - return rokuDeploy.publish({ host: undefined }).then(() => { + return rokuDeploy.sideload({ + host: undefined, + password: 'password', + zip: zipFile + }).then(() => { assert.fail('Should not have succeeded'); }, () => { expect(true).to.be.true; @@ -1166,7 +1014,7 @@ describe('RokuDeploy', () => { it('throws when package upload fails', async () => { //intercept the post requests sinon.stub(request, 'post').callsFake((data: any, callback: any) => { - if (data.url === `http://${options.host}/plugin_install`) { + if (data.url === `http://1.2.3.4/plugin_install`) { process.nextTick(() => { callback(new Error('Failed to publish to server')); }); @@ -1177,7 +1025,12 @@ describe('RokuDeploy', () => { }); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + close: false + }); } catch (e) { assert.ok('Exception was thrown as expected'); return; @@ -1186,13 +1039,18 @@ describe('RokuDeploy', () => { }); it('rejects as CompileError when initial replace fails', () => { - options.failOnCompileError = true; mockDoPostRequest(` Install Failure: Compilation Failed. Shell.create('Roku.Message').trigger('Set message type', 'error').trigger('Set message content', 'Install Failure: Compilation Failed').trigger('Render', node); `); - return rokuDeploy.publish(options).then(() => { + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + close: false + }).then(() => { assert.fail('Should not have succeeded due to roku server compilation failure'); }, (err) => { expect(err).to.be.instanceOf(errors.CompileError); @@ -1200,13 +1058,18 @@ describe('RokuDeploy', () => { }); it('rejects as CompileError when initial replace fails', () => { - options.failOnCompileError = true; mockDoPostRequest(` Install Failure: Compilation Failed. Shell.create('Roku.Message').trigger('Set message type', 'error').trigger('Set message content', 'Install Failure: Compilation Failed').trigger('Render', node); `); - return rokuDeploy.publish(options).then(() => { + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + close: false + }).then(() => { assert.fail('Should not have succeeded due to roku server compilation failure'); }, (err) => { expect(err).to.be.instanceOf(errors.CompileError); @@ -1214,11 +1077,16 @@ describe('RokuDeploy', () => { }); it('rejects when response contains compile error wording', () => { - options.failOnCompileError = true; let body = 'Install Failure: Compilation Failed.'; mockDoPostRequest(body); - return rokuDeploy.publish(options).then(() => { + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + close: false + }).then(() => { assert.fail('Should not have succeeded due to roku server compilation failure'); }, (err) => { expect(err.message).to.equal('Compile error'); @@ -1242,10 +1110,15 @@ describe('RokuDeploy', () => { }); it('rejects when response contains invalid password status code', () => { - options.failOnCompileError = true; mockDoPostRequest('', 401); - return rokuDeploy.publish(options).then(() => { + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + close: false + }).then(() => { assert.fail('Should not have succeeded due to roku server compilation failure'); }, (err) => { expect(err.message).to.be.a('string').and.satisfy(msg => msg.startsWith('Unauthorized. Please verify credentials for host')); @@ -1258,7 +1131,14 @@ describe('RokuDeploy', () => { mockDoPostRequest(`'Failed to check for software update'`, 200); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload( + { + host: '1.2.3.4', + password: 'password', + zip: zipFile, + close: false + } + ); assert.fail('Should not have succeeded due to roku server compilation failure'); } catch (err) { expect((err as any).message).to.eql( @@ -1281,7 +1161,15 @@ describe('RokuDeploy', () => { }); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload( + { + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false + } + ); assert.fail('Should not have succeeded due to roku server compilation failure'); } catch (err) { expect(spy.callCount).to.eql(1); @@ -1298,14 +1186,22 @@ describe('RokuDeploy', () => { if (params?.formData['mysubmit'] === 'Replace') { results = { response: { statusCode: 500 }, body: `` }; } else { - results = { response: { statusCode: 500 }, body: `'Failed to check for software update'` }; + results = { response: { statusCode: 200 }, body: `'Failed to check for software update'` }; } rokuDeploy['checkRequest'](results); return Promise.resolve(results); }); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload( + { + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false + } + ); assert.fail('Should not have succeeded due to roku server compilation failure'); } catch (err) { expect(spy.callCount).to.eql(2); @@ -1316,23 +1212,34 @@ describe('RokuDeploy', () => { }); it('handles successful deploy', () => { - options.failOnCompileError = true; mockDoPostRequest(); - return rokuDeploy.publish(options).then((result) => { - expect(result.message).to.equal('Successful deploy'); + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + close: false + }).then((result) => { + expect(result.message).to.equal('Successful sideload'); }, () => { assert.fail('Should not have rejected the promise'); }); }); it('handles successful deploy with remoteDebug', () => { - options.failOnCompileError = true; - options.remoteDebug = true; const stub = mockDoPostRequest(); - return rokuDeploy.publish(options).then((result) => { - expect(result.message).to.equal('Successful deploy'); + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + deleteDevChannel: false, + close: false + }).then((result) => { + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.remotedebug).to.eql('1'); }, () => { assert.fail('Should not have rejected the promise'); @@ -1340,83 +1247,173 @@ describe('RokuDeploy', () => { }); it('handles successful deploy with remotedebug_connect_early', () => { - options.failOnCompileError = true; - options.remoteDebug = true; - options.remoteDebugConnectEarly = true; const stub = mockDoPostRequest(); - return rokuDeploy.publish(options).then((result) => { - expect(result.message).to.equal('Successful deploy'); + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + remoteDebugConnectEarly: true, + deleteDevChannel: false, + close: false + }).then((result) => { + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.remotedebug_connect_early).to.eql('1'); }, () => { assert.fail('Should not have rejected the promise'); }); }); - it('sets dev_autolaunch to 0 when autoLaunch is false', async () => { - options.autoLaunch = false; + it('does not set appType if not explicitly defined', async () => { + delete options.appType; + const stub = mockDoPostRequest(); + + fsExtra.outputFileSync(zipFile, 'asdf'); + + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false + }); + expect(result.message).to.equal('Successful sideload'); + expect(stub.getCall(0).args[0].formData.app_type).to.be.undefined; + }); + + it('does not set appType if not appType is set to null or undefined', async () => { const stub = mockDoPostRequest(); + fsExtra.outputFileSync(zipFile, 'asdf'); - const result = await rokuDeploy.publish(options); - expect(result.message).to.equal('Successful deploy'); - expect(stub.getCall(0).args[0].formData.dev_autolaunch).to.eql('0'); + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false, + appType: null + }); + expect(result.message).to.equal('Successful sideload'); + expect(stub.getCall(0).args[0].formData.app_type).to.be.undefined; }); - it('omits dev_autolaunch when autoLaunch is not false', async () => { + it('sets appType="channel" when defined', async () => { const stub = mockDoPostRequest(); + fsExtra.outputFileSync(zipFile, 'asdf'); + + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false, + appType: 'channel' + }); + expect(result.message).to.equal('Successful sideload'); + expect(stub.getCall(0).args[0].formData.app_type).to.eql('channel'); + }); - delete options.autoLaunch; - await rokuDeploy.publish(options); - expect(stub.getCall(0).args[0].formData).to.not.haveOwnProperty('dev_autolaunch'); + it('sets appType="dcl" when defined', async () => { + const stub = mockDoPostRequest(); + fsExtra.outputFileSync(zipFile, 'asdf'); - options.autoLaunch = true; - await rokuDeploy.publish(options); - expect(stub.getCall(1).args[0].formData).to.not.haveOwnProperty('dev_autolaunch'); + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false, + appType: 'dcl' + }); + expect(result.message).to.equal('Successful sideload'); + expect(stub.getCall(0).args[0].formData.app_type).to.eql('dcl'); }); - it('does not set appType if not explicitly defined', async () => { - delete options.appType; + it('Does not reject when response contains compile error wording but config is set to ignore compile warnings', async () => { const stub = mockDoPostRequest(); + options.failOnCompileError = false; - const result = await rokuDeploy.publish(options); - expect(result.message).to.equal('Successful deploy'); + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + remoteDebugConnectEarly: true, + deleteDevChannel: false, + close: false + }); + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.app_type).to.be.undefined; }); it('does not set appType if not appType is set to null or undefined', async () => { - options.appType = null; const stub = mockDoPostRequest(); - const result = await rokuDeploy.publish(options); - expect(result.message).to.equal('Successful deploy'); + const result = await rokuDeploy.sideload({ + appType: null, + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + remoteDebugConnectEarly: true, + deleteDevChannel: false, + close: false + }); + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.app_type).to.be.undefined; }); it('sets appType="channel" when defined', async () => { - options.appType = 'channel'; const stub = mockDoPostRequest(); - const result = await rokuDeploy.publish(options); - expect(result.message).to.equal('Successful deploy'); + const result = await rokuDeploy.sideload({ + appType: 'channel', + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + remoteDebugConnectEarly: true, + deleteDevChannel: false, + close: false + }); + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.app_type).to.eql('channel'); }); - it('sets appType="channel" when defined', async () => { - options.appType = 'dcl'; + it('sets appType="dcl" when defined', async () => { const stub = mockDoPostRequest(); - const result = await rokuDeploy.publish(options); - expect(result.message).to.equal('Successful deploy'); + const result = await rokuDeploy.sideload({ + appType: 'dcl', + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: true, + remoteDebug: true, + remoteDebugConnectEarly: true, + deleteDevChannel: false, + close: false + }); + expect(result.message).to.equal('Successful sideload'); expect(stub.getCall(0).args[0].formData.app_type).to.eql('dcl'); }); it('Does not reject when response contains compile error wording but config is set to ignore compile warnings', () => { - options.failOnCompileError = false; - let body = 'Identical to previous version -- not replacing.'; mockDoPostRequest(body); - return rokuDeploy.publish(options).then((result) => { + return rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + failOnCompileError: false, + close: false + }).then((result) => { expect(result.results.body).to.equal(body); }, () => { assert.fail('Should have resolved promise'); @@ -1424,12 +1421,17 @@ describe('RokuDeploy', () => { }); it('rejects when response is unknown status code', async () => { - options.failOnCompileError = true; let body = 'Identical to previous version -- not replacing.'; mockDoPostRequest(body, 123); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + failOnCompileError: true, + zip: zipFile, + close: false + }); } catch (e) { expect(e).to.be.instanceof(errors.InvalidDeviceResponseCodeError); return; @@ -1438,11 +1440,16 @@ describe('RokuDeploy', () => { }); it('rejects when user is unauthorized', async () => { - options.failOnCompileError = true; mockDoPostRequest('', 401); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + failOnCompileError: true, + zip: zipFile, + close: false + }); } catch (e) { expect(e).to.be.instanceof(errors.UnauthorizedDeviceResponseError); return; @@ -1451,11 +1458,16 @@ describe('RokuDeploy', () => { }); it('rejects when encountering an undefined response', async () => { - options.failOnCompileError = true; mockDoPostRequest(null); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + failOnCompileError: true, + zip: zipFile, + close: false + }); } catch (e) { assert.ok('Exception was thrown as expected'); return; @@ -1468,7 +1480,13 @@ describe('RokuDeploy', () => { let spy = mockDoPostRequest('', 577); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false + }); } catch (e) { expect(spy.callCount).to.eql(1); assert.ok('Exception was thrown as expected'); @@ -1492,7 +1510,13 @@ describe('RokuDeploy', () => { }); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + deleteDevChannel: false, + close: false + }); } catch (e) { expect(spy.callCount).to.eql(2); assert.ok('Exception was thrown as expected'); @@ -1511,14 +1535,19 @@ describe('RokuDeploy', () => { } } - it('Should throw an excpetion', async () => { + it('Should throw an exception', async () => { options.failOnCompileError = true; sinon.stub(rokuDeploy as any, 'doPostRequest').callsFake((params) => { throw new ErrorWithConnectionResetCode(); }); try { - await rokuDeploy.publish(options); + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipFile, + close: false + }); } catch (e) { assert.ok('Exception was thrown as expected'); expect(e).to.be.instanceof(errors.ConnectionResetError); @@ -1526,30 +1555,94 @@ describe('RokuDeploy', () => { } assert.fail('Should not have succeeded'); }); - }); - describe('convertToSquashfs', () => { - it('should not return an error if successful', async () => { - mockDoPostRequest('Conversion succeeded


Parallel mksquashfs: Using 1 processor'); - await rokuDeploy.convertToSquashfs(options); + it('succeeds when using a pre-built zip', async () => { + mockDoPostRequest(); + const zipPath = `${outDir}/myapp.zip`; + fsExtra.outputFileSync(zipPath, 'zip contents'); + + const result = await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipPath, + close: false + }); + expect(result.message).to.equal('Successful sideload'); }); - it('should return MissingRequiredOptionError if host was not provided', async () => { + it('calls closeChannel before sideloading by default', async () => { mockDoPostRequest(); - try { - options.host = undefined; - await rokuDeploy.convertToSquashfs(options); - } catch (e) { - expect(e).to.be.instanceof(errors.MissingRequiredOptionError); - return; - } - assert.fail('Should not have succeeded'); + const closeChannelStub = sinon.stub(rokuDeploy, 'closeChannel').resolves(); + const zipPath = `${outDir}/myapp.zip`; + fsExtra.outputFileSync(zipPath, 'zip contents'); + + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipPath + }); + expect(closeChannelStub.callCount).to.eql(1); + }); + + it('skips closeChannel when close is false', async () => { + mockDoPostRequest(); + const closeChannelStub = sinon.stub(rokuDeploy, 'closeChannel').resolves(); + const zipPath = `${outDir}/myapp.zip`; + fsExtra.outputFileSync(zipPath, 'zip contents'); + + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + zip: zipPath, + close: false + }); + expect(closeChannelStub.callCount).to.eql(0); + }); + + it('triggers zip when dir is provided', async () => { + mockDoPostRequest(); + // Stub zip to create the file at the path sideload expects + const zipStub = sinon.stub(rokuDeploy, 'zip').callsFake((zipOptions) => { + fsExtra.outputFileSync(zipOptions.out, 'dummy'); + return Promise.resolve(); + }); + sinon.stub(rokuDeploy, 'closeChannel').resolves(); + + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: 'password', + dir: rootDir + }); + expect(zipStub.callCount).to.eql(1); + }); + + it('fails when no password is provided', async () => { + await expectThrowsAsync(async () => { + await rokuDeploy.sideload({ + host: '1.2.3.4', + password: undefined, + zip: zipFile + }); + }, 'Missing required option: password'); + }); + }); + + describe('squash', () => { + it('should not return an error if successful', async () => { + mockDoPostRequest('Conversion succeeded


Parallel mksquashfs: Using 1 processor'); + await rokuDeploy.convertToSquashfs({ + host: options.host, + password: 'password' + }); }); it('should return ConvertError if converting failed', async () => { mockDoPostRequest(); try { - await rokuDeploy.convertToSquashfs(options); + await rokuDeploy.convertToSquashfs({ + host: options.host, + password: 'password' + }); } catch (e) { expect(e).to.be.instanceof(errors.ConvertError); return; @@ -1564,7 +1657,11 @@ describe('RokuDeploy', () => { }); doPostStub.onSecondCall().returns({ body: '..."fileType":"squashfs"...' }); try { - await rokuDeploy.convertToSquashfs(options); + await rokuDeploy.convertToSquashfs({ + ...options, + host: options.host, + password: 'password' + }); } catch (e) { assert.fail('Should not have throw'); } @@ -1576,7 +1673,11 @@ describe('RokuDeploy', () => { throw new ErrorWithCode('Something else'); }); try { - await rokuDeploy.convertToSquashfs(options); + await rokuDeploy.convertToSquashfs({ + ...options, + host: options.host, + password: 'password' + }); } catch (e) { expect(e).to.be.instanceof(ErrorWithCode); expect(e['code']).to.be.eql('Something else'); @@ -1592,7 +1693,11 @@ describe('RokuDeploy', () => { }); doPostStub.onSecondCall().returns({ body: '..."fileType":"zip"...' }); try { - await rokuDeploy.convertToSquashfs(options); + await rokuDeploy.convertToSquashfs({ + ...options, + host: options.host, + password: 'password' + }); } catch (e) { expect(e).to.be.instanceof(errors.ConvertError); return; @@ -1609,7 +1714,11 @@ describe('RokuDeploy', () => { throw new Error('Never seen'); }); try { - await rokuDeploy.convertToSquashfs(options); + await rokuDeploy.convertToSquashfs({ + ...options, + host: options.host, + password: 'password' + }); } catch (e) { expect(e).to.be.instanceof(ErrorWithCode); return; @@ -1627,13 +1736,13 @@ describe('RokuDeploy', () => { } } - describe('rekeyDevice', () => { + describe('rekey', () => { beforeEach(() => { const body = ` ${options.devId} `; mockDoGetRequest(body); - fsExtra.outputFileSync(path.resolve(rootDir, options.rekeySignedPackage), ''); + fsExtra.outputFileSync(path.resolve(rootDir, options.pkg), ''); }); it('does not crash when archive is undefined', async () => { @@ -1641,7 +1750,13 @@ describe('RokuDeploy', () => { sinon.stub(fsExtra, 'createReadStream').throws(expectedError); let actualError: Error; try { - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: options.devId + }); } catch (e) { actualError = e as Error; } @@ -1653,11 +1768,18 @@ describe('RokuDeploy', () => { Success. `; mockDoPostRequest(body); - options.rekeySignedPackage = s`../notReal.pkg`; + fsExtra.outputFileSync(s`${tempDir}/notReal.pkg`, ''); //small sleep to ensure the file exists (hack for testing!) await util.sleep(10); - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + cwd: rootDir, + pkg: s`../notReal.pkg`, + signingPassword: options.signingPassword, + devId: options.devId + }); }); it('should work with absolute path', async () => { @@ -1665,9 +1787,13 @@ describe('RokuDeploy', () => { Success. `; mockDoPostRequest(body); - - options.rekeySignedPackage = s`${tempDir}/testSignedPackage.pkg`; - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: s`${tempDir}/testSignedPackage.pkg`, + signingPassword: options.signingPassword, + devId: options.devId + }); }); it('should not return an error if dev ID is set and matches output', async () => { @@ -1675,7 +1801,13 @@ describe('RokuDeploy', () => { Success. `; mockDoPostRequest(body); - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: options.devId + }); }); it('should not return an error if dev ID is not set', async () => { @@ -1683,36 +1815,25 @@ describe('RokuDeploy', () => { Success. `; mockDoPostRequest(body); - options.devId = undefined; - await rokuDeploy.rekeyDevice(options); - }); - - it('should throw error if missing rekeySignedPackage option', async () => { - try { - options.rekeySignedPackage = null; - await rokuDeploy.rekeyDevice(options); - } catch (e) { - expect(e).to.be.instanceof(errors.MissingRequiredOptionError); - return; - } - assert.fail('Exception should have been thrown'); - }); - - it('should throw error if missing signingPassword option', async () => { - try { - options.signingPassword = null; - await rokuDeploy.rekeyDevice(options); - } catch (e) { - expect(e).to.be.instanceof(errors.MissingRequiredOptionError); - return; - } - assert.fail('Exception should have been thrown'); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: undefined + }); }); it('should throw error if response is not parsable', async () => { try { mockDoPostRequest(); - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: options.devId + }); } catch (e) { expect(e).to.be.instanceof(errors.UnparsableDeviceResponseError); return; @@ -1726,7 +1847,13 @@ describe('RokuDeploy', () => { Invalid public key. `; mockDoPostRequest(body); - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: options.devId + }); } catch (e) { expect(e).to.be.instanceof(errors.FailedDeviceResponseError); return; @@ -1740,9 +1867,13 @@ describe('RokuDeploy', () => { Success. `; mockDoPostRequest(body); - - options.devId = '45fdc2019903ac333ff624b0b2cddd2c733c3e74'; - await rokuDeploy.rekeyDevice(options); + await rokuDeploy.rekeyDevice({ + host: '1.2.3.4', + password: 'password', + pkg: options.pkg, + signingPassword: options.signingPassword, + devId: '45fdc2019903ac333ff624b0b2cddd2c733c3e74' + }); } catch (e) { expect(e).to.be.instanceof(errors.UnknownDeviceResponseError); return; @@ -1751,16 +1882,35 @@ describe('RokuDeploy', () => { }); }); - describe('signExistingPackage', () => { + describe('package', () => { + let onHandler: any; beforeEach(() => { - fsExtra.outputFileSync(`${stagingDir}/manifest`, ``); - }); + fsExtra.outputFileSync(`${tempDir}/manifest`, ` + title=RokuDeployTestChannel + major_version=1 + minor_version=0`); + sinon.stub(fsExtra, 'ensureDir').callsFake(((pth: string, callback: (err: Error) => void) => { + //do nothing, assume the dir gets created + }) as any); - it('should return our error if signingPassword is not supplied', async () => { - options.signingPassword = undefined; - await expectThrowsAsync(async () => { - await rokuDeploy.signExistingPackage(options); - }, 'Must supply signingPassword'); + //intercept the http request + sinon.stub(request, 'get').callsFake(() => { + let req: any = { + on: (event, callback) => { + process.nextTick(() => { + onHandler(event, callback); + }); + return req; + }, + pipe: async () => { + //if a write stream gets created, write some stuff and close it + const writeStream = await writeStreamPromise; + writeStream.write('test'); + writeStream.close(); + } + }; + return req; + }); }); it('should return an error if there is a problem with the network request', async () => { @@ -1771,7 +1921,12 @@ describe('RokuDeploy', () => { process.nextTick(callback, error); return {} as any; }); - await rokuDeploy.signExistingPackage(options); + await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }); } catch (e) { expect(e).to.equal(error); return; @@ -1782,7 +1937,12 @@ describe('RokuDeploy', () => { it('should return our error if it received invalid data', async () => { try { mockDoPostRequest(null); - await rokuDeploy.signExistingPackage(options); + await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }); } catch (e) { expect(e).to.be.instanceof(errors.UnparsableDeviceResponseError); return; @@ -1798,7 +1958,12 @@ describe('RokuDeploy', () => { mockDoPostRequest(body); await expectThrowsAsync( - rokuDeploy.signExistingPackage(options), + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }), 'Invalid Password.' ); }); @@ -1809,41 +1974,199 @@ describe('RokuDeploy', () => { node.appendChild(pkgDiv);`; mockDoPostRequest(body); - let pkgPath = await rokuDeploy.signExistingPackage(options); - expect(pkgPath).to.equal('pkgs//P6953175d5df120c0069c53de12515b9a.pkg'); + const stub = sinon.stub(rokuDeploy as any, 'downloadFile').returns(Promise.resolve('pkgs//P6953175d5df120c0069c53de12515b9a.pkg')); + + let pkgPath = await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + out: s`${outDir}/roku-deploy.pkg`, + manifestPath: s`${tempDir}/manifest` + }); + expect(pkgPath).to.equal(s`${outDir}/roku-deploy.pkg`); + expect(stub.getCall(0).args[0].url).to.equal('http://1.2.3.4:80/pkgs//P6953175d5df120c0069c53de12515b9a.pkg'); }); it('should return created pkg from SD card on success', async () => { mockDoPostRequest(fakePluginPackageResponse); - let pkgPath = await rokuDeploy.signExistingPackage(options); - expect(pkgPath).to.equal('pkgs/sdcard0/Pae6cec1eab06a45ca1a7f5b69edd3a20.pkg'); + const stub = sinon.stub(rokuDeploy as any, 'downloadFile').returns(Promise.resolve()); + + let pkgPath = await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest`, + out: s`${outDir}/roku-deploy.pkg` + }); + expect(pkgPath).to.equal(s`${outDir}/roku-deploy.pkg`); + expect(stub.getCall(0).args[0].url).to.equal('http://1.2.3.4:80/pkgs/sdcard0/Pae6cec1eab06a45ca1a7f5b69edd3a20.pkg'); + }); + + it('should return created pkg from a JSON', async () => { + let body = `var params = JSON.parse('{"messages":[{"text":"Success.","text_type":"text","type":"success"}],"metadata":{"dev_key":true,"voice_sdk":false}, + "packages":[{"appType":"channel","fileType":"zip", + "pkgPath":"pkgs/P69f2e034f46a57a98bb35d387f22e1f3.pkg"}]}')`; + mockDoPostRequest(body); + + const stub = sinon.stub(rokuDeploy as any, 'downloadFile').returns(Promise.resolve()); + + let pkgPath = await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest`, + out: s`${outDir}/roku-deploy.pkg` + }); + expect(pkgPath).to.equal(s`${outDir}/roku-deploy.pkg`); + expect(stub.getCall(0).args[0].url).to.equal('http://1.2.3.4:80/pkgs/P69f2e034f46a57a98bb35d387f22e1f3.pkg'); }); it('should return our fallback error if neither error or package link was detected', async () => { mockDoPostRequest(); await expectThrowsAsync( - rokuDeploy.signExistingPackage(options), + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }), 'Unknown error signing package' ); }); + + it('should return error if dev id does not match', async () => { + mockDoGetRequest(` + + 789 + + `); + await expectThrowsAsync( + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + devId: '123', + manifestPath: s`${tempDir}/manifest` + }), + `Package signing cancelled: provided devId '123' does not match on-device devId '789'` + ); + }); + + it('should return error if neither manifestPath nor appTitle and appVersion are provided', async () => { + await expectThrowsAsync( + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + devId: '123' + }), + `Either appTitle and appVersion or manifestPath must be provided` + ); + }); + + it('should return error if major or minor version is missing from manifest', async () => { + fsExtra.outputFileSync(`${tempDir}/manifest`, `title=AwesomeApp`); + await expectThrowsAsync( + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + devId: '123', + manifestPath: s`${tempDir}/manifest` + }), + `Either major or minor version is missing from the manifest` + ); + }); + + it('should return error if value for appTitle is missing from manifest', async () => { + fsExtra.outputFileSync(`${tempDir}/manifest`, `major_version=1\nminor_version=0`); + await expectThrowsAsync( + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + devId: '123', + manifestPath: s`${tempDir}/manifest` + }), + `Value for appTitle is missing from the manifest` + ); + }); + + it('returns a pkg file path on success', async () => { + //the write stream should return null, which causes a specific branch to be executed + createWriteStreamStub.callsFake(() => { + return null; + }); + + // let onHandler: any; + onHandler = (event, callback) => { + if (event === 'response') { + callback({ + statusCode: 200 + }); + } + }; + + let body = `var pkgDiv = document.createElement('div'); + pkgDiv.innerHTML = '
P6953175d5df120c0069c53de12515b9a.pkg
package file (7360 bytes)
'; + node.appendChild(pkgDiv);`; + mockDoPostRequest(body); + + let error: Error; + try { + await rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'password', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }); + } catch (e) { + error = e as any; + } + expect(error.message.startsWith('Unable to create write stream for')).to.be.true; + }); + + it('throws when error in request is encountered', async () => { + onHandler = (event, callback) => { + if (event === 'error') { + callback(new Error('Some error')); + } + }; + + let body = `var pkgDiv = document.createElement('div'); + pkgDiv.innerHTML = '
P6953175d5df120c0069c53de12515b9a.pkg
package file (7360 bytes)
'; + node.appendChild(pkgDiv);`; + mockDoPostRequest(body); + + await expectThrowsAsync( + rokuDeploy.createSignedPackage({ + host: '1.2.3.4', + password: 'aaaa', + signingPassword: options.signingPassword, + manifestPath: s`${tempDir}/manifest` + }), + 'Some error' + ); + }); }); - describe('prepublishToStaging', () => { + describe('stage', () => { it('should use outDir for staging folder', async () => { - await rokuDeploy.prepublishToStaging({ + await rokuDeploy.stage({ files: [ 'manifest' - ] + ], + rootDir: rootDir }); expectPathExists(`${stagingDir}`); }); it('should support overriding the staging folder', async () => { - await rokuDeploy.prepublishToStaging({ - ...options, + await rokuDeploy.stage({ files: ['manifest'], - stagingDir: `${tempDir}/custom-out-dir` + out: `${tempDir}/custom-out-dir`, + rootDir: rootDir }); expectPathExists(`${tempDir}/custom-out-dir`); }); @@ -1853,11 +2176,14 @@ describe('RokuDeploy', () => { 'manifest', 'source/main.brs' ]); - options.files = [ - 'manifest', - 'source/main.brs' - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + 'source/main.brs' + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/manifest`); expectPathExists(`${stagingDir}/source/main.brs`); }); @@ -1867,14 +2193,17 @@ describe('RokuDeploy', () => { 'manifest', 'source/main.brs' ]); - options.files = [ - 'manifest', - { - src: 'source/**/*', - dest: 'source' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + { + src: 'source/**/*', + dest: 'source' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/source/main.brs`); }); @@ -1883,21 +2212,24 @@ describe('RokuDeploy', () => { 'manifest', 'source/main.brs' ]); - options.files = [ - { - src: 'manifest', - dest: '' - }, - { - src: 'source/**/*', - dest: 'source/' - }, - { - src: 'source/main.brs', - dest: 'source/main.brs' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + { + src: 'manifest', + dest: '' + }, + { + src: 'source/**/*', + dest: 'source/' + }, + { + src: 'source/main.brs', + dest: 'source/main.brs' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/manifest`); expectPathExists(`${stagingDir}/source/main.brs`); }); @@ -1907,17 +2239,20 @@ describe('RokuDeploy', () => { 'manifest', 'source/main.brs' ]); - options.files = [ - { - src: 'manifest', - dest: '' - }, - { - src: 'source/main.brs', - dest: 'source/renamed.brs' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + { + src: 'manifest', + dest: '' + }, + { + src: 'source/main.brs', + dest: 'source/renamed.brs' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/source/renamed.brs`); }); @@ -1925,17 +2260,20 @@ describe('RokuDeploy', () => { writeFiles(rootDir, [ 'manifest' ]); - options.files = [ - { - src: `${rootDir}/manifest`, - dest: '' - }, - { - src: 'source/main.brs', - dest: 'source/renamed.brs' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + { + src: sp`${rootDir}/manifest`, + dest: '' + }, + { + src: 'source/main.brs', + dest: 'source/renamed.brs' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/manifest`); }); @@ -1945,13 +2283,15 @@ describe('RokuDeploy', () => { 'components/loader/loader.brs', 'components/scenes/home/home.brs' ]); - options.files = [ - 'manifest', - 'components/!(scenes)/**/*' - ]; - options.retainStagingFolder = true; console.log('before'); - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + 'components/!(scenes)/**/*' + ], + rootDir: rootDir, + out: stagingDir + }); console.log('after'); expectPathExists(s`${stagingDir}/components/loader/loader.brs`); expectPathNotExists(s`${stagingDir}/components/scenes/home/home.brs`); @@ -1963,27 +2303,30 @@ describe('RokuDeploy', () => { 'components/Loader/Loader.brs', 'components/scenes/Home/Home.brs' ]); - options.retainStagingFolder = true; - await rokuDeploy.prepublishToStaging({ - ...options, files: [ + await rokuDeploy.stage({ + files: [ 'manifest', 'source', 'components/**/*', '!components/scenes/**/*' - ] + ], + rootDir: rootDir, + out: stagingDir }); expectPathExists(`${stagingDir}/components/Loader/Loader.brs`); expectPathNotExists(`${stagingDir}/components/scenes/Home/Home.brs`); }); it('throws on invalid entries', async () => { - options.files = [ - 'manifest', - {} - ]; - options.retainStagingFolder = true; try { - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + {} + ], + rootDir: rootDir, + out: stagingDir + }); expect(true).to.be.false; } catch (e) { expect(true).to.be.true; @@ -1992,14 +2335,17 @@ describe('RokuDeploy', () => { it('retains subfolder structure when referencing a folder', async () => { fsExtra.outputFileSync(`${rootDir}/flavors/shared/resources/images/fhd/image.jpg`, ''); - options.files = [ - 'manifest', - { - src: 'flavors/shared/resources/**/*', - dest: 'resources' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + { + src: 'flavors/shared/resources/**/*', + dest: 'resources' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(`${stagingDir}/resources/images/fhd/image.jpg`); }); @@ -2009,15 +2355,18 @@ describe('RokuDeploy', () => { 'flavors/shared/resources/images/fhd/image.jpg', 'resources/image.jpg' ]); - options.files = [ - 'manifest', - { - //the relative structure after /resources should be retained - src: 'flavors/shared/resources/**/*', - dest: 'resources' - } - ]; - await rokuDeploy.prepublishToStaging(options); + await rokuDeploy.stage({ + files: [ + 'manifest', + { + //the relative structure after /resources should be retained + src: 'flavors/shared/resources/**/*', + dest: 'resources' + } + ], + rootDir: rootDir, + out: stagingDir + }); expectPathExists(s`${stagingDir}/resources/images/fhd/image.jpg`); expectPathNotExists(s`${stagingDir}/resources/image.jpg`); }); @@ -2101,15 +2450,21 @@ describe('RokuDeploy', () => { ] }; - let stagingDirValue = rokuDeploy.getOptions(opts).stagingDir; //getFilePaths detects the file - expect(await rokuDeploy.getFilePaths(['renamed_test.md'], opts.rootDir)).to.eql([{ + expect(await rokuDeploy.getFilePaths({ files: ['renamed_test.md'], rootDir: opts.rootDir })).to.eql([{ src: s`${opts.rootDir}/renamed_test.md`, dest: s`renamed_test.md` }]); - await rokuDeploy.prepublishToStaging(opts); - let stagedFilePath = s`${stagingDirValue}/renamed_test.md`; + await rokuDeploy.stage({ + rootDir: rootDir, + out: stagingDir, + files: [ + 'manifest', + 'renamed_test.md' + ] + }); + let stagedFilePath = s`${stagingDir}/renamed_test.md`; expectPathExists(stagedFilePath); let fileContents = await fsExtra.readFile(stagedFilePath); expect(fileContents.toString()).to.equal('hello symlink'); @@ -2138,10 +2493,9 @@ describe('RokuDeploy', () => { ] }; - let stagingPath = rokuDeploy.getOptions(opts).stagingDir; //getFilePaths detects the file expect( - (await rokuDeploy.getFilePaths(opts.files, opts.rootDir)).sort((a, b) => a.src.localeCompare(b.src)) + (await rokuDeploy.getFilePaths({ files: opts.files, rootDir: opts.rootDir })).sort((a, b) => a.src.localeCompare(b.src)) ).to.eql([{ src: s`${tempDir}/mainProject/source/lib/lib.brs`, dest: s`source/lib/lib.brs` @@ -2153,44 +2507,117 @@ describe('RokuDeploy', () => { dest: s`source/main.brs` }]); - await rokuDeploy.prepublishToStaging(opts); - expect(fsExtra.pathExistsSync(`${stagingPath}/source/lib/promise/promise.brs`)); + await rokuDeploy.stage({ + files: [ + 'manifest', + 'source/**/*' + ], + rootDir: s`${tempDir}/mainProject` + }); + expect(fsExtra.pathExistsSync(`${stagingDir}/source/lib/promise/promise.brs`)); }); }); - }); + it('is resilient to file system errors', async () => { + let copy = fsExtra.copy; + let count = 0; + + //mock writeFile so we can throw a few errors during the test + sinon.stub(fsExtra, 'copy').callsFake((...args) => { + count += 1; + //fail a few times + if (count < 5) { + throw new Error('fake error thrown as part of the unit test'); + } else { + return copy.apply(fsExtra, args); + } + }); + + //override the retry milliseconds to make test run faster + let orig = util.tryRepeatAsync.bind(util); + sinon.stub(util, 'tryRepeatAsync').callsFake(async (...args) => { + return orig(args[0], args[1], 0); + }); + + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + + await rokuDeploy.stage({ + rootDir: rootDir, + out: stagingDir, + files: [ + 'source/main.brs' + ] + }); + expectPathExists(s`${stagingDir}/source/main.brs`); + expect(count).to.be.greaterThan(4); + }); + + it('throws underlying error after the max fs error threshold is reached', async () => { + let copy = fsExtra.copy; + let count = 0; + + //mock writeFile so we can throw a few errors during the test + sinon.stub(fsExtra, 'copy').callsFake((...args) => { + count += 1; + //fail a few times + if (count < 15) { + throw new Error('fake error thrown as part of the unit test'); + } else { + return copy.apply(fsExtra, args); + } + }); + + //override the timeout for tryRepeatAsync so this test runs faster + let orig = util.tryRepeatAsync.bind(util); + sinon.stub(util, 'tryRepeatAsync').callsFake(async (...args) => { + return orig(args[0], args[1], 0); + }); + + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + await expectThrowsAsync( + rokuDeploy.stage({ + rootDir: rootDir, + out: stagingDir, + files: [ + 'source/main.brs' + ] + }), + 'fake error thrown as part of the unit test' + ); + }); + }); describe('normalizeFilesArray', () => { it('catches invalid dest entries', () => { expect(() => { - rokuDeploy['normalizeFilesArray']([{ + util['normalizeFilesArray']([{ src: 'some/path', dest: true }]); }).to.throw(); expect(() => { - rokuDeploy['normalizeFilesArray']([{ + util['normalizeFilesArray']([{ src: 'some/path', dest: false }]); }).to.throw(); expect(() => { - rokuDeploy['normalizeFilesArray']([{ + util['normalizeFilesArray']([{ src: 'some/path', dest: /asdf/gi }]); }).to.throw(); expect(() => { - rokuDeploy['normalizeFilesArray']([{ + util['normalizeFilesArray']([{ src: 'some/path', dest: {} }]); }).to.throw(); expect(() => { - rokuDeploy['normalizeFilesArray']([{ + util['normalizeFilesArray']([{ src: 'some/path', dest: [] }]); @@ -2198,27 +2625,27 @@ describe('RokuDeploy', () => { }); it('normalizes directory separators paths', () => { - expect(rokuDeploy['normalizeFilesArray']([{ + expect(util['normalizeFilesArray']([{ src: `long/source/path`, dest: `long/dest/path` }])).to.eql([{ - src: s`long/source/path`, + src: sp`long/source/path`, dest: s`long/dest/path` }]); }); it('works for simple strings', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ 'manifest', 'source/main.brs' ])).to.eql([ 'manifest', - s`source/main.brs` + 'source/main.brs' ]); }); it('works for negated strings', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ '!.git' ])).to.eql([ '!.git' @@ -2226,7 +2653,7 @@ describe('RokuDeploy', () => { }); it('skips falsey and bogus entries', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ '', 'manifest', false, @@ -2238,7 +2665,7 @@ describe('RokuDeploy', () => { }); it('works for {src:string} objects', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ { src: 'manifest' } @@ -2249,7 +2676,7 @@ describe('RokuDeploy', () => { }); it('works for {src:string[]} objects', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ { src: [ 'manifest', @@ -2260,96 +2687,32 @@ describe('RokuDeploy', () => { src: 'manifest', dest: undefined }, { - src: s`source/main.brs`, + src: sp`source/main.brs`, dest: undefined }]); }); - it('preserves negation prefix and parent-dir segments in {src:string[]} entries', () => { - expect(rokuDeploy['normalizeFilesArray']([ - { - src: [ - '../../external/**/*', - '!../../external/skip/**/*.brs' - ], - dest: '/' - } - ])).to.eql([{ - src: s`../../external/**/*`, - dest: s`/` - }, { - src: `!${s`../../external/skip/**/*.brs`}`, - dest: s`/` - }]); - }); - it('retains dest option', () => { - expect(rokuDeploy['normalizeFilesArray']([ + expect(util['normalizeFilesArray']([ { src: 'source/config.dev.brs', dest: 'source/config.brs' } ])).to.eql([{ - src: s`source/config.dev.brs`, + src: sp`source/config.dev.brs`, dest: s`source/config.brs` }]); }); it('throws when encountering invalid entries', () => { - expect(() => rokuDeploy['normalizeFilesArray']([true])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([/asdf/])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([new Date()])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([1])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([{ src: true }])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([{ src: /asdf/ }])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([{ src: new Date() }])).to.throw(); - expect(() => rokuDeploy['normalizeFilesArray']([{ src: 1 }])).to.throw(); - }); - }); - - describe('deploy', () => { - it('does the whole migration', async () => { - mockDoPostRequest(); - - writeFiles(rootDir, ['manifest']); - - let result = await rokuDeploy.deploy(options); - expect(result).not.to.be.undefined; - }); - - it('continues with deploy if deleteInstalledChannel fails', async () => { - sinon.stub(rokuDeploy, 'deleteInstalledChannel').returns( - Promise.reject( - new Error('failed') - ) - ); - mockDoPostRequest(); - let result = await rokuDeploy.deploy({ - ...options, - //something in the previous test is locking the default output zip file. We should fix that at some point... - outDir: s`${tempDir}/test1` - }); - expect(result).not.to.be.undefined; - }); - - it('should delete installed channel if requested', async () => { - const spy = sinon.spy(rokuDeploy, 'deleteInstalledChannel'); - options.deleteInstalledChannel = true; - mockDoPostRequest(); - - await rokuDeploy.deploy(options); - - expect(spy.called).to.equal(true); - }); - - it('should not delete installed channel if not requested', async () => { - const spy = sinon.spy(rokuDeploy, 'deleteInstalledChannel'); - options.deleteInstalledChannel = false; - mockDoPostRequest(); - - await rokuDeploy.deploy(options); - - expect(spy.notCalled).to.equal(true); + expect(() => util['normalizeFilesArray']([true])).to.throw(); + expect(() => util['normalizeFilesArray']([/asdf/])).to.throw(); + expect(() => util['normalizeFilesArray']([new Date()])).to.throw(); + expect(() => util['normalizeFilesArray']([1])).to.throw(); + expect(() => util['normalizeFilesArray']([{ src: true }])).to.throw(); + expect(() => util['normalizeFilesArray']([{ src: /asdf/ }])).to.throw(); + expect(() => util['normalizeFilesArray']([{ src: new Date() }])).to.throw(); + expect(() => util['normalizeFilesArray']([{ src: 1 }])).to.throw(); }); }); @@ -2362,7 +2725,10 @@ describe('RokuDeploy', () => { it('should send a request to the plugin_swup endpoint for a reboot', async () => { mockGetDeviceInfo('15.0.4'); let stub = mockDoPostRequest(); - let result = await rokuDeploy.rebootDevice(options); + let result = await rokuDeploy.rebootDevice({ + host: '1.2.3.4', + password: 'password' + }); expect(result).not.to.be.undefined; expect(stub.args[0][0].url).to.include(`/plugin_swup`); expect(stub.args[0][0].formData.mysubmit).to.include('Reboot'); @@ -2371,7 +2737,10 @@ describe('RokuDeploy', () => { it('should send a request to the plugin_swup endpoint to check for update', async () => { mockGetDeviceInfo('15.0.4'); let stub = mockDoPostRequest(); - let result = await rokuDeploy.checkForUpdate(options); + let result = await rokuDeploy.checkForUpdate({ + host: '1.2.3.4', + password: 'password' + }); expect(result).not.to.be.undefined; expect(stub.args[0][0].url).to.include(`/plugin_swup`); expect(stub.args[0][0].formData.mysubmit).to.include('CheckUpdate'); @@ -2380,28 +2749,40 @@ describe('RokuDeploy', () => { it('should fail to reboot when sw version is just below minimum (15.0.3)', async () => { mockGetDeviceInfo('15.0.3'); await assertThrowsAsync(async () => { - await rokuDeploy.rebootDevice(options); + await rokuDeploy.rebootDevice({ + host: '1.2.3.4', + password: 'password' + }); }); }); it('should fail to reboot when software-version is null', async () => { mockGetDeviceInfo(null); await assertThrowsAsync(async () => { - await rokuDeploy.rebootDevice(options); + await rokuDeploy.rebootDevice({ + host: '1.2.3.4', + password: 'password' + }); }); }); it('should fail to check for updates when sw version is just below minimum (15.0.3)', async () => { mockGetDeviceInfo('15.0.3'); await assertThrowsAsync(async () => { - await rokuDeploy.checkForUpdate(options); + await rokuDeploy.checkForUpdate({ + host: '1.2.3.4', + password: 'password' + }); }); }); it('should fail to check for updates when software-version is null', async () => { mockGetDeviceInfo(null); await assertThrowsAsync(async () => { - await rokuDeploy.checkForUpdate(options); + await rokuDeploy.checkForUpdate({ + host: '1.2.3.4', + password: 'password' + }); }); }); }); @@ -2410,12 +2791,15 @@ describe('RokuDeploy', () => { it('attempts to delete any installed dev channel on the device', async () => { mockDoPostRequest(); - let result = await rokuDeploy.deleteInstalledChannel(options); + let result = await rokuDeploy.deleteDevChannel({ + host: '1.2.3.4', + password: 'password' + }); expect(result).not.to.be.undefined; }); }); - describe('takeScreenshot', () => { + describe('screenshot', () => { let onHandler: any; let screenshotAddress: any; @@ -2458,19 +2842,19 @@ describe('RokuDeploy', () => { `); mockDoPostRequest(body); - await expectThrowsAsync(rokuDeploy.takeScreenshot({ host: options.host, password: options.password })); + await expectThrowsAsync(rokuDeploy.captureScreenshot({ host: options.host, password: 'password' })); }); it('throws when there is no response body', async () => { // missing body mockDoPostRequest(null); - await expectThrowsAsync(rokuDeploy.takeScreenshot({ host: options.host, password: options.password })); + await expectThrowsAsync(rokuDeploy.captureScreenshot({ host: options.host, password: 'password' })); }); it('throws when there is an empty response body', async () => { // empty body mockDoPostRequest(); - await expectThrowsAsync(rokuDeploy.takeScreenshot({ host: options.host, password: options.password })); + await expectThrowsAsync(rokuDeploy.captureScreenshot({ host: options.host, password: 'password' })); }); it('throws when there is an error downloading the image from device', async () => { @@ -2491,7 +2875,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - await expectThrowsAsync(rokuDeploy.takeScreenshot({ host: options.host, password: options.password })); + await expectThrowsAsync(rokuDeploy.captureScreenshot({ host: options.host, password: 'password' })); }); it('handles the device returning a png', async () => { @@ -2512,7 +2896,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password' }); expect(result).not.to.be.undefined; expect(path.extname(result)).to.equal('.png'); expect(fsExtra.existsSync(result)); @@ -2536,7 +2920,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password' }); expect(result).not.to.be.undefined; expect(path.extname(result)).to.equal('.jpg'); expect(fsExtra.existsSync(result)); @@ -2560,7 +2944,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password, outDir: `${tempDir}/myScreenShots` }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/myScreenShots/screenshot` }); expect(result).not.to.be.undefined; expect(util.standardizePath(`${tempDir}/myScreenShots`)).to.equal(path.dirname(result)); expect(fsExtra.existsSync(result)); @@ -2584,7 +2968,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password, outDir: tempDir, outFile: 'my' }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/my` }); expect(result).not.to.be.undefined; expect(util.standardizePath(tempDir)).to.equal(path.dirname(result)); expect(fsExtra.existsSync(path.join(tempDir, 'my.png'))); @@ -2608,7 +2992,7 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password, outDir: tempDir, outFile: 'my.jpg' }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/my.jpg` }); expect(result).not.to.be.undefined; expect(util.standardizePath(tempDir)).to.equal(path.dirname(result)); expect(fsExtra.existsSync(path.join(tempDir, 'my.jpg.png'))); @@ -2632,12 +3016,86 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password' }); + expect(result).not.to.be.undefined; + expect(fsExtra.existsSync(result)); + }); + + it('take a screenshot from the device and saves to temp but with the supplied file name (no extension added by default)', async () => { + let body = getFakeResponseBody(` + Shell.create('Roku.Message').trigger('Set message type', 'success').trigger('Set message content', 'Screenshot ok').trigger('Render', node); + + var screenshoot = document.createElement('div'); + screenshoot.innerHTML = '
'; + node.appendChild(screenshoot); + `); + + onHandler = (event, callback) => { + if (event === 'response') { + callback({ + statusCode: 200 + }); + } + }; + + mockDoPostRequest(body); + // With autoExtension: false (default), user-provided filename is used exactly + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/myFile` }); + expect(result).not.to.be.undefined; + expect(path.basename(result)).to.equal('myFile'); + expect(fsExtra.existsSync(result)); + }); + + it('autoExtension: true appends device extension when user filename has no extension', async () => { + let body = getFakeResponseBody(` + Shell.create('Roku.Message').trigger('Set message type', 'success').trigger('Set message content', 'Screenshot ok').trigger('Render', node); + + var screenshoot = document.createElement('div'); + screenshoot.innerHTML = '
'; + node.appendChild(screenshoot); + `); + + onHandler = (event, callback) => { + if (event === 'response') { + callback({ + statusCode: 200 + }); + } + }; + + mockDoPostRequest(body); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/myFile`, autoExtension: true }); + expect(result).not.to.be.undefined; + expect(path.basename(result)).to.equal('myFile.jpg'); + expect(fsExtra.existsSync(result)); + }); + + it('autoExtension: true swaps extension when user extension does not match device', async () => { + let body = getFakeResponseBody(` + Shell.create('Roku.Message').trigger('Set message type', 'success').trigger('Set message content', 'Screenshot ok').trigger('Render', node); + + var screenshoot = document.createElement('div'); + screenshoot.innerHTML = '
'; + node.appendChild(screenshoot); + `); + + onHandler = (event, callback) => { + if (event === 'response') { + callback({ + statusCode: 200 + }); + } + }; + + mockDoPostRequest(body); + // User provides .png but device returns .jpg - should swap to .jpg + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/myFile.png`, autoExtension: true }); expect(result).not.to.be.undefined; + expect(path.basename(result)).to.equal('myFile.jpg'); expect(fsExtra.existsSync(result)); }); - it('take a screenshot from the device and saves to temp but with the supplied file name', async () => { + it('autoExtension: true keeps extension when user extension matches device', async () => { let body = getFakeResponseBody(` Shell.create('Roku.Message').trigger('Set message type', 'success').trigger('Set message content', 'Screenshot ok').trigger('Render', node); @@ -2655,67 +3113,23 @@ describe('RokuDeploy', () => { }; mockDoPostRequest(body); - let result = await rokuDeploy.takeScreenshot({ host: options.host, password: options.password, outFile: 'myFile' }); + let result = await rokuDeploy.captureScreenshot({ host: options.host, password: 'password', out: `${tempDir}/myFile.jpg`, autoExtension: true }); expect(result).not.to.be.undefined; expect(path.basename(result)).to.equal('myFile.jpg'); expect(fsExtra.existsSync(result)); }); }); - describe('zipFolder', () => { + describe('makeZip', () => { //this is mainly done to hit 100% coverage, but why not ensure the errors are handled properly? :D it('rejects the promise when an error occurs', async () => { //zip path doesn't exist await assertThrowsAsync(async () => { - sinon.stub(fsExtra, 'outputFile').callsFake(() => { + sinon.stub(fsExtra, 'writeFile').callsFake(() => { throw new Error(); }); - await rokuDeploy.zipFolder('source', '.tmp/some/zip/path/that/does/not/exist'); - }); - }); - - it('allows modification of file contents with callback', async () => { - writeFiles(rootDir, [ - 'components/components/Loader/Loader.brs', - 'images/splash_hd.jpg', - 'source/main.brs', - 'manifest' - ]); - const stageFolder = path.join(tempDir, 'testProject'); - fsExtra.ensureDirSync(stageFolder); - const files = [ - 'components/components/Loader/Loader.brs', - 'images/splash_hd.jpg', - 'source/main.brs', - 'manifest' - ]; - for (const file of files) { - fsExtra.copySync(path.join(options.rootDir, file), path.join(stageFolder, file)); - } - - const outputZipPath = path.join(tempDir, 'output.zip'); - const addedManifestLine = 'bs_libs_required=roku_ads_lib'; - await rokuDeploy.zipFolder(stageFolder, outputZipPath, (file, data) => { - if (file.dest === 'manifest') { - let manifestContents = data.toString(); - manifestContents += addedManifestLine; - data = Buffer.from(manifestContents, 'utf8'); - } - return data; + await rokuDeploy['makeZip']('source', '.tmp/some/zip/path/that/does/not/exist'); }); - - const data = fsExtra.readFileSync(outputZipPath); - const zip = await JSZip.loadAsync(data as any); - for (const file of files) { - const zipFileContents = await zip.file(file.toString()).async('string'); - const sourcePath = path.join(options.rootDir, file); - const incomingContents = fsExtra.readFileSync(sourcePath, 'utf8'); - if (file === 'manifest') { - expect(zipFileContents).to.contain(addedManifestLine); - } else { - expect(zipFileContents).to.equal(incomingContents); - } - } }); it('filters the folders before making the zip', async () => { @@ -2730,7 +3144,7 @@ describe('RokuDeploy', () => { writeFiles(stagingDir, files); const outputZipPath = path.join(tempDir, 'output.zip'); - await rokuDeploy.zipFolder(stagingDir, outputZipPath, null, ['**/*', '!**/*.map']); + await rokuDeploy['makeZip'](stagingDir, outputZipPath, ['**/*', '!**/*.map']); const data = fsExtra.readFileSync(outputZipPath); const zip = await JSZip.loadAsync(data as any); @@ -2746,74 +3160,76 @@ describe('RokuDeploy', () => { ].sort().filter(x => !x.endsWith('.map')) ); }); - }); - describe('parseManifest', () => { - it('correctly parses valid manifest', async () => { - fsExtra.outputFileSync(`${rootDir}/manifest`, `title=AwesomeApp`); - let parsedManifest = await rokuDeploy.parseManifest(`${rootDir}/manifest`); - expect(parsedManifest.title).to.equal('AwesomeApp'); + it('should create zip in proper directory', async () => { + const outputZipPath = path.join(outDir, 'output.zip'); + await rokuDeploy['makeZip'](rootDir, outputZipPath, ['**/*', '!**/*.map']); + expectPathExists(outputZipPath); }); - it('Throws our error message for a missing file', async () => { - await expectThrowsAsync( - rokuDeploy.parseManifest('invalid-path'), - `invalid-path does not exist` - ); - }); - }); + it('should only include the specified files', async () => { + await rokuDeploy.stage({ + files: [ + 'manifest' + ], + out: stagingDir, + rootDir: rootDir + }); - describe('parseManifestFromString', () => { - it('correctly parses valid manifest', () => { - let parsedManifest = rokuDeploy.parseManifestFromString(` - title=RokuDeployTestChannel - major_version=1 - minor_version=0 - build_version=0 - splash_screen_hd=pkg:/images/splash_hd.jpg - ui_resolutions=hd - bs_const=IS_DEV_BUILD=false - splash_color=#000000 - `); - expect(parsedManifest.title).to.equal('RokuDeployTestChannel'); - expect(parsedManifest.major_version).to.equal('1'); - expect(parsedManifest.minor_version).to.equal('0'); - expect(parsedManifest.build_version).to.equal('0'); - expect(parsedManifest.splash_screen_hd).to.equal('pkg:/images/splash_hd.jpg'); - expect(parsedManifest.ui_resolutions).to.equal('hd'); - expect(parsedManifest.bs_const).to.equal('IS_DEV_BUILD=false'); - expect(parsedManifest.splash_color).to.equal('#000000'); - }); - }); + const zipPath = `${outDir}/roku-deploy.zip`; + await rokuDeploy.zip({ + dir: stagingDir, + out: zipPath + }); + const data = fsExtra.readFileSync(zipPath); + const zip = await JSZip.loadAsync(data as any); - describe('stringifyManifest', () => { - it('correctly converts back to a valid manifest when lineNumber and keyIndexes are provided', () => { - expect( - rokuDeploy.stringifyManifest( - rokuDeploy.parseManifestFromString('major_version=3\nminor_version=4') - ) - ).to.equal( - 'major_version=3\nminor_version=4' - ); + const files = ['manifest']; + for (const file of files) { + const zipFileContents = await zip.file(file.toString()).async('string'); + const sourcePath = path.join(options.rootDir, file); + const incomingContents = fsExtra.readFileSync(sourcePath, 'utf8'); + expect(zipFileContents).to.equal(incomingContents); + } }); - it('correctly converts back to a valid manifest when lineNumber and keyIndexes are not provided', () => { - const parsed = rokuDeploy.parseManifestFromString('title=App\nmajor_version=3'); - delete parsed.keyIndexes; - delete parsed.lineCount; - let outputParsedManifest = rokuDeploy.parseManifestFromString( - rokuDeploy.stringifyManifest(parsed) - ); - expect(outputParsedManifest.title).to.equal('App'); - expect(outputParsedManifest.major_version).to.equal('3'); - }); - }); + it('generates full package with defaults', async () => { + const filePaths = writeFiles(rootDir, [ + 'components/components/Loader/Loader.brs', + 'images/splash_hd.jpg', + 'source/main.brs', + 'manifest' + ]); + const zipPath = `${outDir}/roku-deploy.zip`; + await rokuDeploy.stage({ + files: filePaths, + out: stagingDir, + rootDir: rootDir + }); + await rokuDeploy.zip({ + dir: stagingDir, + out: zipPath + }); - describe('getFilePaths', () => { - const otherProjectName = 'otherProject'; - const otherProjectDir = s`${rootDir}/../${otherProjectName}`; + const data = fsExtra.readFileSync(zipPath); + const zip = await JSZip.loadAsync(data as any); + + for (const file of filePaths) { + const zipFileContents = await zip.file(file.toString())?.async('string'); + const sourcePath = path.join(rootDir, file); + const incomingContents = fsExtra.readFileSync(sourcePath, 'utf8'); + expect(zipFileContents).to.equal(incomingContents); + } + }); + }); + + describe('getFilePaths', () => { + const otherProjectName = 'otherProject'; + const otherProjectDir = sp`${rootDir}/../${otherProjectName}`; //create baseline project structure beforeEach(() => { + rokuDeploy = new RokuDeploy(); + options = { rootDir: rootDir } as RokuDeployOptions; fsExtra.ensureDirSync(`${rootDir}/components/emptyFolder`); writeFiles(rootDir, [ `manifest`, @@ -2827,7 +3243,7 @@ describe('RokuDeploy', () => { }); async function getFilePaths(files: FileEntry[], rootDirOverride = rootDir) { - return (await rokuDeploy.getFilePaths(files, rootDirOverride)) + return (await rokuDeploy.getFilePaths({ files: files, rootDir: rootDirOverride })) .sort((a, b) => a.src.localeCompare(b.src)); } @@ -2977,6 +3393,33 @@ describe('RokuDeploy', () => { }]); }); + it('Finds folder using square brackets glob pattern', async () => { + fsExtra.outputFileSync(`${rootDir}/e/file.brs`, ''); + expect(await getFilePaths( + [ + '[test]/*' + ], + rootDir + )).to.eql([{ + src: s`${rootDir}/e/file.brs`, + dest: s`e/file.brs` + }]); + }); + + it('Finds folder with escaped square brackets glob pattern as name', async () => { + fsExtra.outputFileSync(`${rootDir}/[test]/file.brs`, ''); + fsExtra.outputFileSync(`${rootDir}/e/file.brs`, ''); + expect(await getFilePaths( + [ + '\\[test\\]/*' + ], + rootDir + )).to.eql([{ + src: s`${rootDir}/[test]/file.brs`, + dest: s`[test]/file.brs` + }]); + }); + it('throws exception when top-level strings reference files not under rootDir', async () => { writeFiles(otherProjectDir, [ 'manifest' @@ -3197,28 +3640,6 @@ describe('RokuDeploy', () => { }]); }); - it('works for other globs without dest', async () => { - expect(await getFilePaths([{ - src: `components/screen1/*creen1.brs` - }])).to.eql([{ - src: s`${rootDir}/components/screen1/screen1.brs`, - dest: s`screen1.brs` - }]); - }); - - it('skips directory folder names for other globs without dest', async () => { - expect(await getFilePaths([{ - //straight wildcard matches folder names too - src: `components/*` - }])).to.eql([{ - src: s`${rootDir}/components/component1.brs`, - dest: s`component1.brs` - }, { - src: s`${rootDir}/components/component1.xml`, - dest: s`component1.xml` - }]); - }); - it('applies negated patterns', async () => { writeFiles(rootDir, [ 'components/component1.brs', @@ -3246,16 +3667,6 @@ describe('RokuDeploy', () => { }); }); - it('converts relative rootDir path to absolute', async () => { - let stub = sinon.stub(rokuDeploy, 'getOptions').callThrough(); - await getFilePaths([ - 'source/main.brs' - ], './rootDir'); - expect(stub.callCount).to.be.greaterThan(0); - expect(stub.getCall(0).args[0].rootDir).to.eql('./rootDir'); - expect(stub.getCall(0).returnValue.rootDir).to.eql(s`${cwd}/rootDir`); - }); - it('works when using a different current working directory than rootDir', async () => { writeFiles(rootDir, [ 'manifest', @@ -3295,19 +3706,17 @@ describe('RokuDeploy', () => { }); it('supports absolute paths from outside of the rootDir', async () => { - options = rokuDeploy.getOptions(options); - //dest not specified - expect(await rokuDeploy.getFilePaths([{ - src: s`${cwd}/README.md` + expect(await getFilePaths([{ + src: sp`${cwd}/README.md` }], options.rootDir)).to.eql([{ src: s`${cwd}/README.md`, dest: s`README.md` }]); //dest specified - expect(await rokuDeploy.getFilePaths([{ - src: path.join(cwd, 'README.md'), + expect(await getFilePaths([{ + src: sp`${cwd}/README.md`, dest: 'docs/README.md' }], options.rootDir)).to.eql([{ src: s`${cwd}/README.md`, @@ -3316,8 +3725,8 @@ describe('RokuDeploy', () => { let paths: any[]; - paths = await rokuDeploy.getFilePaths([{ - src: s`${cwd}/README.md`, + paths = await getFilePaths([{ + src: sp`${cwd}/README.md`, dest: s`docs/README.md` }], outDir); @@ -3328,8 +3737,8 @@ describe('RokuDeploy', () => { //top-level string paths pointing to files outside the root should thrown an exception await expectThrowsAsync(async () => { - paths = await rokuDeploy.getFilePaths([ - s`${cwd}/README.md` + paths = await getFilePaths([ + sp`${cwd}/README.md` ], outDir); }); }); @@ -3339,8 +3748,8 @@ describe('RokuDeploy', () => { 'README.md' ]); expect( - await rokuDeploy.getFilePaths([{ - src: path.join('..', 'README.md') + await getFilePaths([{ + src: sp`../README.md` }], rootDir) ).to.eql([{ src: s`${rootDir}/../README.md`, @@ -3348,8 +3757,8 @@ describe('RokuDeploy', () => { }]); expect( - await rokuDeploy.getFilePaths([{ - src: path.join('..', 'README.md'), + await getFilePaths([{ + src: sp`../README.md`, dest: 'docs/README.md' }], rootDir) ).to.eql([{ @@ -3363,18 +3772,18 @@ describe('RokuDeploy', () => { '../README.md' ]); await expectThrowsAsync( - rokuDeploy.getFilePaths([ - path.join('..', 'README.md') + getFilePaths([ + path.posix.join('..', 'README.md') ], outDir) ); }); it('supports overriding paths', async () => { - let paths = await rokuDeploy.getFilePaths([{ - src: s`${rootDir}/components/component1.brs`, + let paths = await getFilePaths([{ + src: sp`${rootDir}/components/component1.brs`, dest: 'comp1.brs' }, { - src: s`${rootDir}/components/screen1/screen1.brs`, + src: sp`${rootDir}/components/screen1/screen1.brs`, dest: 'comp1.brs' }], rootDir); expect(paths).to.be.lengthOf(1); @@ -3402,673 +3811,87 @@ describe('RokuDeploy', () => { dest: 'components/MainScene.brs' } ]; - let paths = await rokuDeploy.getFilePaths(files, thisRootDir); + let paths = await getFilePaths(files, thisRootDir); //the MainScene.brs file from source should NOT be included - let mainSceneEntries = paths.filter(x => s`${x.dest}` === s`components/MainScene.brs`); - expect( - mainSceneEntries, - `Should only be one files entry for 'components/MainScene.brs'` - ).to.be.lengthOf(1); - expect(s`${mainSceneEntries[0].src}`).to.eql(s`${thisRootDir}/../.tmp/MainScene.brs`); - } finally { - //clean up - await fsExtra.remove(s`${thisRootDir}/../`); - } - }); - - it('DefaultFiles includes locale directory', async () => { - const projectDir = s`${tempDir}/defaultFilesProject`; - writeFiles(projectDir, [ - 'manifest', - 'source/main.brs', - 'source/lib.brs', - 'components/MainScene.xml', - 'components/MainScene.brs', - 'images/splash_hd.jpg', - 'locale/en_US/translations.xml', - 'locale/es_ES/translations.xml', - // these should NOT be included - 'fonts/custom.ttf', - 'componentLibraries/myLib/myLib.brs', - 'bsconfig.json', - '.vscode/settings.json', - 'node_modules/some-package/index.js' - ]); - expect(await getFilePaths(DefaultFiles, projectDir)).to.eql([ - { src: s`${projectDir}/components/MainScene.brs`, dest: s`components/MainScene.brs` }, - { src: s`${projectDir}/components/MainScene.xml`, dest: s`components/MainScene.xml` }, - { src: s`${projectDir}/images/splash_hd.jpg`, dest: s`images/splash_hd.jpg` }, - { src: s`${projectDir}/locale/en_US/translations.xml`, dest: s`locale/en_US/translations.xml` }, - { src: s`${projectDir}/locale/es_ES/translations.xml`, dest: s`locale/es_ES/translations.xml` }, - { src: s`${projectDir}/manifest`, dest: s`manifest` }, - { src: s`${projectDir}/source/lib.brs`, dest: s`source/lib.brs` }, - { src: s`${projectDir}/source/main.brs`, dest: s`source/main.brs` } - ]); - }); - - describe('asAbsolute', () => { - it('returns relative dest paths by default', async () => { - const paths = await rokuDeploy.getFilePaths(['source/main.brs'], rootDir); - expect(paths).to.eql([{ - src: s`${rootDir}/source/main.brs`, - dest: s`source/main.brs` - }]); - }); - - it('returns absolute dest paths when asAbsolute is true', async () => { - const paths = await rokuDeploy.getFilePaths(['source/main.brs'], rootDir, true, stagingDir); - expect(paths).to.eql([{ - src: s`${rootDir}/source/main.brs`, - dest: s`${stagingDir}/source/main.brs` - }]); - }); - - it('returns relative dest paths when asAbsolute is false', async () => { - const paths = await rokuDeploy.getFilePaths(['source/main.brs'], rootDir, false); - expect(paths).to.eql([{ - src: s`${rootDir}/source/main.brs`, - dest: s`source/main.brs` - }]); - }); - - it('resolves absolute dest against stagingDir for glob patterns', async () => { - const paths = (await rokuDeploy.getFilePaths(['source/**/*'], rootDir, true, stagingDir)) - .sort((a, b) => a.src.localeCompare(b.src)); - expect(paths).to.eql([{ - src: s`${rootDir}/source/lib.brs`, - dest: s`${stagingDir}/source/lib.brs` - }, { - src: s`${rootDir}/source/main.brs`, - dest: s`${stagingDir}/source/main.brs` - }]); - }); - - it('resolves absolute dest against stagingDir for {src;dest} with custom dest', async () => { - const paths = await rokuDeploy.getFilePaths([{ - src: 'source/main.brs', - dest: 'renamed/main.brs' - }], rootDir, true, stagingDir); - expect(paths).to.eql([{ - src: s`${rootDir}/source/main.brs`, - dest: s`${stagingDir}/renamed/main.brs` - }]); - }); - - it('resolves absolute dest for file from outside rootDir when asAbsolute is true', async () => { - writeFiles(otherProjectDir, ['source/thirdPartyLib.brs']); - const paths = await rokuDeploy.getFilePaths([{ - src: `${otherProjectDir}/source/thirdPartyLib.brs`, - dest: 'lib/thirdPartyLib.brs' - }], rootDir, true, stagingDir); - expect(paths).to.eql([{ - src: s`${otherProjectDir}/source/thirdPartyLib.brs`, - dest: s`${stagingDir}/lib/thirdPartyLib.brs` - }]); - }); - - it('last-entry-wins deduplication still applies when asAbsolute is true', async () => { - const paths = await rokuDeploy.getFilePaths([{ - src: `${rootDir}/components/component1.brs`, - dest: 'comp.brs' - }, { - src: `${rootDir}/source/main.brs`, - dest: 'comp.brs' - }], rootDir, true, stagingDir); - expect(paths).to.be.lengthOf(1); - expect(paths[0].src).to.equal(s`${rootDir}/source/main.brs`); - expect(paths[0].dest).to.equal(s`${stagingDir}/comp.brs`); - }); - }); - }); - - describe('computeFileDestPath', () => { - it('treats {src;dest} without dest as a top-level string', () => { - expect( - rokuDeploy['computeFileDestPath'](s`${rootDir}/source/main.brs`, { src: s`source/main.brs` } as any, rootDir) - ).to.eql(s`source/main.brs`); - }); - }); - - describe('zipFolder {src;dest} entries', () => { - it('places file at relative dest path', async () => { - writeFiles(stagingDir, ['source/main.brs']); - const outputZipPath = s`${tempDir}/output.zip`; - await rokuDeploy.zipFolder(stagingDir, outputZipPath, null, [{ - src: s`${stagingDir}/source/main.brs`, - dest: 'source/main.brs' - }]); - const zip = await JSZip.loadAsync(fsExtra.readFileSync(outputZipPath) as any); - expect(zip.file('source/main.brs')).to.exist; - }); - - it('places file at redirected relative dest path', async () => { - writeFiles(stagingDir, ['source/main.brs']); - const outputZipPath = s`${tempDir}/output.zip`; - await rokuDeploy.zipFolder(stagingDir, outputZipPath, null, [{ - src: s`${stagingDir}/source/main.brs`, - dest: 'out/renamed.brs' - }]); - const zip = await JSZip.loadAsync(fsExtra.readFileSync(outputZipPath) as any); - expect(zip.file('out/renamed.brs')).to.exist; - expect(zip.file('source/main.brs')).not.to.exist; - }); - - it('places file from outside srcFolder at specified dest', async () => { - const otherDir = s`${tempDir}/other`; - writeFiles(otherDir, ['lib.brs']); - writeFiles(stagingDir, ['source/main.brs']); - const outputZipPath = s`${tempDir}/output.zip`; - await rokuDeploy.zipFolder(stagingDir, outputZipPath, null, [{ - src: s`${otherDir}/lib.brs`, - dest: 'source/lib.brs' - }]); - const zip = await JSZip.loadAsync(fsExtra.readFileSync(outputZipPath) as any); - expect(zip.file('source/lib.brs')).to.exist; - }); - }); - - describe('zipFolder absolute dest', () => { - it('does not create absolute-path entries in the zip when dest is absolute', async () => { - writeFiles(stagingDir, ['source/main.brs']); - const outputZipPath = s`${tempDir}/output.zip`; - await rokuDeploy.zipFolder(stagingDir, outputZipPath, null, [{ - src: s`${stagingDir}/source/main.brs`, - dest: s`${stagingDir}/source/main.brs` - }]); - const zip = await JSZip.loadAsync(fsExtra.readFileSync(outputZipPath) as any); - const zipPaths = Object.keys(zip.files); - expect(zipPaths.every(p => !path.isAbsolute(p))).to.be.true; - }); - }); - - describe('getDestPath', () => { - it('handles absolute paths properly', () => { - expect( - rokuDeploy.getDestPath( - s`${tempDir}/rootDir/source/main.bs`, - [{ - src: `${tempDir}/rootDir/source/main.bs`, - dest: 'source/standalone.brs' - }], - `${tempDir}/src/lsp/standalone-project-1` - ) - ).to.equal(s`source/standalone.brs`); - }); - - it('handles unrelated exclusions properly', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/components/comp1/comp1.brs`, - [ - '**/*', - '!exclude.me' - ], - rootDir - ) - ).to.equal(s`components/comp1/comp1.brs`); - }); - - it('finds dest path for top-level path', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/components/comp1/comp1.brs`, - ['components/**/*'], - rootDir - ) - ).to.equal(s`components/comp1/comp1.brs`); - }); - - it('does not find dest path for non-matched top-level path', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/source/main.brs`, - ['components/**/*'], - rootDir - ) - ).to.be.undefined; - }); - - it('excludes a file that is negated', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/source/main.brs`, - [ - 'source/**/*', - '!source/main.brs' - ], - rootDir - ) - ).to.be.undefined; - }); - - it('excludes file from non-rootdir top-level pattern', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/../externalDir/source/main.brs`, - [ - '!../externalDir/**/*' - ], - rootDir - ) - ).to.be.undefined; - }); - - it('excludes a file that is negated in src;dest;', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/source/main.brs`, - [ - 'source/**/*', - { - src: '!source/main.brs' - } - ], - rootDir - ) - ).to.be.undefined; - }); - - it('works for brighterscript files', () => { - let destPath = rokuDeploy.getDestPath( - util.standardizePath(`${cwd}/src/source/main.bs`), - [ - 'manifest', - 'source/**/*.bs' - ], - s`${cwd}/src` - ); - expect(s`${destPath}`).to.equal(s`source/main.bs`); - }); - - it('excludes a file found outside the root dir', () => { - expect( - rokuDeploy.getDestPath( - s`${rootDir}/../source/main.brs`, - [ - '../source/**/*' - ], - rootDir - ) - ).to.be.undefined; - }); - }); - - describe('normalizeRootDir', () => { - it('handles falsey values', () => { - expect(rokuDeploy.normalizeRootDir(null)).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir(undefined)).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir('')).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir(' ')).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir('\t')).to.equal(cwd); - }); - - it('handles non-falsey values', () => { - expect(rokuDeploy.normalizeRootDir(cwd)).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir('./')).to.equal(cwd); - expect(rokuDeploy.normalizeRootDir('./testProject')).to.equal(path.join(cwd, 'testProject')); - }); - }); - - describe('retrieveSignedPackage', () => { - let onHandler: any; - beforeEach(() => { - sinon.stub(rokuDeploy.fsExtra, 'ensureDir').callsFake(((pth: string, callback: (err: Error) => void) => { - //do nothing, assume the dir gets created - }) as any); - - //intercept the http request - sinon.stub(request, 'get').callsFake(() => { - let req: any = { - on: (event, callback) => { - process.nextTick(() => { - onHandler(event, callback); - }); - return req; - }, - pipe: async () => { - //if a write stream gets created, write some stuff and close it - const writeStream = await writeStreamPromise; - writeStream.write('test'); - writeStream.close(); - } - }; - return req; - }); - }); - - it('returns a pkg file path on success', async () => { - onHandler = (event, callback) => { - if (event === 'response') { - callback({ - statusCode: 200 - }); - } - }; - let pkgFilePath = await rokuDeploy.retrieveSignedPackage('path_to_pkg', { - outFile: 'roku-deploy-test' - }); - expect(pkgFilePath).to.equal(path.join(process.cwd(), 'out', 'roku-deploy-test.pkg')); - }); - - it('returns a pkg file path on success', async () => { - //the write stream should return null, which causes a specific branch to be executed - createWriteStreamStub.callsFake(() => { - return null; - }); - - onHandler = (event, callback) => { - if (event === 'response') { - callback({ - statusCode: 200 - }); - } - }; - - let error: Error; - try { - await rokuDeploy.retrieveSignedPackage('path_to_pkg', { - outFile: 'roku-deploy-test' - }); - } catch (e) { - error = e as any; - } - expect(error.message.startsWith('Unable to create write stream for')).to.be.true; - }); - - it('throws when error in request is encountered', async () => { - onHandler = (event, callback) => { - if (event === 'error') { - callback(new Error('Some error')); - } - }; - await expectThrowsAsync( - rokuDeploy.retrieveSignedPackage('path_to_pkg', { - outFile: 'roku-deploy-test' - }), - 'Some error' - ); - }); - - it('throws when status code is non 200', async () => { - onHandler = (event, callback) => { - if (event === 'response') { - callback({ - statusCode: 500 - }); - } - }; - await expectThrowsAsync( - rokuDeploy.retrieveSignedPackage('path_to_pkg', { - outFile: 'roku-deploy-test' - }), - 'Invalid response code: 500' - ); - }); - }); - - describe('prepublishToStaging', () => { - it('throws when rootDir is empty after getOptions', async () => { - sinon.stub(rokuDeploy, 'getOptions').returns({ rootDir: '', stagingDir: stagingDir } as any); - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({}), - 'rootDir is required' - ); - }); - - it('is resilient to file system errors', async () => { - let copy = rokuDeploy.fsExtra.copy; - let count = 0; - - //mock writeFile so we can throw a few errors during the test - sinon.stub(rokuDeploy.fsExtra, 'copy').callsFake((...args) => { - count += 1; - //fail a few times - if (count < 5) { - throw new Error('fake error thrown as part of the unit test'); - } else { - return copy.apply(rokuDeploy.fsExtra, args); - } - }); - - //override the retry milliseconds to make test run faster - let orig = util.tryRepeatAsync.bind(util); - sinon.stub(util, 'tryRepeatAsync').callsFake(async (...args) => { - return orig(args[0], args[1], 0); - }); - - fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); - - await rokuDeploy.prepublishToStaging({ - ...options, - files: [ - 'source/main.brs' - ] - }); - expectPathExists(s`${stagingDir}/source/main.brs`); - expect(count).to.be.greaterThan(4); - }); - - it('throws underlying error after the max fs error threshold is reached', async () => { - let copy = rokuDeploy.fsExtra.copy; - let count = 0; - - //mock writeFile so we can throw a few errors during the test - sinon.stub(rokuDeploy.fsExtra, 'copy').callsFake((...args) => { - count += 1; - //fail a few times - if (count < 15) { - throw new Error('fake error thrown as part of the unit test'); - } else { - return copy.apply(rokuDeploy.fsExtra, args); - } - }); - - //override the timeout for tryRepeatAsync so this test runs faster - let orig = util.tryRepeatAsync.bind(util); - sinon.stub(util, 'tryRepeatAsync').callsFake(async (...args) => { - return orig(args[0], args[1], 0); - }); - - fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - files: [ - 'source/main.brs' - ] - }), - 'fake error thrown as part of the unit test' - ); - }); - - describe('resolveFilesArray', () => { - it('copies pre-resolved absolute file entries when resolveFilesArray is false', async () => { - fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [{ - src: s`${rootDir}/source/main.brs`, - dest: s`${stagingDir}/source/main.brs` - }] - } as any); - expectPathExists(s`${stagingDir}/source/main.brs`); - }); - - it('throws when resolveFilesArray is false and src is not absolute', async () => { - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [{ - src: 'source/main.brs', - dest: s`${stagingDir}/source/main.brs` - }] - } as any), - 'When resolveFilesArray is false, all src and dest entries in the files array must be absolute paths' - ); - }); - - it('throws when resolveFilesArray is false and dest is not absolute', async () => { - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [{ - src: s`${rootDir}/source/main.brs`, - dest: 'source/main.brs' - }] - } as any), - 'When resolveFilesArray is false, all src and dest entries in the files array must be absolute paths' - ); - }); - - it('throws when resolveFilesArray is false and an entry is null', async () => { - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [null] - } as any), - 'When resolveFilesArray is false, all src and dest entries in the files array must be absolute paths' - ); - }); - - it('throws when resolveFilesArray is false and an entry has a missing dest', async () => { - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [{ src: s`${rootDir}/source/main.brs`, dest: undefined }] - } as any), - 'When resolveFilesArray is false, all src and dest entries in the files array must be absolute paths' - ); - }); - - it('resolves files array via globbing by default (resolveFilesArray defaults to true)', async () => { - fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - files: ['source/main.brs'] - }); - expectPathExists(s`${stagingDir}/source/main.brs`); - }); - - it('throws when rootDir does not exist', async () => { - const missingRootDir = s`${rootDir}/does-not-exist`; - await expectThrowsAsync( - rokuDeploy.prepublishToStaging({ - rootDir: missingRootDir, - stagingDir: stagingDir, - files: ['source/main.brs'] - }), - `rootDir does not exist at "${missingRootDir}"` - ); - }); - }); - - describe('absolute src+dest file list (end-to-end)', () => { - it('copies multiple files to correct locations in stagingDir', async () => { - writeFiles(rootDir, [ - 'source/main.brs', - 'components/Widget.xml', - 'components/Widget.brs' - ]); - - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [ - { src: s`${rootDir}/source/main.brs`, dest: s`${stagingDir}/source/main.brs` }, - { src: s`${rootDir}/components/Widget.xml`, dest: s`${stagingDir}/components/Widget.xml` }, - { src: s`${rootDir}/components/Widget.brs`, dest: s`${stagingDir}/components/Widget.brs` } - ] - } as any); - - expectPathExists(s`${stagingDir}/source/main.brs`); - expectPathExists(s`${stagingDir}/components/Widget.xml`); - expectPathExists(s`${stagingDir}/components/Widget.brs`); - }); - - it('copies file to a remapped dest path', async () => { - writeFiles(rootDir, ['source/main.brs']); - - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [ - { src: s`${rootDir}/source/main.brs`, dest: s`${stagingDir}/renamed/entry.brs` } - ] - } as any); - - expectPathExists(s`${stagingDir}/renamed/entry.brs`); - expectPathNotExists(s`${stagingDir}/source/main.brs`); - }); - - it('copies files from outside rootDir', async () => { - const externalDir = s`${tempDir}/externalLib`; - writeFiles(externalDir, ['source/thirdParty.brs']); - writeFiles(rootDir, ['source/main.brs']); - - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [ - { src: s`${rootDir}/source/main.brs`, dest: s`${stagingDir}/source/main.brs` }, - { src: s`${externalDir}/source/thirdParty.brs`, dest: s`${stagingDir}/source/thirdParty.brs` } - ] - } as any); - - expectPathExists(s`${stagingDir}/source/main.brs`); - expectPathExists(s`${stagingDir}/source/thirdParty.brs`); - }); - - it('does not crash when two files map to the same dest', async () => { - fsExtra.outputFileSync(`${rootDir}/source/main.brs`, 'original'); - fsExtra.outputFileSync(`${rootDir}/source/override.brs`, 'override'); + let mainSceneEntries = paths.filter(x => s`${x.dest}` === s`components/MainScene.brs`); + expect( + mainSceneEntries, + `Should only be one files entry for 'components/MainScene.brs'` + ).to.be.lengthOf(1); + expect(s`${mainSceneEntries[0].src}`).to.eql(s`${thisRootDir}/../.tmp/MainScene.brs`); + } finally { + //clean up + await fsExtra.remove(s`${thisRootDir}/../`); + } + }); - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [ - { src: s`${rootDir}/source/main.brs`, dest: s`${stagingDir}/source/entry.brs` }, - { src: s`${rootDir}/source/override.brs`, dest: s`${stagingDir}/source/entry.brs` } - ] - } as any); + it('maintains original file path', async () => { + fsExtra.outputFileSync(`${rootDir}/components/CustomButton.brs`, ''); + expect( + await getFilePaths([ + 'components/CustomButton.brs' + ], rootDir) + ).to.eql([{ + src: s`${rootDir}/components/CustomButton.brs`, + dest: s`components/CustomButton.brs` + }]); + }); - expectPathExists(s`${stagingDir}/source/entry.brs`); - expect(fsExtra.readFileSync(s`${stagingDir}/source/entry.brs`, 'utf8')).to.be.oneOf(['original', 'override']); - }); + it('correctly assumes file path if not given', async () => { + fsExtra.outputFileSync(`${rootDir}/components/CustomButton.brs`, ''); + expect( + (await getFilePaths([ + { src: 'components/*' } + ], rootDir)).sort((a, b) => a.src.localeCompare(b.src)) + ).to.eql([{ + src: s`${rootDir}/components/component1.brs`, + dest: s`components/component1.brs` + }, { + src: s`${rootDir}/components/component1.xml`, + dest: s`components/component1.xml` + }, { + src: s`${rootDir}/components/CustomButton.brs`, + dest: s`components/CustomButton.brs` + }]); + }); + }); - it('clears stagingDir before copying', async () => { - // pre-populate stagingDir with a stale file - writeFiles(stagingDir, ['stale/old.brs']); - writeFiles(rootDir, ['source/main.brs']); + describe('parseManifest', () => { + it('correctly parses valid manifest', async () => { + fsExtra.outputFileSync(`${rootDir}/manifest`, `title=AwesomeApp`); + let parsedManifest = await rokuDeploy['parseManifest'](`${rootDir}/manifest`); + expect(parsedManifest.title).to.equal('AwesomeApp'); + }); - await rokuDeploy.prepublishToStaging({ - rootDir: rootDir, - stagingDir: stagingDir, - resolveFilesArray: false, - files: [ - { src: s`${rootDir}/source/main.brs`, dest: s`${stagingDir}/source/main.brs` } - ] - } as any); + it('Throws our error message for a missing file', async () => { + await expectThrowsAsync( + rokuDeploy['parseManifest']('invalid-path'), + `invalid-path does not exist` + ); + }); + }); - expectPathNotExists(s`${stagingDir}/stale/old.brs`); - expectPathExists(s`${stagingDir}/source/main.brs`); - }); + describe('parseManifestFromString', () => { + it('correctly parses valid manifest', () => { + let parsedManifest = rokuDeploy['parseManifestFromString'](` + title=RokuDeployTestChannel + major_version=1 + minor_version=0 + build_version=0 + splash_screen_hd=pkg:/images/splash_hd.jpg + ui_resolutions=hd + bs_const=IS_DEV_BUILD=false + splash_color=#000000 + `); + expect(parsedManifest.title).to.equal('RokuDeployTestChannel'); + expect(parsedManifest.major_version).to.equal('1'); + expect(parsedManifest.minor_version).to.equal('0'); + expect(parsedManifest.build_version).to.equal('0'); + expect(parsedManifest.splash_screen_hd).to.equal('pkg:/images/splash_hd.jpg'); + expect(parsedManifest.ui_resolutions).to.equal('hd'); + expect(parsedManifest.bs_const).to.equal('IS_DEV_BUILD=false'); + expect(parsedManifest.splash_color).to.equal('#000000'); }); }); @@ -4090,236 +3913,76 @@ describe('RokuDeploy', () => { }); }); - describe('getOptions', () => { - it('supports deprecated stagingFolderPath option', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return false; - }); - expect( - rokuDeploy.getOptions({ stagingFolderPath: 'staging-folder-path' }).stagingDir - ).to.eql(s`${cwd}/staging-folder-path`); - expect( - rokuDeploy.getOptions({ stagingFolderPath: 'staging-folder-path', stagingDir: 'staging-dir' }).stagingDir - ).to.eql(s`${cwd}/staging-dir`); - expect( - rokuDeploy.getOptions({ stagingFolderPath: 'staging-folder-path' }).stagingFolderPath - ).to.eql(s`${cwd}/staging-folder-path`); - }); - - it('supports deprecated retainStagingFolder option', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return false; - }); - expect( - rokuDeploy.getOptions({ retainStagingFolder: true }).retainStagingDir - ).to.be.true; - expect( - rokuDeploy.getOptions({ retainStagingFolder: true, retainStagingDir: false }).retainStagingDir - ).to.be.false; - expect( - rokuDeploy.getOptions({ retainStagingFolder: true, retainStagingDir: false }).retainStagingFolder - ).to.be.false; - }); - - it('calling with no parameters works', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return false; - }); - options = rokuDeploy.getOptions(undefined); - expect(options.stagingDir).to.exist; - }); - - it('calling with empty param object', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return false; - }); - options = rokuDeploy.getOptions({}); - expect(options.stagingDir).to.exist; - }); - - it('works when passing in stagingDir', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return false; - }); - options = rokuDeploy.getOptions({ - stagingDir: './staging-dir' - }); - expect(options.stagingDir.endsWith('staging-dir')).to.be.true; - }); + describe('checkRequiredOptions', () => { + async function testRequiredOptions(action: string, requiredOptions: Partial, testedOption: string) { + const newOptions = { ...requiredOptions }; + delete newOptions[testedOption]; + await expectThrowsAsync(async () => { + await rokuDeploy[action](newOptions); + }, `Missing required option: ${testedOption}`); + } - it('works when loading stagingDir from rokudeploy.json', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return true; - }); - sinon.stub(fsExtra, 'readFileSync').returns(` - { - "stagingDir": "./staging-dir" - } - `); - options = rokuDeploy.getOptions(); - expect(options.stagingDir.endsWith('staging-dir')).to.be.true; + it('throws error when sendKeyEvent is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', key: 'up' }; + await testRequiredOptions('sendKeyEvent', requiredOptions, 'host'); + await testRequiredOptions('sendKeyEvent', requiredOptions, 'key'); }); - it('supports jsonc for roku-deploy.json', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return (filePath as string).endsWith('rokudeploy.json'); - }); - sinon.stub(fsExtra, 'readFileSync').returns(` - //leading comment - { - //inner comment - "rootDir": "src" //trailing comment - } - //trailing comment - `); - options = rokuDeploy.getOptions(undefined); - expect(options.rootDir).to.equal(path.join(process.cwd(), 'src')); + it('throws error when sideload is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd' }; + await testRequiredOptions('sideload', requiredOptions, 'host'); + await testRequiredOptions('sideload', requiredOptions, 'password'); }); - it('supports jsonc for bsconfig.json', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return (filePath as string).endsWith('bsconfig.json'); - }); - sinon.stub(fsExtra, 'readFileSync').returns(` - //leading comment - { - //inner comment - "rootDir": "src" //trailing comment - } - //trailing comment - `); - options = rokuDeploy.getOptions(undefined); - expect(options.rootDir).to.equal(path.join(process.cwd(), 'src')); + it('throws error when convertToSquashfs is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd' }; + await testRequiredOptions('convertToSquashfs', requiredOptions, 'host'); + await testRequiredOptions('convertToSquashfs', requiredOptions, 'password'); }); - it('catches invalid json with jsonc parser', () => { - sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { - return (filePath as string).endsWith('bsconfig.json'); - }); - sinon.stub(fsExtra, 'readFileSync').returns(` - { - "rootDir": "src" - `); - let ex; - try { - rokuDeploy.getOptions(undefined); - } catch (e) { - ex = e; - } - expect(ex).to.exist; - expect(ex.message.startsWith('Error parsing')).to.be.true; + it('throws error when rekeyDevice is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd', pkg: 'abcd', signingPassword: 'abcd' }; + await testRequiredOptions('rekeyDevice', requiredOptions, 'host'); + await testRequiredOptions('rekeyDevice', requiredOptions, 'password'); + await testRequiredOptions('rekeyDevice', requiredOptions, 'pkg'); + await testRequiredOptions('rekeyDevice', requiredOptions, 'signingPassword'); }); - it('does not error when no parameter provided', () => { - expect(rokuDeploy.getOptions(undefined)).to.exist; + it('throws error when createSignedPackage is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd', signingPassword: 'abcd' }; + await testRequiredOptions('createSignedPackage', requiredOptions, 'host'); + await testRequiredOptions('createSignedPackage', requiredOptions, 'password'); + await testRequiredOptions('createSignedPackage', requiredOptions, 'signingPassword'); }); - describe('deleteInstalledChannel', () => { - it('defaults to true', () => { - expect(rokuDeploy.getOptions({}).deleteInstalledChannel).to.equal(true); - }); - - it('can be overridden', () => { - expect(rokuDeploy.getOptions({ deleteInstalledChannel: false }).deleteInstalledChannel).to.equal(false); - }); + it('throws error when deleteDevChannel is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd' }; + await testRequiredOptions('deleteDevChannel', requiredOptions, 'host'); + await testRequiredOptions('deleteDevChannel', requiredOptions, 'password'); }); - describe('packagePort', () => { - - it('defaults to 80', () => { - expect(rokuDeploy.getOptions({}).packagePort).to.equal(80); - }); - - it('can be overridden', () => { - expect(rokuDeploy.getOptions({ packagePort: 95 }).packagePort).to.equal(95); - }); - + it('throws error when captureScreenshot is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd' }; + await testRequiredOptions('captureScreenshot', requiredOptions, 'host'); + await testRequiredOptions('captureScreenshot', requiredOptions, 'password'); }); - describe('remotePort', () => { - it('defaults to 8060', () => { - expect(rokuDeploy.getOptions({}).remotePort).to.equal(8060); - }); - - it('can be overridden', () => { - expect(rokuDeploy.getOptions({ remotePort: 1234 }).remotePort).to.equal(1234); - }); + it('throws error when getDeviceInfo is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4' }; + await testRequiredOptions('getDeviceInfo', requiredOptions, 'host'); }); - describe('config file', () => { - beforeEach(() => { - process.chdir(rootDir); - }); - - it('if no config file is available it should use the default values', () => { - expect(rokuDeploy.getOptions().outFile).to.equal('roku-deploy'); - }); - - it('if rokudeploy.json config file is available it should use those values instead of the default', () => { - fsExtra.writeJsonSync(s`${rootDir}/rokudeploy.json`, { outFile: 'rokudeploy-outfile' }); - expect(rokuDeploy.getOptions().outFile).to.equal('rokudeploy-outfile'); - }); - - it('if bsconfig.json config file is available it should use those values instead of the default', () => { - fsExtra.writeJsonSync(`${rootDir}/bsconfig.json`, { outFile: 'bsconfig-outfile' }); - expect(rokuDeploy.getOptions().outFile).to.equal('bsconfig-outfile'); - }); - - it('if rokudeploy.json config file is available and bsconfig.json is also available it should use rokudeploy.json instead of bsconfig.json', () => { - fsExtra.outputJsonSync(`${rootDir}/bsconfig.json`, { outFile: 'bsconfig-outfile' }); - fsExtra.outputJsonSync(`${rootDir}/rokudeploy.json`, { outFile: 'rokudeploy-outfile' }); - expect(rokuDeploy.getOptions().outFile).to.equal('rokudeploy-outfile'); - }); - - it('if runtime options are provided, they should override any existing config file options', () => { - fsExtra.writeJsonSync(`${rootDir}/bsconfig.json`, { outFile: 'bsconfig-outfile' }); - fsExtra.writeJsonSync(`${rootDir}/rokudeploy.json`, { outFile: 'rokudeploy-outfile' }); - expect(rokuDeploy.getOptions({ - outFile: 'runtime-outfile' - }).outFile).to.equal('runtime-outfile'); - }); - - it('if runtime config should override any existing config file options', () => { - fsExtra.writeJsonSync(s`${rootDir}/rokudeploy.json`, { outFile: 'rokudeploy-outfile' }); - fsExtra.writeJsonSync(s`${rootDir}/bsconfig`, { outFile: 'rokudeploy-outfile' }); - - fsExtra.writeJsonSync(s`${rootDir}/brsconfig.json`, { outFile: 'project-config-outfile' }); - options = { - project: 'brsconfig.json' - }; - expect(rokuDeploy.getOptions(options).outFile).to.equal('project-config-outfile'); - }); + it('throws error when getDevId is missing required options', async () => { + const requiredOptions: Partial = { host: '1.2.3.4' }; + await testRequiredOptions('getDevId', requiredOptions, 'host'); }); }); - describe('getToFile', () => { - it('rejects when the write stream emits an error', async () => { - //intercept the http request so it never tries to network - sinon.stub(request, 'get').callsFake(() => { - let req: any = { - on: () => req, - pipe: () => req - }; - return req; - }); - - const finalPromise = rokuDeploy['getToFile']({}, s`${tempDir}/out/something.txt`); - const writeStream = await writeStreamPromise; - writeStream.emit('error', new Error('write stream blew up')); - - let caught: Error | undefined; - try { - await finalPromise; - } catch (e) { - caught = e as Error; - } - expect(caught?.message).to.equal('write stream blew up'); - }); - + describe('downloadFile', () => { it('waits for the write stream to finish writing before resolving', async () => { - let getToFileIsResolved = false; + let downloadFileIsResolved = false; - let requestCalled = defer(); + let requestCalled = defer(); let onResponse = defer<(res) => any>(); //intercept the http request @@ -4339,70 +4002,25 @@ describe('RokuDeploy', () => { return req; }); - const finalPromise = rokuDeploy['getToFile']({}, s`${tempDir}/out/something.txt`).then(() => { - getToFileIsResolved = true; + const finalPromise = rokuDeploy['downloadFile']({}, s`${tempDir}/out/something.txt`).then(() => { + downloadFileIsResolved = true; }); await requestCalled.promise; - expect(getToFileIsResolved).to.be.false; + expect(downloadFileIsResolved).to.be.false; const callback = await onResponse.promise; callback({ statusCode: 200 }); await util.sleep(10); - expect(getToFileIsResolved).to.be.false; + expect(downloadFileIsResolved).to.be.false; const writeStream = await writeStreamPromise; writeStream.write('test'); writeStream.close(); await finalPromise; - expect(getToFileIsResolved).to.be.true; - }); - }); - - describe('deployAndSignPackage', () => { - beforeEach(() => { - //pretend the deploy worked - sinon.stub(rokuDeploy, 'deploy').returns(Promise.resolve(null)); - //pretend the sign worked - sinon.stub(rokuDeploy, 'signExistingPackage').returns(Promise.resolve(null)); - //pretend fetching the signed package worked - sinon.stub(rokuDeploy, 'retrieveSignedPackage').returns(Promise.resolve('some_local_path')); - }); - - it('succeeds and does proper things with staging folder', async () => { - let stub = sinon.stub(rokuDeploy['fsExtra'], 'remove').returns(Promise.resolve() as any); - - //this should not fail - let pkgFilePath = await rokuDeploy.deployAndSignPackage({ - retainStagingDir: false - }); - - //the return value should equal what retrieveSignedPackage returned. - expect(pkgFilePath).to.equal('some_local_path'); - - //fsExtra.remove should have been called - expect(stub.getCalls()).to.be.lengthOf(1); - - //call it again, but specify true for retainStagingDir - await rokuDeploy.deployAndSignPackage({ - retainStagingDir: true - }); - //call count should NOT increase - expect(stub.getCalls()).to.be.lengthOf(1); - - //call it again, but don't specify retainStagingDir at all (it should default to FALSE) - await rokuDeploy.deployAndSignPackage({}); - //call count should NOT increase - expect(stub.getCalls()).to.be.lengthOf(2); - }); - - it('converts to squashfs if we request it to', async () => { - options.convertToSquashfs = true; - let stub = sinon.stub(rokuDeploy, 'convertToSquashfs').returns(Promise.resolve(null)); - await rokuDeploy.deployAndSignPackage(options); - expect(stub.getCalls()).to.be.lengthOf(1); + expect(downloadFileIsResolved).to.be.true; }); }); @@ -4491,38 +4109,38 @@ describe('RokuDeploy', () => { describe('isUpdateRequiredError', () => { it('returns true if the status code is 577', () => { expect( - rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 577 } } }) + rokuDeploy['isUpdateRequiredError']({ details: { httpResponse: { statusCode: 577 } } }) ).to.be.true; }); it('returns true if the body is an update response from device', () => { const response = `\n\n \n \n Roku Development Kit \n\n \n\n\n
\n\n
\n\n \n \n\n\n
\n\n Please make sure that your Roku device is connected to internet, and running most recent software version (d=953108)\n\n
\n\n\n\n`; expect( - rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 500 }, body: response } }) + rokuDeploy['isUpdateRequiredError']({ details: { httpResponse: { statusCode: 500, body: response } } }) ).to.be.true; }); - it('returns false on missing results', () => { + it('returns false on missing details', () => { expect( rokuDeploy['isUpdateRequiredError']({}) ).to.be.false; }); - it('returns false on missing response', () => { + it('returns false on missing httpResponse', () => { expect( - rokuDeploy['isUpdateRequiredError']({ results: {} }) + rokuDeploy['isUpdateRequiredError']({ details: {} }) ).to.be.false; }); it('returns false on missing status code', () => { expect( - rokuDeploy['isUpdateRequiredError']({ results: { response: {} } }) + rokuDeploy['isUpdateRequiredError']({ details: { httpResponse: {} } }) ).to.be.false; }); it('returns false on non-string missing body', () => { expect( - rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 500 }, body: false } }) + rokuDeploy['isUpdateRequiredError']({ details: { httpResponse: { statusCode: 500, body: false } } }) ).to.be.false; }); }); @@ -4531,7 +4149,7 @@ describe('RokuDeploy', () => { it('sends the dcl_enabled qs flag', async () => { const stub = mockDoGetRequest(); sinon.stub(rokuDeploy as any, 'getPackagesFromResponseBody').returns([]); - const result = await rokuDeploy['getInstalledPackages']({} as any); + const result = await rokuDeploy['getInstalledPackages']({ host: 'localhost', password: 'test' } as any); expect(stub.getCall(0).args[0].qs.dcl_enabled).to.eql('1'); expect(result).to.eql([]); }); @@ -4544,7 +4162,7 @@ describe('RokuDeploy', () => { } as any); const stub = mockDoGetRequest(); sinon.stub(rokuDeploy as any, 'getPackagesFromResponseBody').returns([]); - const result = await rokuDeploy['getInstalledPackages']({} as any); + const result = await rokuDeploy['getInstalledPackages']({ host: 'localhost', password: 'test' } as any); expect(stub.getCall(0).args[0].qs).to.eql({ existing: 'value', dcl_enabled: '1' @@ -4556,7 +4174,7 @@ describe('RokuDeploy', () => { const stub = mockDoGetRequest(` var params = JSON.parse('{"messages":null,"metadata":{"dev_id":"12345","dev_key":true,"voice_sdk":false},"packages":[{"appType":"channel","archiveFileName":"roku-deploy.zip","fileType":"zip","id":"0","location":"nvram","md5":"a8d2f9974e2736174c1033b8a7183288","pkgPath":"","size":"2267547"}]}'); `); - const result = await rokuDeploy['getInstalledPackages']({} as any); + const result = await rokuDeploy['getInstalledPackages']({ host: 'localhost', password: 'test' } as any); expect(stub.getCall(0).args[0].qs.dcl_enabled).to.eql('1'); expect(result).to.eql([{ appType: 'channel', @@ -4574,7 +4192,7 @@ describe('RokuDeploy', () => { mockDoGetRequest(` var params = JSON.parse('{"messages":null,"metadata":{"dev_id":"12345","dev_key":true,"voice_sdk":false},"packages": 123}'); `); - const result = await rokuDeploy['getInstalledPackages']({} as any); + const result = await rokuDeploy['getInstalledPackages']({ host: 'localhost', password: 'test' } as any); expect(result).to.eql([]); }); @@ -4582,7 +4200,7 @@ describe('RokuDeploy', () => { mockDoGetRequest(` var params = JSON.parse('123'); `); - const result = await rokuDeploy['getInstalledPackages']({} as any); + const result = await rokuDeploy['getInstalledPackages']({ host: 'localhost', password: 'test' } as any); expect(result).to.eql([]); }); }); @@ -4592,7 +4210,7 @@ describe('RokuDeploy', () => { const stub = mockDoPostRequest(); sinon.stub(rokuDeploy as any, 'generateBaseRequestOptions').returns({} as any); - await rokuDeploy.deleteComponentLibrary({} as any); + await rokuDeploy.deleteComponentLibrary({ host: 'localhost', password: 'test', fileName: 'test.zip' } as any); expect(stub.getCall(0).args[0].qs.dcl_enabled).to.eql('1'); }); @@ -4605,7 +4223,7 @@ describe('RokuDeploy', () => { } as any); const stub = mockDoPostRequest(); - await rokuDeploy.deleteComponentLibrary({} as any); + await rokuDeploy.deleteComponentLibrary({ host: 'localhost', password: 'test', fileName: 'test.zip' } as any); expect(stub.getCall(0).args[0].qs).to.eql({ existing: 'value', @@ -4750,220 +4368,151 @@ describe('RokuDeploy', () => { }); } - async function assertThrowsAsync(fn) { - let f = () => { }; - try { - await fn(); - } catch (e) { - f = () => { - throw e; - }; - } finally { - assert.throws(f); - } - } - - describe('validateDeveloperPassword', () => { - const CHALLENGE_HEADER = 'Digest qop="auth", realm="rokudev", nonce="abc123"'; - - // Minimal Response-like stub — validateDeveloperPassword only reads status + headers.get - function fakeResponse(status: number, headers: Record = {}): any { - const lower: Record = {}; - for (const [k, v] of Object.entries(headers)) { - lower[k.toLowerCase()] = v; - } - return { - status: status, - headers: { - get: (name: string) => lower[name.toLowerCase()] ?? null - } - }; - } - - it('returns true when the device accepts the credentials', async () => { - const fetchStub = sinon.stub(httpClient, 'fetch') - .onFirstCall().resolves(fakeResponse(401, { 'www-authenticate': CHALLENGE_HEADER })) - .onSecondCall().resolves(fakeResponse(200)); - - const result = await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'aaaa' }); - - expect(result).to.be.true; - expect(fetchStub.callCount).to.equal(2); - // Second call carries the computed Authorization header - const secondCallHeaders = (fetchStub.secondCall.args[1] as any).headers; - expect(secondCallHeaders.Authorization).to.match(/^Digest /); - expect(secondCallHeaders.Authorization).to.include('realm="rokudev"'); - expect(secondCallHeaders.Authorization).to.include('nonce="abc123"'); - expect(secondCallHeaders.Authorization).to.include('uri="/plugin_install"'); - }); - - it('returns false when the authenticated retry is rejected', async () => { - sinon.stub(httpClient, 'fetch') - .onFirstCall().resolves(fakeResponse(401, { 'www-authenticate': CHALLENGE_HEADER })) - .onSecondCall().resolves(fakeResponse(401, { 'www-authenticate': CHALLENGE_HEADER })); - - const result = await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'wrong' }); - - expect(result).to.be.false; - }); - - it('throws DeviceUnreachableError when the first request throws', async () => { - sinon.stub(httpClient, 'fetch').rejects(new Error('ECONNREFUSED')); - - let thrown: unknown; - try { - await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'aaaa' }); - } catch (e) { - thrown = e; - } - expect(thrown).to.be.instanceOf(errors.DeviceUnreachableError); - expect((thrown as Error).message).to.include('ECONNREFUSED'); - }); - - it('throws InvalidDeviceResponseCodeError on an unexpected status (e.g. 500)', async () => { - sinon.stub(httpClient, 'fetch').resolves(fakeResponse(500)); - - let thrown: unknown; - try { - await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'aaaa' }); - } catch (e) { - thrown = e; - } - expect(thrown).to.be.instanceOf(errors.InvalidDeviceResponseCodeError); - expect((thrown as Error).message).to.include('500'); - }); - - it('returns false when a 401 has no WWW-Authenticate header', async () => { - sinon.stub(httpClient, 'fetch').resolves(fakeResponse(401)); - - const result = await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'aaaa' }); - - expect(result).to.be.false; - }); - - it('uses default port 80, username rokudev, and plugin_install path', async () => { - const fetchStub = sinon.stub(httpClient, 'fetch') - .onFirstCall().resolves(fakeResponse(401, { 'www-authenticate': CHALLENGE_HEADER })) - .onSecondCall().resolves(fakeResponse(200)); + describe('defaults', () => { + describe('generateBaseRequestOptions', () => { + it('uses default timeout', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test' }); + expect(result.timeout).to.equal(RokuDeploy['defaults'].timeout); + }); - await rokuDeploy.validateDeveloperPassword({ host: 'device.local', password: 'aaaa' }); + it('uses default packagePort', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test' }); + expect(result.url).to.equal(`http://localhost:${RokuDeploy['defaults'].packagePort}/test`); + }); - expect(fetchStub.firstCall.args[0]).to.equal('http://device.local:80/plugin_install'); - const authHeader = (fetchStub.secondCall.args[1] as any).headers.Authorization as string; - expect(authHeader).to.include('username="rokudev"'); - }); + it('uses default username of rokudev', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test' }); + expect(result.auth.user).to.equal('rokudev'); + }); - it('honors custom username and port', async () => { - const fetchStub = sinon.stub(httpClient, 'fetch') - .onFirstCall().resolves(fakeResponse(401, { 'www-authenticate': CHALLENGE_HEADER })) - .onSecondCall().resolves(fakeResponse(200)); + it('allows overriding timeout', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test', timeout: 5000 }); + expect(result.timeout).to.equal(5000); + }); - await rokuDeploy.validateDeveloperPassword({ - host: 'device.local', - password: 'aaaa', - username: 'somebody', - port: 8888 + it('allows overriding packagePort', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test', packagePort: 8080 }); + expect(result.url).to.equal('http://localhost:8080/test'); }); - expect(fetchStub.firstCall.args[0]).to.equal('http://device.local:8888/plugin_install'); - const authHeader = (fetchStub.secondCall.args[1] as any).headers.Authorization as string; - expect(authHeader).to.include('username="somebody"'); + it('allows overriding username', () => { + const result = rokuDeploy['generateBaseRequestOptions']('test', { host: 'localhost', password: 'test', username: 'admin' }); + expect(result.auth.user).to.equal('admin'); + }); }); - it('aborts the request when the timeout elapses', async () => { - sinon.stub(httpClient, 'fetch').callsFake((_url, init?: any) => { - return new Promise((resolve, reject) => { - init?.signal?.addEventListener('abort', () => { - const err: any = new Error('aborted'); - err.name = 'AbortError'; - reject(err); - }); - }); + describe('sendKeyEvent', () => { + it('uses default ecpPort', async () => { + const stub = sinon.stub(rokuDeploy as any, 'doPostRequest').resolves({}); + await rokuDeploy['sendKeyEvent']({ host: 'localhost', key: 'home', action: 'keypress' }); + expect(stub.getCall(0).args[0].url).to.include(`:${RokuDeploy['defaults'].ecpPort}/`); }); - let thrown: unknown; - try { - await rokuDeploy.validateDeveloperPassword({ - host: '1.2.3.4', - password: 'aaaa', - timeout: 20 - }); - } catch (e) { - thrown = e; - } - expect(thrown).to.be.instanceOf(errors.DeviceUnreachableError); - }); - - it('stringifies non-Error fetch rejections', async () => { - sinon.stub(httpClient, 'fetch').callsFake(() => Promise.reject('boom')); + it('uses default timeout', async () => { + const stub = sinon.stub(rokuDeploy as any, 'doPostRequest').resolves({}); + await rokuDeploy['sendKeyEvent']({ host: 'localhost', key: 'home', action: 'keypress' }); + expect(stub.getCall(0).args[0].timeout).to.equal(RokuDeploy['defaults'].timeout); + }); - let thrown: unknown; - try { - await rokuDeploy.validateDeveloperPassword({ host: '1.2.3.4', password: 'aaaa' }); - } catch (e) { - thrown = e; - } - expect(thrown).to.be.instanceOf(errors.DeviceUnreachableError); - expect((thrown as Error).message).to.include('boom'); + it('allows overriding ecpPort', async () => { + const stub = sinon.stub(rokuDeploy as any, 'doPostRequest').resolves({}); + await rokuDeploy['sendKeyEvent']({ host: 'localhost', key: 'home', action: 'keypress', ecpPort: 9000 }); + expect(stub.getCall(0).args[0].url).to.include(':9000/'); + }); }); - }); - describe('parseDigestChallenge', () => { - it('parses quoted values', () => { - const parsed = parseDigestChallenge('Digest realm="rokudev", nonce="abc123", qop="auth"'); - expect(parsed).to.deep.equal({ realm: 'rokudev', nonce: 'abc123', qop: 'auth' }); - }); + describe('getDeviceInfo', () => { + it('uses default ecpPort', async () => { + const stub = sinon.stub(rokuDeploy as any, 'doGetRequest').resolves({ body: '' }); + sinon.stub(util, 'dnsLookup').resolves('localhost'); + try { + await rokuDeploy.getDeviceInfo({ host: 'localhost' }); + } catch (e) { + // ignore parse errors + } + expect(stub.getCall(0).args[0].url).to.include(`:${RokuDeploy['defaults'].ecpPort}/`); + }); - it('parses bare (unquoted) values', () => { - const parsed = parseDigestChallenge('Digest realm="rokudev", algorithm=MD5, stale=false'); - expect(parsed.algorithm).to.equal('MD5'); - expect(parsed.stale).to.equal('false'); + it('uses default timeout', async () => { + const stub = sinon.stub(rokuDeploy as any, 'doGetRequest').resolves({ body: '' }); + sinon.stub(util, 'dnsLookup').resolves('localhost'); + try { + await rokuDeploy.getDeviceInfo({ host: 'localhost' }); + } catch (e) { + // ignore parse errors + } + expect(stub.getCall(0).args[0].timeout).to.equal(RokuDeploy['defaults'].timeout); + }); }); - }); - describe('buildDigestAuthorization', () => { - const baseParams = { - username: 'rokudev', - password: 'aaaa', - method: 'HEAD', - uri: '/plugin_install' - }; + describe('stage', () => { + it('uses default rootDir of ./', async () => { + // stage uses rootDir ?? './' which resolves to cwd + const currentDir = process.cwd(); + writeFiles(currentDir, ['manifest']); + try { + const result = await rokuDeploy.stage({ out: stagingDir }); + // If it doesn't throw, it found the manifest in cwd (default rootDir) + expect(result).to.equal(stagingDir); + } finally { + await fsExtra.remove(`${currentDir}/manifest`); + } + }); - it('uses MD5-SESS HA1 when algorithm=MD5-SESS', () => { - const header = buildDigestAuthorization({ - ...baseParams, - challenge: { realm: 'rokudev', nonce: 'abc', qop: 'auth', algorithm: 'MD5-SESS' } + it('uses default outDir for staging', async () => { + writeFiles(rootDir, ['manifest']); + const result = await rokuDeploy.stage({ rootDir: rootDir }); + expect(result).to.equal(s`${process.cwd()}/${RokuDeploy['defaults'].outDir}/.roku-deploy-staging`); }); - expect(header).to.include('algorithm=MD5-SESS'); }); - it('omits qop/nc/cnonce when the challenge has no qop', () => { - const header = buildDigestAuthorization({ - ...baseParams, - challenge: { realm: 'rokudev', nonce: 'abc' } + describe('zip', () => { + it('uses default outDir and outFile for zip path', async () => { + writeFiles(stagingDir, ['manifest']); + const zipSpy = sinon.spy(rokuDeploy as any, 'makeZip'); + await rokuDeploy.zip({ dir: stagingDir }); + const outPath = zipSpy.getCall(0).args[1]; + expect(outPath).to.equal(s`${process.cwd()}/${RokuDeploy['defaults'].outDir}/${RokuDeploy['defaults'].outFile}`); }); - expect(header).to.not.include('qop='); - expect(header).to.not.include('nc='); - expect(header).to.not.include('cnonce='); }); - it('includes opaque when present in the challenge', () => { - const header = buildDigestAuthorization({ - ...baseParams, - challenge: { realm: 'rokudev', nonce: 'abc', qop: 'auth', opaque: 'xyz' } + describe('sideload', () => { + it('uses default deleteDevChannel of true', async () => { + const deleteStub = sinon.stub(rokuDeploy, 'deleteDevChannel').resolves(); + sinon.stub(rokuDeploy, 'closeChannel').resolves(); + sinon.stub(fsExtra, 'pathExists').resolves(true); + sinon.stub(fsExtra, 'createReadStream').returns({ on: (event, cb) => cb() } as any); + mockDoPostRequest('success'); + + await rokuDeploy.sideload({ host: 'localhost', password: 'test', zip: 'test.zip' }); + expect(deleteStub.called).to.be.true; }); - expect(header).to.include('opaque="xyz"'); - }); - it('defaults missing realm and nonce to empty strings', () => { - const header = buildDigestAuthorization({ - ...baseParams, - challenge: { qop: 'auth' } + it('allows disabling deleteDevChannel', async () => { + const deleteStub = sinon.stub(rokuDeploy, 'deleteDevChannel').resolves(); + sinon.stub(rokuDeploy, 'closeChannel').resolves(); + sinon.stub(fsExtra, 'pathExists').resolves(true); + sinon.stub(fsExtra, 'createReadStream').returns({ on: (event, cb) => cb() } as any); + mockDoPostRequest('success'); + + await rokuDeploy.sideload({ host: 'localhost', password: 'test', zip: 'test.zip', deleteDevChannel: false }); + expect(deleteStub.called).to.be.false; }); - expect(header).to.include('realm=""'); - expect(header).to.include('nonce=""'); }); }); + + async function assertThrowsAsync(fn) { + let f = () => { }; + try { + await fn(); + } catch (e) { + f = () => { + throw e; + }; + } finally { + assert.throws(f); + } + } }); function getFakeResponseBody(messages: string): string { diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index ad6967db..d58d2327 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -1,246 +1,171 @@ import * as path from 'path'; -import * as _fsExtra from 'fs-extra'; +import * as fsExtra from 'fs-extra'; +import type { WriteStream, ReadStream } from 'fs-extra'; import * as r from 'postman-request'; import type * as requestType from 'request'; -const request = r as typeof requestType; +const request = r; import * as JSZip from 'jszip'; -import * as dateformat from 'dateformat'; import * as errors from './Errors'; -import * as isGlob from 'is-glob'; -import * as picomatch from 'picomatch'; +import { extractHttpResponseDetails } from './Errors'; import * as xml2js from 'xml2js'; -import type { ParseError } from 'jsonc-parser'; -import { parse as parseJsonc, printParseErrorCode } from 'jsonc-parser'; +import { parse as parseJsonc } from 'jsonc-parser'; import { util } from './util'; -import type { RokuDeployOptions, FileEntry } from './RokuDeployOptions'; -import { Logger, LogLevel } from './Logger'; -import * as tempDir from 'temp-dir'; +import type { FileEntry } from './RokuDeployOptions'; +import { logger, type LogLevel } from '@rokucommunity/logger'; import * as dayjs from 'dayjs'; import * as lodash from 'lodash'; import type { DeviceInfo, DeviceInfoRaw } from './DeviceInfo'; +import * as tempDir from 'temp-dir'; import * as semver from 'semver'; -import { fetchWithDigest } from './fetch'; export class RokuDeploy { - - constructor() { - this.logger = new Logger(); - } - - private logger: Logger; - - //store the import on the class to make testing easier - public fsExtra = _fsExtra; - - public screenshotDir = path.join(tempDir, '/roku-deploy/screenshots/'); + /** + * Default values for common options used across multiple functions + */ + private static readonly defaults = { + timeout: 150000, + packagePort: 80, + ecpPort: 8060, + outDir: './out', + outFile: 'roku-deploy.zip' + }; /** * Copies all of the referenced files to the staging folder * @param options */ - public async prepublishToStaging(options: RokuDeployOptions & { resolveFilesArray?: boolean }) { - options = this.getOptions(options) as any; - options.resolveFilesArray ??= true; - - if (!options.rootDir) { - throw new Error('rootDir is required'); - } - if (!await this.fsExtra.pathExists(options.rootDir)) { - throw new Error(`rootDir does not exist at "${options.rootDir}"`); - } + public async stage(options: StageOptions) { + logger.info('Beginning to copy files to staging folder'); + const cwd = options.cwd ?? process.cwd(); - //clean the staging directory - await this.fsExtra.emptyDir(options.stagingDir); + // Set defaults and resolve paths + const rootDir = path.resolve(cwd, options.rootDir ?? './'); + const files = options.files ?? [...DefaultFiles]; - let fileObjects: StandardizedFileEntry[]; - //if we are supposed to resolve the files array, do it - if (options.resolveFilesArray) { - fileObjects = await this.getFilePaths(options.files, options.rootDir); + // Resolve output directory - use 'out' if provided, otherwise default to staging dir + const out = options.out + ? path.resolve(cwd, options.out) + : path.resolve(cwd, RokuDeploy.defaults.outDir, '.roku-deploy-staging'); - //do not resolve the files array. (user likely build the list themselves earlier and wants to skip re-globbing the whole list) - } else { - this.ensureFilesArrayIsResolved(options.files); - fileObjects = options.files as StandardizedFileEntry[]; - } + //clean the staging directory + await fsExtra.remove(out); - await this.copyToStaging(fileObjects, options.stagingDir); - return options.stagingDir; - } + //make sure the staging folder exists + await fsExtra.ensureDir(out); - /** - * Ensures that all entries in the files array are in the form of {src:string, dest:string}. - * - * Assumes all src and dest entries are absolute paths, but may add additional checks or relax this in the future. - * - * Will throw an exception on the first occurance that was not in the correct format - * @param files - */ - private ensureFilesArrayIsResolved(files: FileEntry[]) { - //ensure the is no glob magic in any of the patterns - for (let fileEntry of files as StandardizedFileEntry[]) { - if (!fileEntry || typeof fileEntry.src !== 'string' || !path.isAbsolute(fileEntry.src) || typeof fileEntry.dest !== 'string' || !path.isAbsolute(fileEntry.dest)) { - throw new Error('When resolveFilesArray is false, all src and dest entries in the files array must be absolute paths'); - } + if (!await fsExtra.pathExists(rootDir)) { + throw new Error(`rootDir does not exist at "${rootDir}"`); } - return true; - } - /** - * Given an array of `FilesType`, normalize them each into a `StandardizedFileEntry`. - * Each entry in the array or inner `src` array will be extracted out into its own object. - * This makes it easier to reason about later on in the process. - * @param files - */ - public normalizeFilesArray(files: FileEntry[]) { - const result: Array = []; - - for (let i = 0; i < files.length; i++) { - let entry = files[i]; - //skip falsey and blank entries - if (!entry) { - continue; - - //string entries - } else if (typeof entry === 'string') { - result.push(this.standardizeSrcPattern(entry)); - - //objects with src: (string | string[]) - } else if ('src' in entry) { - //validate dest - if (entry.dest !== undefined && entry.dest !== null && typeof entry.dest !== 'string') { - throw new Error(`Invalid type for "dest" at index ${i} of files array`); - } - - //objects with src: string - if (typeof entry.src === 'string') { - result.push({ - src: this.standardizeSrcPattern(entry.src), - dest: util.standardizePath(entry.dest) - }); - - //objects with src:string[] - } else if ('src' in entry && Array.isArray(entry.src)) { - //create a distinct entry for each item in the src array - for (let srcEntry of entry.src) { - result.push({ - src: this.standardizeSrcPattern(srcEntry), - dest: util.standardizePath(entry.dest) - }); - } - } else { - throw new Error(`Invalid type for "src" at index ${i} of files array`); - } - } else { - throw new Error(`Invalid entry at index ${i} in files array`); - } - } + let fileObjects = await this.getFilePaths({ files: files, rootDir: rootDir }); + //copy all of the files + await Promise.all(fileObjects.map(async (fileObject) => { + let destFilePath = util.standardizePath(`${out}/${fileObject.dest}`); - return result; - } + //make sure the containing folder exists + await fsExtra.ensureDir(path.dirname(destFilePath)); - /** - * Standardize a glob `src` pattern from the `files` array. Mirrors - * `util.standardizePath` (slash + parent-dir normalization) but preserves a - * leading `!` glob-negation prefix that `path.normalize` would otherwise - * consume — it treats `!..` as a regular path segment, and a subsequent - * `..` then resolves through it, dropping both the `!` and one `..` - * (e.g. `!../../X` -> `X`). - */ - private standardizeSrcPattern(pattern: string) { - const isNegated = pattern.startsWith('!'); - const stripped = isNegated ? pattern.slice(1) : pattern; - const normalized = util.standardizePath(stripped); - return isNegated ? '!' + normalized : normalized; + //sometimes the copyfile action fails due to race conditions (normally to poorly constructed src;dest; objects with duplicate files in them + await util.tryRepeatAsync(async () => { + //copy the src item using the filesystem + await fsExtra.copy(fileObject.src, destFilePath, { + //copy the actual files that symlinks point to, not the symlinks themselves + dereference: true + }); + }, 10); + })); + logger.info('Relevant files copied to:', out); + return out; } /** * Given an already-populated staging folder, create a zip archive of it and copy it to the output folder * @param options */ - public async zipPackage(options: RokuDeployOptions) { - options = this.getOptions(options); + public async zip(options: ZipOptions) { + logger.info('Beginning to zip'); + const cwd = options.cwd ?? process.cwd(); + + // dir is required + if (!options.dir) { + throw new Error('"dir" is required for zip'); + } - //make sure the output folder exists - await this.fsExtra.ensureDir(options.outDir); + const dir = path.resolve(cwd, options.dir); - let zipFilePath = this.getOutputZipFilePath(options); + // Resolve output zip path - use 'out' if provided, otherwise default + let out = options.out + ? path.resolve(cwd, options.out) + : path.resolve(cwd, RokuDeploy.defaults.outDir, RokuDeploy.defaults.outFile); - //ensure the manifest file exists in the staging folder - if (!await util.fileExistsCaseInsensitive(`${options.stagingDir}/manifest`)) { - throw new Error(`Cannot zip package: missing manifest file in "${options.stagingDir}"`); + // Ensure .zip extension + if (!out.toLowerCase().endsWith('.zip')) { + out += '.zip'; } - //create a zip of the staging folder - await this.zipFolder(options.stagingDir, zipFilePath); + // Get files to include - use provided files array or default to everything + const files = options.files ?? ['**/*']; - //delete the staging folder unless told to retain it. - if (options.retainStagingDir !== true) { - await this.fsExtra.remove(options.stagingDir); + // Check that manifest will be included + const filePaths = await this.getFilePaths({ files: files, rootDir: dir }); + const hasManifest = filePaths.some(f => f.dest.toLowerCase() === 'manifest'); + if (!hasManifest) { + throw new Error(`Cannot zip package: missing manifest file in "${dir}"`); } + + await this.makeZip(dir, out, files); + logger.info('Zip created at:', out); } /** - * Create a zip folder containing all of the specified roku project files. - * @param options + * Given a path to a folder, zip up that folder and all of its contents + * @param srcFolder the folder that should be zipped + * @param zipFilePath the path to the zip that will be created + * @param files a files array used to filter the files from `srcFolder` */ - public async createPackage(options: RokuDeployOptions, beforeZipCallback?: (info: BeforeZipCallbackInfo) => Promise | void) { - options = this.getOptions(options); - - await this.prepublishToStaging(options); + private async makeZip(srcFolder: string, zipFilePath: string, files: FileEntry[] = ['**/*']) { + const filePaths = await this.getFilePaths({ files: files, rootDir: srcFolder }); - let manifestPath = util.standardizePath(`${options.stagingDir}/manifest`); - let parsedManifest = await this.parseManifest(manifestPath); - - if (options.incrementBuildNumber) { - let timestamp = dateformat(new Date(), 'yymmddHHMM'); - parsedManifest.build_version = timestamp; //eslint-disable-line camelcase - await this.fsExtra.outputFile(manifestPath, this.stringifyManifest(parsedManifest)); - } - - if (beforeZipCallback) { - let info: BeforeZipCallbackInfo = { - manifestData: parsedManifest, - stagingFolderPath: options.stagingDir, - stagingDir: options.stagingDir - }; + const zip = new JSZip(); + // Allows us to wait until all are done before we build the zip + const promises = []; + for (const file of filePaths) { + const promise = fsExtra.readFile(file.src).then((data) => { + const ext = path.extname(file.dest).toLowerCase(); + let compression: 'DEFLATE' | 'STORE' = 'DEFLATE'; - await Promise.resolve(beforeZipCallback(info)); + if (ext === '.jpg' || ext === '.png' || ext === '.jpeg') { + compression = 'STORE'; + } + zip.file(file.dest.replace(/[\\/]/g, '/'), data as Uint8Array, { + compression: compression + }); + }); + promises.push(promise); } - await this.zipPackage(options); - } + await Promise.all(promises); - /** - * Given a root directory, normalize it to a full path. - * Fall back to cwd if not specified - * @param rootDir - */ - public normalizeRootDir(rootDir: string) { - if (!rootDir || (typeof rootDir === 'string' && rootDir.trim().length === 0)) { - return process.cwd(); - } else { - return path.resolve(rootDir); - } + //ensure the output directory exists + await fsExtra.ensureDir( + path.dirname(zipFilePath) + ); + // level 2 compression seems to be the best balance between speed and file size. Speed matters more since most will be calling squashfs afterwards. + const content = await zip.generateAsync({ type: 'nodebuffer', compressionOptions: { level: 2 } }); + return fsExtra.writeFile(zipFilePath, content); } /** * Get all file paths for the specified options - * @param files - * @param rootFolderPath - the absolute path to the root dir where relative files entries are relative to - * @param asAbsolute - if true, all returned file paths will be absolute. If false, all returned dest paths will be relative (default: false) - * @param stagingDir - the absolute path to the staging dir, used for computing absolute dest paths if `asAbsolute` is true */ - public async getFilePaths(files: FileEntry[], rootDir: string, asAbsolute = false, stagingDir?: string): Promise { - const options = this.getOptions({ - rootDir: rootDir, - stagingDir: stagingDir - }); - //if the rootDir isn't absolute, convert it to absolute using the standard options flow + public async getFilePaths(options: GetFilePathsOptions): Promise { + let rootDir = options.rootDir; + const files = options.files; + + //if the rootDir isn't absolute, convert it to absolute if (path.isAbsolute(rootDir) === false) { - rootDir = options.rootDir; + rootDir = path.resolve(process.cwd(), rootDir); } - stagingDir = options.stagingDir; - - const entries = this.normalizeFilesArray(files); + const entries = util.normalizeFilesArray(files); const srcPathsByIndex = await util.globAllByIndex( entries.map(x => { return typeof x === 'string' ? x : x.src; @@ -261,14 +186,11 @@ export class RokuDeploy { for (let srcPath of srcPaths) { srcPath = util.standardizePath(srcPath); - let dest = this.computeFileDestPath(srcPath, entry, rootDir); - + const dest = util.computeFileDestPath(srcPath, entry, rootDir); //the last file with this `dest` will win, so just replace any existing entry with this one. result.set(dest, { src: srcPath, - dest: asAbsolute - ? util.standardizePath(path.resolve(stagingDir, dest)) - : dest + dest: dest }); } } @@ -276,168 +198,18 @@ export class RokuDeploy { return [...result.values()]; } - /** - * Given a full path to a file, determine its dest path - * @param srcPath the absolute path to the file. This MUST be a file path, and it is not verified to exist on the filesystem - * @param files the files array - * @param rootDir the absolute path to the root dir - * @param skipMatch - skip running the minimatch process (i.e. assume the file is a match - * @returns the RELATIVE path to the dest location for the file. - */ - public getDestPath(srcPathAbsolute: string, files: FileEntry[], rootDir: string, skipMatch = false) { - srcPathAbsolute = util.standardizePath(srcPathAbsolute); - rootDir = rootDir.replace(/\\+/g, '/'); - const entries = this.normalizeFilesArray(files); - - function makeGlobAbsolute(pattern: string) { - return path.resolve( - rootDir, - //remove leading exclamation point if pattern is negated - pattern - //coerce all slashes to forward - ).replace(/\\+/g, '/'); - } - - let result: string; - - //add the file into every matching cache bucket - for (let entry of entries) { - const pattern = (typeof entry === 'string' ? entry : entry.src); - //filter previous paths - if (pattern.startsWith('!')) { - const keepFile = picomatch('!' + makeGlobAbsolute(pattern.replace(/^!/, ''))); - if (!keepFile(srcPathAbsolute)) { - result = undefined; - } - } else { - const keepFile = picomatch(makeGlobAbsolute(pattern)); - if (keepFile(srcPathAbsolute)) { - try { - result = this.computeFileDestPath( - srcPathAbsolute, - entry, - util.standardizePath(rootDir) - ); - } catch { - //ignore errors...the file just has no dest path - } - } - } - } - return result; - } - - /** - * Compute the `dest` path. This accounts for magic globstars in the pattern, - * as well as relative paths based on the dest. This is only used internally. - * @param src an absolute, normalized path for a file - * @param dest the `dest` entry for this file. If omitted, files will derive their paths relative to rootDir. - * @param pattern the glob pattern originally used to find this file - * @param rootDir absolute normalized path to the rootDir - */ - private computeFileDestPath(srcPath: string, entry: string | StandardizedFileEntry, rootDir: string) { - let result: string; - let globstarIdx: number; - //files under rootDir with no specified dest - if (typeof entry === 'string') { - if (util.isParentOfPath(rootDir, srcPath, false)) { - //files that are actually relative to rootDir - result = util.stringReplaceInsensitive(srcPath, rootDir, ''); - } else { - // result = util.stringReplaceInsensitive(srcPath, rootDir, ''); - throw new Error('Cannot reference a file outside of rootDir when using a top-level string. Please use a src;des; object instead'); - } - - //non-glob-pattern explicit file reference - } else if (!isGlob(entry.src.replace(/\\/g, '/'), { strict: false })) { - let isEntrySrcAbsolute = path.isAbsolute(entry.src); - let entrySrcPathAbsolute = isEntrySrcAbsolute ? entry.src : util.standardizePath(`${rootDir}/${entry.src}`); - - let isSrcChildOfRootDir = util.isParentOfPath(rootDir, entrySrcPathAbsolute, false); - - let fileNameAndExtension = path.basename(entrySrcPathAbsolute); - - //no dest - if (entry.dest === null || entry.dest === undefined) { - //no dest, absolute path or file outside of rootDir - if (isEntrySrcAbsolute || isSrcChildOfRootDir === false) { - //copy file to root of staging folder - result = fileNameAndExtension; - - //no dest, relative path, lives INSIDE rootDir - } else { - //copy relative file structure to root of staging folder - let srcPathRelative = util.stringReplaceInsensitive(entrySrcPathAbsolute, rootDir, ''); - result = srcPathRelative; - } - - //assume entry.dest is the relative path to the folder AND file if applicable - } else if (entry.dest === '') { - result = fileNameAndExtension; - } else { - result = entry.dest; - } - //has a globstar - } else if ((globstarIdx = entry.src.indexOf('**')) > -1) { - const rootGlobstarPath = path.resolve(rootDir, entry.src.substring(0, globstarIdx)) + path.sep; - const srcPathRelative = util.stringReplaceInsensitive(srcPath, rootGlobstarPath, ''); - if (entry.dest) { - result = `${entry.dest}/${srcPathRelative}`; - } else { - result = srcPathRelative; - } - - //`pattern` is some other glob magic - } else { - const fileNameAndExtension = path.basename(srcPath); - result = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); - } - - result = util.standardizePath( - //remove leading slashes - result.replace(/^[\/\\]+/, '') - ); - return result; - } - - /** - * Copy all of the files to the staging directory - * @param fileGlobs - * @param stagingPath - */ - private async copyToStaging(files: StandardizedFileEntry[], stagingDir: string) { - if (!stagingDir) { - throw new Error('stagingPath is required'); - } - - //copy all of the files - await Promise.all(files.map(async (fileObject) => { - let destFilePath = util.standardizePath( - path.resolve(stagingDir, fileObject.dest ?? '') - ); - - //make sure the containing folder exists - await this.fsExtra.ensureDir(path.dirname(destFilePath)); - - //sometimes the copyfile action fails due to race conditions (normally to poorly constructed src;dest; objects with duplicate files in them - await util.tryRepeatAsync(async () => { - //copy the src item using the filesystem - await this.fsExtra.copy(fileObject.src, destFilePath, { - //copy the actual files that symlinks point to, not the symlinks themselves - dereference: true - }); - }, 10); - })); - } + private generateBaseRequestOptions(requestPath: string, options: BaseRequestOptions, formData = {} as T): requestType.OptionsWithUrl { + // Set defaults for request options + const packagePort = options.packagePort ?? RokuDeploy.defaults.packagePort; + const timeout = options.timeout ?? RokuDeploy.defaults.timeout; + const username = options.username ?? 'rokudev'; - private generateBaseRequestOptions(requestPath: string, options: RokuDeployOptions, formData = {} as T): requestType.OptionsWithUrl { - options = this.getOptions(options); - let url = `http://${options.host}:${options.packagePort}/${requestPath}`; + let url = `http://${options.host}:${packagePort}/${requestPath}`; let baseRequestOptions = { url: url, - timeout: options.timeout, + timeout: timeout, auth: { - user: options.username, + user: username, pass: options.password, sendImmediately: false }, @@ -447,50 +219,120 @@ export class RokuDeploy { return baseRequestOptions; } + public async keyPress(options: KeyPressOptions) { + return this.sendKeyEvent({ + ...options, + key: options.key, + action: 'keypress' + }); + } + + public async keyUp(options: KeyUpOptions) { + return this.sendKeyEvent({ + ...options, + action: 'keyup' + }); + } + + public async keyDown(options: KeyDownOptions) { + return this.sendKeyEvent({ + ...options, + action: 'keydown' + }); + } + + public async sendText(options: SendTextOptions) { + this.checkRequiredOptions(options, ['host', 'text']); + const chars = options.text.split(''); + for (const char of chars) { + await this.sendKeyEvent({ + ...options, + key: `lit_${encodeURIComponent(char)}`, + action: 'keypress' + }); + } + } + /** * Simulate pressing the home button on the remote for this roku. * This makes the roku return to the home screen - * @param host - the host - * @param port - the port that should be used for the request. defaults to 8060 - * @param timeout - request timeout duration in milliseconds. defaults to 150000 */ - public async pressHomeButton(host, port?: number, timeout?: number) { - let options = this.getOptions(); - port = port ? port : options.remotePort; - timeout = timeout ? timeout : options.timeout; + private async sendKeyEvent(options: SendKeyEventOptions) { + logger.info('Sending key event:', options.key); + this.checkRequiredOptions(options, ['host', 'key']); + // Set defaults + const ecpPort = options.ecpPort ?? RokuDeploy.defaults.ecpPort; + const timeout = options.timeout ?? RokuDeploy.defaults.timeout; // press the home button to return to the main screen return this.doPostRequest({ - url: `http://${host}:${port}/keypress/Home`, + url: `http://${options.host}:${ecpPort}/${options.action}/${options.key}`, timeout: timeout }, false); } + public async closeChannel(options: CloseChannelOptions) { + // TODO: After 13.0 releases, add check for ECP close-app support, and use that twice to kill instant resume if available + await this.sendKeyEvent({ + ...options, + action: 'keypress', + key: 'home' + }); + } + /** - * Publish a pre-existing packaged zip file to a remote Roku. + * Sideload a zip to a remote Roku. Either `zip` (path to a pre-built zip) or `dir` (directory + * to zip on-the-fly) must be provided. * @param options */ - public async publish(options: RokuDeployOptions): Promise<{ message: string; results: any }> { - options = this.getOptions(options); - if (!options.host) { - throw new errors.MissingRequiredOptionError('must specify the host for the Roku device'); + public async sideload(options: SideloadOptions): Promise<{ message: string; results: any }> { + logger.info('Beginning to sideload package'); + this.checkRequiredOptions(options, ['host', 'password']); + + const cwd = options.cwd ?? process.cwd(); + // Set defaults + const deleteDevChannel = options.deleteDevChannel ?? true; + const failOnCompileError = options.failOnCompileError ?? true; + + let zipFilePath: string; + let deleteZipAfterSideload = false; + + // Close the channel before sideloading unless explicitly disabled + if (options.close !== false) { + await this.closeChannel(options as CloseChannelOptions); + } + + // Determine the zip file path based on whether zip or dir was provided + if ('zip' in options && options.zip) { + zipFilePath = path.resolve(cwd, options.zip); + } else if ('dir' in options && options.dir) { + // Generate zip from directory to a temp location + zipFilePath = path.resolve(cwd, RokuDeploy.defaults.outDir, RokuDeploy.defaults.outFile); + await this.zip({ dir: path.resolve(cwd, options.dir), out: zipFilePath, cwd: cwd }); + deleteZipAfterSideload = true; + } else { + throw new Error('Either zip or dir must be provided'); } - //make sure the outDir exists - await this.fsExtra.ensureDir(options.outDir); - let zipFilePath = this.getOutputZipFilePath(options); - let readStream: _fsExtra.ReadStream; + if (deleteDevChannel) { + try { + await this.deleteDevChannel(options); + } catch (e) { + // note we don't report the error; as we don't actually care that we could not deploy - it's just useless noise to log it. + } + } + + let readStream: ReadStream; try { - if ((await this.fsExtra.pathExists(zipFilePath)) === false) { - throw new Error(`Cannot publish because file does not exist at '${zipFilePath}'`); + if ((await fsExtra.pathExists(zipFilePath)) === false) { + throw new Error(`Cannot sideload because file does not exist at '${zipFilePath}'`); } - readStream = this.fsExtra.createReadStream(zipFilePath); + readStream = fsExtra.createReadStream(zipFilePath); //wait for the stream to open (no harm in doing this, and it helps solve an issue in the tests) await new Promise((resolve) => { readStream.on('open', resolve); }); const route = options.packageUploadOverrides?.route ?? 'plugin_install'; - let requestOptions = this.generateBaseRequestOptions(route, options, { mysubmit: 'Replace', archive: readStream, @@ -508,12 +350,6 @@ export class RokuDeploy { requestOptions.formData.remotedebug_connect_early = '1'; } - //disable auto-launching the channel after install if explicitly opted out - if (options.autoLaunch === false) { - // eslint-disable-next-line camelcase - requestOptions.formData.dev_autolaunch = '0'; - } - //apply any supplied formData overrides for (const key in options.packageUploadOverrides?.formData ?? {}) { const value = options.packageUploadOverrides.formData[key]; @@ -531,8 +367,13 @@ export class RokuDeploy { response = await this.doPostRequest(requestOptions); } catch (replaceError: any) { //fail if this is a compile error - if (this.isCompileError(replaceError.message) && options.failOnCompileError) { - throw new errors.CompileError('Compile error', replaceError, replaceError.results); + if (this.isCompileError(replaceError.message) && failOnCompileError) { + const rokuMessages = this.getRokuMessagesFromResponseBody(replaceError.results?.body ?? ''); + throw new errors.CompileError('Compile error', { + httpResponse: extractHttpResponseDetails(replaceError.results?.response, replaceError.results?.body), + rokuMessages: rokuMessages, + host: options.host + }, replaceError); } else if (this.isUpdateRequiredError(replaceError)) { throw replaceError; } else { @@ -543,11 +384,17 @@ export class RokuDeploy { } catch (e: any) { //if this is a 577 error, we have high confidence that the device needs to do an update check if (this.isUpdateRequiredError(e)) { - throw new errors.UpdateCheckRequiredError(response, requestOptions, e); + throw new errors.UpdateCheckRequiredError({ + url: requestOptions.url as string, + host: options.host + }, e); //a reset connection could be cause by several things, but most likely it's due to the device needing to check for updates } else if (e.code === 'ECONNRESET') { - throw new errors.ConnectionResetError(e, requestOptions); + throw new errors.ConnectionResetError({ + url: requestOptions.url as string, + host: options.host + }, e); } else { throw e; } @@ -555,29 +402,38 @@ export class RokuDeploy { //if we got a non-error status code, but the body includes a message about needing to update, throw a special error if (this.isUpdateCheckRequiredResponse(response.body)) { - throw new errors.UpdateCheckRequiredError(response, requestOptions); + throw new errors.UpdateCheckRequiredError({ + url: requestOptions.url as string, + host: options.host + }); } - if (options.failOnCompileError) { + if (failOnCompileError) { if (this.isCompileError(response.body)) { - throw new errors.CompileError('Compile error', response, this.getRokuMessagesFromResponseBody(response.body)); + const rokuMessages = this.getRokuMessagesFromResponseBody(response.body); + throw new errors.CompileError('Compile error', { + httpResponse: extractHttpResponseDetails(response.response, response.body), + rokuMessages: rokuMessages, + host: options.host + }); } } if (response.body.indexOf('Identical to previous version -- not replacing.') > -1) { return { message: 'Identical to previous version -- not replacing', results: response }; } - return { message: 'Successful deploy', results: response }; + logger.info('Successful sideload'); + return { message: 'Successful sideload', results: response }; } finally { - //delete the zip file only if configured to do so - if (options.retainDeploymentArchive === false) { - await this.fsExtra.remove(zipFilePath); + //delete the zip file if we generated it from rootDir + if (deleteZipAfterSideload) { + await fsExtra.remove(zipFilePath); } //try to close the read stream to prevent files becoming locked try { readStream?.close(); } catch (e) { - this.logger.info('Error closing read stream', e); + logger.warn('Error closing read stream', e); } } } @@ -600,18 +456,18 @@ export class RokuDeploy { * Checks to see if the exception is due to the device needing to check for updates */ private isUpdateRequiredError(e: any): boolean { - return e.results?.response?.statusCode === 577 || (typeof e.results?.body === 'string' && this.isUpdateCheckRequiredResponse(e.results.body)); + // Check for 577 status code in the new error format (details.httpResponse.statusCode) + const statusCode = e.details?.httpResponse?.statusCode; + const body = e.details?.httpResponse?.body; + return statusCode === 577 || (typeof body === 'string' && this.isUpdateCheckRequiredResponse(body)); } /** - * Converts existing loaded package to squashfs for faster loading packages + * Converts the currently sideloaded dev app to squashfs for faster loading packages * @param options */ - public async convertToSquashfs(options: RokuDeployOptions) { - options = this.getOptions(options); - if (!options.host) { - throw new errors.MissingRequiredOptionError('must specify the host for the Roku device'); - } + public async convertToSquashfs(options: ConvertToSquashfsOptions) { + this.checkRequiredOptions(options, ['host', 'password']); let requestOptions = this.generateBaseRequestOptions('plugin_install', options, { archive: '', mysubmit: 'Convert to squashfs' @@ -631,6 +487,7 @@ export class RokuDeploy { return results; } } catch (e) { + logger.warn('Error converting to squashfs:', error); throw error; } } else { @@ -638,37 +495,34 @@ export class RokuDeploy { } } if (results.body.indexOf('Conversion succeeded') === -1) { - throw new errors.ConvertError('Squashfs conversion failed'); + throw new errors.ConvertError('Squashfs conversion failed', { + httpResponse: extractHttpResponseDetails(results.response, results.body), + rokuMessages: this.getRokuMessagesFromResponseBody(results.body) + }); } } /** - * resign Roku Device with supplied pkg and + * resign Roku Device with a supplied signed pkg and * @param options */ - public async rekeyDevice(options: RokuDeployOptions) { - options = this.getOptions(options); - if (!options.rekeySignedPackage) { - throw new errors.MissingRequiredOptionError('Must supply rekeySignedPackage'); - } - - if (!options.signingPassword) { - throw new errors.MissingRequiredOptionError('Must supply signingPassword'); - } + public async rekeyDevice(options: RekeyDeviceOptions) { + this.checkRequiredOptions(options, ['host', 'password', 'pkg', 'signingPassword']); + const cwd = path.resolve(process.cwd(), options.cwd ?? '.'); - let rekeySignedPackagePath = options.rekeySignedPackage; - if (!path.isAbsolute(options.rekeySignedPackage)) { - rekeySignedPackagePath = path.join(options.rootDir, options.rekeySignedPackage); + let pkgPath = options.pkg; + if (!path.isAbsolute(options.pkg)) { + pkgPath = path.resolve(cwd, options.pkg); } - let requestOptions = this.generateBaseRequestOptions('plugin_inspect', options, { + let requestOptions = this.generateBaseRequestOptions('plugin_inspect', options as any, { mysubmit: 'Rekey', passwd: options.signingPassword, - archive: null as _fsExtra.ReadStream + archive: null as ReadStream }); let results: HttpResponse; try { - requestOptions.formData.archive = this.fsExtra.createReadStream(rekeySignedPackagePath); + requestOptions.formData.archive = fsExtra.createReadStream(pkgPath); results = await this.doPostRequest(requestOptions); } finally { //ensure the stream is closed @@ -679,34 +533,86 @@ export class RokuDeploy { let resultTextSearch = /([^<]+)<\/font>/.exec(results.body); if (!resultTextSearch) { - throw new errors.UnparsableDeviceResponseError('Unknown Rekey Failure'); + throw new errors.UnparsableDeviceResponseError('Unknown Rekey Failure', { + httpResponse: extractHttpResponseDetails(results.response, results.body), + rokuMessages: this.getRokuMessagesFromResponseBody(results.body), + host: options.host + }); } if (resultTextSearch[1] !== 'Success.') { - throw new errors.FailedDeviceResponseError('Rekey Failure: ' + resultTextSearch[1]); + throw new errors.FailedDeviceResponseError('Rekey Failure: ' + resultTextSearch[1], { + httpResponse: extractHttpResponseDetails(results.response, results.body), + rokuMessages: this.getRokuMessagesFromResponseBody(results.body), + host: options.host + }); } if (options.devId) { let devId = await this.getDevId(options); if (devId !== options.devId) { - throw new errors.UnknownDeviceResponseError('Rekey was successful but resulting Dev ID "' + devId + '" did not match expected value of "' + options.devId + '"'); + throw new errors.UnknownDeviceResponseError( + 'Rekey was successful but resulting Dev ID "' + devId + '" did not match expected value of "' + options.devId + '"', + { + httpResponse: extractHttpResponseDetails(results.response, results.body), + host: options.host + } + ); } } } /** - * Sign a pre-existing package using Roku and return path to retrieve it + * Sign a pre-existing package using Roku and return path to it locally * @param options */ - public async signExistingPackage(options: RokuDeployOptions): Promise { - options = this.getOptions(options); - if (!options.signingPassword) { - throw new errors.MissingRequiredOptionError('Must supply signingPassword'); + public async createSignedPackage(options: CreateSignedPackageOptions): Promise { + logger.info('Creating signed package'); + this.checkRequiredOptions(options, ['host', 'password', 'signingPassword']); + const cwd = options.cwd ?? process.cwd(); + + // Resolve output pkg path - use 'out' if provided, otherwise derive from default + let out = options.out + ? path.resolve(cwd, options.out) + : path.resolve(cwd, RokuDeploy.defaults.outDir, 'roku-deploy.pkg'); + + // Ensure .pkg extension + if (out.toLowerCase().endsWith('.zip')) { + out = out.replace(/\.zip$/i, '.pkg'); + } else if (!out.toLowerCase().endsWith('.pkg')) { + out += '.pkg'; + } + + // Process options for app title and app version + if (options.appTitle || options.appVersion) { + if (!options.appTitle || !options.appVersion) { + throw new Error('Either appTitle and appVersion is missing; both must be provided, or a manifestPath can be provided instead.'); + } + } else if (options.manifestPath) { + let manifestPath = path.resolve(cwd, options.manifestPath); + let parsedManifest = await this.parseManifest(manifestPath); + if (parsedManifest.major_version === undefined || parsedManifest.minor_version === undefined) { + throw new Error('Either major or minor version is missing from the manifest'); + } + options.appVersion = parsedManifest.major_version + '.' + parsedManifest.minor_version; + options.appTitle = parsedManifest.title; + if (!options.appTitle) { + throw new Error('Value for appTitle is missing from the manifest'); + } + } else { + throw new Error('Either appTitle and appVersion or manifestPath must be provided'); + } + + let appName = options.appTitle + '/' + options.appVersion; + + //prevent devId mismatch (if devId is specified) + if (options.devId) { + const deviceDevId = await this.getDevId(options); + if (options.devId !== deviceDevId) { + throw new Error(`Package signing cancelled: provided devId '${options.devId}' does not match on-device devId '${deviceDevId}'`); + } } - let manifestPath = path.join(options.stagingDir, 'manifest'); - let parsedManifest = await this.parseManifest(manifestPath); - let appName = parsedManifest.title + '/' + parsedManifest.major_version + '.' + parsedManifest.minor_version; let requestOptions = this.generateBaseRequestOptions('plugin_package', options, { mysubmit: 'Package', @@ -719,34 +625,32 @@ export class RokuDeploy { let failedSearchMatches = /Failed: (.*)/.exec(results.body); if (failedSearchMatches) { - throw new errors.FailedDeviceResponseError(failedSearchMatches[1], results); + throw new errors.FailedDeviceResponseError(failedSearchMatches[1], { + httpResponse: extractHttpResponseDetails(results.response, results.body), + rokuMessages: this.getRokuMessagesFromResponseBody(results.body), + host: options.host + }); } //grab the package url from the JSON on the page if it exists (https://regex101.com/r/1HUXgk/1) let pkgSearchMatches = /"pkgPath"\s*:\s*"(.*?)"/.exec(results.body); - if (pkgSearchMatches) { - return pkgSearchMatches[1]; + if (!pkgSearchMatches) { + //for some reason we couldn't find the pkgPath from json, look in the tag + pkgSearchMatches = //.exec(results.body); } - //for some reason we couldn't find the pkgPath from json, look in the tag - pkgSearchMatches = //.exec(results.body); if (pkgSearchMatches) { - return pkgSearchMatches[1]; + const url = pkgSearchMatches[1]; + let requestOptions2 = this.generateBaseRequestOptions(url, options); + await this.downloadFile(requestOptions2, out); + logger.info('Signed package created at:', out); + return out; } - throw new errors.UnknownDeviceResponseError('Unknown error signing package', results); - } - - /** - * Sign a pre-existing package using Roku and return path to retrieve it - * @param pkgPath - * @param options - */ - public async retrieveSignedPackage(pkgPath: string, options: RokuDeployOptions): Promise { - options = this.getOptions(options); - let requestOptions = this.generateBaseRequestOptions(pkgPath, options); - - let pkgFilePath = this.getOutputPkgFilePath(options); - return this.getToFile(requestOptions, pkgFilePath); + throw new errors.UnknownDeviceResponseError('Unknown error signing package', { + httpResponse: extractHttpResponseDetails(results.response, results.body), + rokuMessages: this.getRokuMessagesFromResponseBody(results.body), + host: options.host + }); } /** @@ -774,7 +678,7 @@ export class RokuDeploy { private getUserAgent() { try { if (this._packageVersion === undefined) { - this._packageVersion = _fsExtra.readJsonSync(`${__dirname}/../package.json`).version; + this._packageVersion = fsExtra.readJsonSync(`${__dirname}/../package.json`).version; } } catch (e) { this._packageVersion = null; @@ -789,6 +693,7 @@ export class RokuDeploy { * @param params */ private async doPostRequest(params: requestType.OptionsWithUrl, verify = true) { + logger.info('handling POST request to', params.url); let results: { response: any; body: any } = await new Promise((resolve, reject) => { this.setUserAgentIfMissing(params); @@ -811,6 +716,7 @@ export class RokuDeploy { * @param params */ private async doGetRequest(params: requestType.OptionsWithUrl) { + logger.info('handling GET request to', params.url); let results: { response: any; body: any } = await new Promise((resolve, reject) => { this.setUserAgentIfMissing(params); @@ -826,24 +732,45 @@ export class RokuDeploy { return results as HttpResponse; } - private checkRequest(results) { + private checkRequest(results: { response?: any; body?: any }) { if (!results || !results.response || typeof results.body !== 'string') { - throw new errors.UnparsableDeviceResponseError('Invalid response', results); + throw new errors.UnparsableDeviceResponseError('Invalid response', { + httpResponse: extractHttpResponseDetails(results?.response, results?.body) + }); } + const host = results.response.request?.host?.toString?.(); + const httpResponse = extractHttpResponseDetails(results.response, results.body); + if (results.response.statusCode === 401) { - const host = results.response.request?.host?.toString?.(); - throw new errors.UnauthorizedDeviceResponseError(`Unauthorized. Please verify credentials for host '${host}'`, results); + throw new errors.UnauthorizedDeviceResponseError( + `Unauthorized. Please verify credentials for host '${host}'`, + { + httpResponse: httpResponse, + host: host + } + ); } let rokuMessages = this.getRokuMessagesFromResponseBody(results.body); if (rokuMessages.errors.length > 0) { - throw new errors.FailedDeviceResponseError(rokuMessages.errors[0], rokuMessages); + throw new errors.FailedDeviceResponseError(rokuMessages.errors[0], { + httpResponse: httpResponse, + rokuMessages: rokuMessages, + host: host + }); } if (results.response.statusCode !== 200) { - throw new errors.InvalidDeviceResponseCodeError('Invalid response code: ' + results.response.statusCode, results); + throw new errors.InvalidDeviceResponseCodeError( + 'Invalid response code: ' + results.response.statusCode, + { + httpResponse: httpResponse, + rokuMessages: rokuMessages, + host: host + } + ); } } @@ -956,30 +883,13 @@ export class RokuDeploy { return []; } - /** - * Create a zip of the project, and then publish to the target Roku device - * @param options - */ - public async deploy(options?: RokuDeployOptions, beforeZipCallback?: (info: BeforeZipCallbackInfo) => void) { - options = this.getOptions(options); - await this.createPackage(options, beforeZipCallback); - if (options.deleteInstalledChannel) { - try { - await this.deleteInstalledChannel(options); - } catch (e) { - // note we don't report the error; as we don't actually care that we could not deploy - it's just useless noise to log it. - } - } - let result = await this.publish(options); - return result; - } - /** * Deletes any installed dev channel on the target Roku device * @param options */ - public async deleteInstalledChannel(options?: RokuDeployOptions) { - options = this.getOptions(options); + public async deleteDevChannel(options?: DeleteDevChannelOptions) { + logger.info('Deleting dev channel...'); + this.checkRequiredOptions(options, ['host', 'password']); let deleteOptions = this.generateBaseRequestOptions('plugin_install', options); deleteOptions.formData = { @@ -993,7 +903,7 @@ export class RokuDeploy { * Delete the component library with the specified filename from the device */ public async deleteComponentLibrary(options?: { host: string; password: string; fileName: string; username?: string }) { - options = this.getOptions(options) as any; + this.checkRequiredOptions(options, ['host', 'password', 'fileName']); let deleteOptions = this.generateBaseRequestOptions('plugin_install', options); deleteOptions.formData = { @@ -1026,7 +936,7 @@ export class RokuDeploy { * Fetch the full list of installed packages from the device. Useful for finding the file names of installed component libraries or the dev channel. */ private async getInstalledPackages(options: { host: string; password: string; username?: string }): Promise { - options = this.getOptions(options) as any; + this.checkRequiredOptions(options, ['host', 'password']); let deleteOptions = this.generateBaseRequestOptions('plugin_install', options); deleteOptions.qs ??= {}; // eslint-disable-next-line camelcase @@ -1039,10 +949,15 @@ export class RokuDeploy { /** * Gets a screenshot from the device. A side-loaded channel must be running or an error will be thrown. */ - public async takeScreenshot(options: TakeScreenshotOptions) { - options.outDir = options.outDir ?? this.screenshotDir; - options.outFile = options.outFile ?? `screenshot-${dayjs().format('YYYY-MM-DD-HH.mm.ss.SSS')}`; - let saveFilePath: string; + public async captureScreenshot(options: CaptureScreenshotOptions) { + this.checkRequiredOptions(options, ['host', 'password']); + const cwd = options.cwd ?? process.cwd(); + const screenshotDir = options.screenshotDir + ? path.resolve(cwd, options.screenshotDir) + : path.join(tempDir, '/roku-deploy/screenshots/'); + + // Track if user provided output path + const userProvidedOut = options.out !== undefined; // Ask for the device to make an image let createScreenshotResult = await this.doPostRequest({ @@ -1054,25 +969,50 @@ export class RokuDeploy { }); // Pull the image url out of the response body - const [_, imageUrlOnDevice, imageExt] = /["'](pkgs\/dev(\.jpg|\.png)\?.+?)['"]/gi.exec(createScreenshotResult.body) ?? []; - - if (imageUrlOnDevice) { - saveFilePath = util.standardizePath(path.join(options.outDir, options.outFile + imageExt)); - await this.getToFile( - this.generateBaseRequestOptions(imageUrlOnDevice, options), - saveFilePath - ); + const [_, imageUrlOnDevice, deviceExt] = /["'](pkgs\/dev(\.jpg|\.png)\?.+?)['"]/gi.exec(createScreenshotResult.body) ?? []; + + if (!imageUrlOnDevice) { + throw new Error('No screenshot url returned from device'); + } + + // Determine output path + let out: string; + if (userProvidedOut) { + out = path.resolve(cwd, options.out); + const userExt = path.extname(out).toLowerCase(); + const deviceExtLower = deviceExt.toLowerCase(); + + if (options.autoExtension) { + // Smart extension handling when autoExtension is enabled + if (userExt === deviceExtLower) { + // User extension matches device extension - use as-is + } else if (userExt === '.jpg' || userExt === '.jpeg' || userExt === '.png') { + // User provided an image extension that doesn't match - swap it + out = out.slice(0, -userExt.length) + deviceExt; + } else { + // No recognized image extension - append device extension + out += deviceExt; + } + } + // else: use exactly as provided } else { - throw new Error('No screen shot url returned from device'); + // No user-provided path - use default directory with generated filename + const defaultFilename = `screenshot-${dayjs().format('YYYY-MM-DD-HH.mm.ss.SSS')}${deviceExt}`; + out = path.resolve(cwd, screenshotDir, defaultFilename); } - return saveFilePath; + + await this.downloadFile( + this.generateBaseRequestOptions(imageUrlOnDevice, options), + out + ); + return out; } - private async getToFile(requestParams: any, filePath: string) { - let writeStream: _fsExtra.WriteStream; - await this.fsExtra.ensureFile(filePath); + private async downloadFile(requestParams: any, filePath: string) { + let writeStream: WriteStream; + await fsExtra.ensureFile(filePath); return new Promise((resolve, reject) => { - writeStream = this.fsExtra.createWriteStream(filePath, { + writeStream = fsExtra.createWriteStream(filePath, { flags: 'w' }); if (!writeStream) { @@ -1103,174 +1043,12 @@ export class RokuDeploy { }); } - /** - * executes sames steps as deploy and signs the package and stores it in the out folder - * @param options - */ - public async deployAndSignPackage(options?: RokuDeployOptions, beforeZipCallback?: (info: BeforeZipCallbackInfo) => void): Promise { - options = this.getOptions(options); - let retainStagingDirInitialValue = options.retainStagingDir; - options.retainStagingDir = true; - await this.deploy(options, beforeZipCallback); - - if (options.convertToSquashfs) { - await this.convertToSquashfs(options); - } - - let remotePkgPath = await this.signExistingPackage(options); - let localPkgFilePath = await this.retrieveSignedPackage(remotePkgPath, options); - if (retainStagingDirInitialValue !== true) { - await this.fsExtra.remove(options.stagingDir); - } - return localPkgFilePath; - } - - /** - * Get an options with all overridden vaues, and then defaults for missing values - * @param options - */ - public getOptions(options: RokuDeployOptions = {}) { - let fileOptions: RokuDeployOptions = {}; - const fileNames = ['rokudeploy.json', 'bsconfig.json']; - if (options.project) { - fileNames.unshift(options.project); - } - - for (const fileName of fileNames) { - if (this.fsExtra.existsSync(fileName)) { - let configFileText = this.fsExtra.readFileSync(fileName).toString(); - let parseErrors = [] as ParseError[]; - fileOptions = parseJsonc(configFileText, parseErrors, { - allowEmptyContent: true, - allowTrailingComma: true, - disallowComments: false - }); - if (parseErrors.length > 0) { - throw new Error(`Error parsing "${path.resolve(fileName)}": ` + JSON.stringify( - parseErrors.map(x => { - return { - message: printParseErrorCode(x.error), - offset: x.offset, - length: x.length - }; - }) - )); - } - break; + public checkRequiredOptions>(options: T, requiredOptions: Array) { + for (let opt of requiredOptions as string[]) { + if (options[opt] === undefined) { + throw new Error('Missing required option: ' + opt); } } - - let defaultOptions = { - outDir: './out', - outFile: 'roku-deploy', - retainDeploymentArchive: true, - incrementBuildNumber: false, - failOnCompileError: true, - deleteInstalledChannel: true, - packagePort: 80, - remotePort: 8060, - timeout: 150000, - rootDir: './', - files: [...DefaultFiles], - username: 'rokudev', - logLevel: LogLevel.log - }; - - //override the defaults with any found or provided options - let finalOptions = { ...defaultOptions, ...fileOptions, ...options }; - this.logger.logLevel = finalOptions.logLevel; - - //fully resolve the folder paths - finalOptions.rootDir = path.resolve(process.cwd(), finalOptions.rootDir); - finalOptions.outDir = path.resolve(process.cwd(), finalOptions.outDir); - finalOptions.retainStagingDir = (finalOptions.retainStagingDir !== undefined) ? finalOptions.retainStagingDir : finalOptions.retainStagingFolder; - //sync the new option with the old one (for back-compat) - finalOptions.retainStagingFolder = finalOptions.retainStagingDir; - - let stagingDir = finalOptions.stagingDir || finalOptions.stagingFolderPath; - - //stagingDir - if (stagingDir) { - finalOptions.stagingDir = path.resolve(process.cwd(), stagingDir); - } else { - finalOptions.stagingDir = path.resolve( - process.cwd(), - util.standardizePath(`${finalOptions.outDir}/.roku-deploy-staging`) - ); - } - //sync the new option with the old one (for back-compat) - finalOptions.stagingFolderPath = finalOptions.stagingDir; - - return finalOptions; - } - - /** - * Centralizes getting output zip file path based on passed in options - * @param options - */ - public getOutputZipFilePath(options: RokuDeployOptions) { - options = this.getOptions(options); - - let zipFileName = options.outFile; - if (!zipFileName.toLowerCase().endsWith('.zip') && !zipFileName.toLowerCase().endsWith('.squashfs')) { - zipFileName += '.zip'; - } - let outFolderPath = path.resolve(options.outDir); - - let outZipFilePath = path.join(outFolderPath, zipFileName); - return outZipFilePath; - } - - /** - * Centralizes getting output pkg file path based on passed in options - * @param options - */ - public getOutputPkgFilePath(options?: RokuDeployOptions) { - options = this.getOptions(options); - - let pkgFileName = options.outFile; - if (pkgFileName.toLowerCase().endsWith('.zip')) { - pkgFileName = pkgFileName.replace('.zip', '.pkg'); - } else { - pkgFileName += '.pkg'; - } - let outFolderPath = path.resolve(options.outDir); - - let outPkgFilePath = path.join(outFolderPath, pkgFileName); - return outPkgFilePath; - } - - /** - * Check whether the given developer password is accepted by a Roku device. - * Resolves `true` if the device accepts the credentials, `false` if it rejects them. - * Throws `DeviceUnreachableError` for network failures and `InvalidDeviceResponseCodeError` for unexpected statuses. - */ - public async validateDeveloperPassword(options: ValidateDeveloperPasswordOptions): Promise { - const username = options.username ?? 'rokudev'; - const port = options.port ?? 80; - const timeout = options.timeout ?? 3000; - const url = `http://${options.host}:${port}/plugin_install`; - - let response: Response; - try { - response = await fetchWithDigest(url, { - method: 'HEAD', - username: username, - password: options.password, - timeout: timeout - }); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - throw new errors.DeviceUnreachableError(`Device ${options.host} was unreachable: ${message}`, err); - } - - if (response.status === 200) { - return true; - } - if (response.status === 401) { - return false; - } - throw new errors.InvalidDeviceResponseCodeError(`Unexpected status ${response.status} from device at ${options.host}`, response); } /** @@ -1278,29 +1056,40 @@ export class RokuDeploy { * @param host the host or IP address of the Roku * @param port the port to use for the ECP request (defaults to 8060) */ - public async getDeviceInfo(options?: { enhance: true } & GetDeviceInfoOptions): Promise; - public async getDeviceInfo(options?: GetDeviceInfoOptions): Promise + public async getDeviceInfo(options?: GetDeviceInfoOptions & { enhance: true }): Promise; + public async getDeviceInfo(options?: GetDeviceInfoOptions): Promise; public async getDeviceInfo(options: GetDeviceInfoOptions) { - options = this.getOptions(options) as any; + this.checkRequiredOptions(options, ['host']); + + // Set defaults + const ecpPort = options.ecpPort ?? RokuDeploy.defaults.ecpPort; + const timeout = options.timeout ?? RokuDeploy.defaults.timeout; //if the host is a DNS name, look up the IP address + let host = options.host; try { - options.host = await util.dnsLookup(options.host); + host = await util.dnsLookup(options.host); } catch (e) { //try using the host as-is (it'll probably fail...) } - const url = `http://${options.host}:${options.remotePort}/query/device-info`; + const url = `http://${host}:${ecpPort}/query/device-info`; - let response; + let response: HttpResponse | undefined; try { response = await this.doGetRequest({ url: url, - timeout: options.timeout + timeout: timeout }); } catch (e) { - if ((e as any)?.results?.response?.headers?.server?.includes('Roku')) { - throw new errors.EcpNetworkAccessModeDisabledError(`Unable to access device-info because ecp-setting-mode is 'disabled'`, response); + if ((e as any)?.details?.httpResponse?.headers?.server?.includes('Roku')) { + throw new errors.EcpNetworkAccessModeDisabledError( + `Unable to access device-info because ecp-setting-mode is 'disabled'`, + { + httpResponse: (e as any)?.details?.httpResponse, + host: options.host + } + ); } throw e; } @@ -1321,9 +1110,14 @@ export class RokuDeploy { } deviceInfo = result; } + logger.debug('Device info:', deviceInfo); return deviceInfo; } catch (e) { - throw new errors.UnparsableDeviceResponseError('Could not retrieve device info', response); + logger.warn('Error getting device info:', e); + throw new errors.UnparsableDeviceResponseError('Could not retrieve device info', { + httpResponse: extractHttpResponseDetails(response?.response, response?.body), + host: options.host + }, e instanceof Error ? e : undefined); } } @@ -1331,7 +1125,7 @@ export class RokuDeploy { * Get the External Control Protocol (ECP) setting mode of the device. This determines whether * the device accepts remote control commands via the ECP API. * - * @param options - Configuration options including host, remotePort, timeout, etc. + * @param options - Configuration options including host, ecpPort, timeout, etc. * @returns The ECP setting mode: * - 'enabled': fully enabled and accepting commands * - 'disabled': ECP is disabled (device may still be reachable but ECP commands won't work) @@ -1341,14 +1135,15 @@ export class RokuDeploy { public async getEcpNetworkAccessMode(options: GetDeviceInfoOptions): Promise { try { const deviceInfo = await this.getDeviceInfo(options); - return deviceInfo.ecpSettingMode; + return deviceInfo['ecp-setting-mode']; } catch (e) { - if ((e as any)?.results?.response?.headers?.server?.includes('Roku')) { + if ((e as any)?.details?.httpResponse?.headers?.server?.includes('Roku')) { return 'disabled'; } - throw new errors.UnknownDeviceResponseError('Could not retrieve device ECP setting'); + throw new errors.UnknownDeviceResponseError('Could not retrieve device ECP setting', { + host: options.host + }, e instanceof Error ? e : undefined); } - } /** @@ -1356,7 +1151,7 @@ export class RokuDeploy { * decoding HtmlEntities, etc. * @param deviceInfo */ - public normalizeDeviceInfoFieldValue(value: any) { + private normalizeDeviceInfoFieldValue(value: any) { let num: number; // convert 'true' and 'false' string values to boolean if (value === 'true') { @@ -1370,21 +1165,28 @@ export class RokuDeploy { } } - public async getDevId(options?: RokuDeployOptions) { - const deviceInfo = await this.getDeviceInfo(options as any); + /** + * Get the developer ID from the device-info response + * @param options + * @returns + */ + public async getDevId(options?: GetDevIdOptions) { + this.checkRequiredOptions(options, ['host']); + const deviceInfo = await this.getDeviceInfo(options); + logger.debug('Found dev id:', deviceInfo['keyed-developer-id']); return deviceInfo['keyed-developer-id']; } - public async parseManifest(manifestPath: string): Promise { - if (!await this.fsExtra.pathExists(manifestPath)) { + private async parseManifest(manifestPath: string): Promise { + if (!await fsExtra.pathExists(manifestPath)) { throw new Error(manifestPath + ' does not exist'); } - let manifestContents = await this.fsExtra.readFile(manifestPath, 'utf-8'); + let manifestContents = await fsExtra.readFile(manifestPath, 'utf-8'); return this.parseManifestFromString(manifestContents); } - public parseManifestFromString(manifestContents: string): ManifestData { + private parseManifestFromString(manifestContents: string): ManifestData { let manifestLines = manifestContents.split('\n'); let manifestData: ManifestData = {}; manifestData.keyIndexes = {}; @@ -1401,77 +1203,23 @@ export class RokuDeploy { return manifestData; } - public stringifyManifest(manifestData: ManifestData): string { - let output = []; - - if (manifestData.keyIndexes && manifestData.lineCount) { - output.fill('', 0, manifestData.lineCount); - - let key; - for (key in manifestData) { - if (key === 'lineCount' || key === 'keyIndexes') { - continue; - } - - let index = manifestData.keyIndexes[key]; - output[index] = `${key}=${manifestData[key]}`; - } - } else { - output = Object.keys(manifestData).map((key) => { - return `${key}=${manifestData[key]}`; - }); - } - - return output.join('\n'); - } - - /** - * Given a path to a folder, zip up that folder and all of its contents - * @param srcFolder the folder that should be zipped - * @param zipFilePath the path to the zip that will be created - * @param preZipCallback a function to call right before every file gets added to the zip - * @param files a files array used to filter the files from `srcFolder` - */ - public async zipFolder(srcFolder: string, zipFilePath: string, preFileZipCallback?: (file: StandardizedFileEntry, data: Buffer) => Buffer, files: FileEntry[] = ['**/*']) { - const filePaths = await this.getFilePaths(files, srcFolder); - - const zip = new JSZip(); - // Allows us to wait until all are done before we build the zip - const promises = []; - for (const file of filePaths) { - const promise = this.fsExtra.readFile(file.src).then((data) => { - if (preFileZipCallback) { - data = preFileZipCallback(file, data); - } - - const ext = path.extname(file.dest).toLowerCase(); - let compression = 'DEFLATE'; - - if (ext === '.jpg' || ext === '.png' || ext === '.jpeg') { - compression = 'STORE'; - } - zip.file(path.relative(srcFolder, path.resolve(srcFolder, file.dest)).replace(/[\\/]/g, '/'), data as any, { - compression: compression - }); - }); - promises.push(promise); - } - await Promise.all(promises); - // level 2 compression seems to be the best balance between speed and file size. Speed matters more since most will be calling squashfs afterwards. - const content = await zip.generateAsync({ type: 'nodebuffer', compressionOptions: { level: 2 } }); - return this.fsExtra.outputFile(zipFilePath, content); - } - - public async rebootDevice(options: RokuDeployOptions) { - options = this.getOptions(options); + public async rebootDevice(options: RebootDeviceOptions) { + this.checkRequiredOptions(options, ['host', 'password']); // Get device info to check software version - const deviceInfo = await this.getDeviceInfo(options as any); + const deviceInfo = await this.getDeviceInfo(options); const softwareVersion = deviceInfo['software-version']; // Check if device version is at least 15.0.4 if (!softwareVersion || semver.lt(semver.coerce(softwareVersion), '15.0.4')) { - throw new errors.UnsupportedFirmwareVersionError(`Device software version ${softwareVersion} is below the minimum required version 15.0.4 for reboot operation`); + throw new errors.UnsupportedFirmwareVersionError( + `Device software version ${softwareVersion} is below the minimum required version 15.0.4 for reboot operation`, + { + currentVersion: softwareVersion, + minimumVersion: '15.0.4', + operation: 'reboot' + } + ); } return this.doPostRequest({ @@ -1483,16 +1231,23 @@ export class RokuDeploy { }); } - public async checkForUpdate(options: RokuDeployOptions) { - options = this.getOptions(options); + public async checkForUpdate(options: CheckForUpdateOptions) { + this.checkRequiredOptions(options, ['host', 'password']); // Get device info to check software version - const deviceInfo = await this.getDeviceInfo(options as any); + const deviceInfo = await this.getDeviceInfo(options); const softwareVersion = deviceInfo['software-version']; // Check if device version is at least 15.0.4 if (!softwareVersion || semver.lt(semver.coerce(softwareVersion), '15.0.4')) { - throw new errors.UnsupportedFirmwareVersionError(`Device software version ${softwareVersion} is below the minimum required version 15.0.4 for check update operation`); + throw new errors.UnsupportedFirmwareVersionError( + `Device software version ${softwareVersion} is below the minimum required version 15.0.4 for check update operation`, + { + currentVersion: softwareVersion, + minimumVersion: '15.0.4', + operation: 'checkForUpdate' + } + ); } return this.doPostRequest({ @@ -1511,21 +1266,6 @@ export interface ManifestData { lineCount?: number; } -export interface BeforeZipCallbackInfo { - /** - * Contains an associative array of the parsed values in the manifest - */ - manifestData: ManifestData; - /** - * @deprecated since 3.9.0. use `stagingDir` instead - */ - stagingFolderPath: string; - /** - * The directory where the files were staged - */ - stagingDir: string; -} - export interface StandardizedFileEntry { /** * The full path to the source file @@ -1565,7 +1305,10 @@ export const DefaultFiles = [ 'components/**/*.*', 'images/**/*.*', 'locale/**/*', - 'manifest' + 'fonts/**/*', + 'manifest', + '!node_modules', + '!**/*.{md,DS_Store,db}' ]; export interface HttpResponse { @@ -1573,62 +1316,184 @@ export interface HttpResponse { body: any; } -export interface TakeScreenshotOptions { +export interface CaptureScreenshotOptions extends BaseRequestOptions { /** - * The IP address or hostname of the target Roku device. - * @example '192.168.1.21' + * The output screenshot file path (e.g., './screenshots/capture.jpg') + * Will use the OS temp directory with auto-generated filename by default */ - host: string; + out?: string; /** - * The password for logging in to the developer portal on the target Roku device + * The current working directory to use for relative paths */ - password: string; + cwd?: string; /** - * A full path to the folder where the screenshots should be saved. - * Will use the OS temp directory by default + * The directory where screenshots should be saved when no `out` path is specified. + * Defaults to the OS temp directory. */ - outDir?: string; + screenshotDir?: string; /** - * The base filename the image file should be given (excluding the extension) - * The default format looks something like this: screenshot-YYYY-MM-DD-HH.mm.ss.SSS. + * When false (default), the filename is used exactly as provided by the user. + * When true, the extension is automatically handled: + * - If the user's filename ends with the device's extension, use it as-is + * - If the user's filename ends with .png or .jpg but doesn't match the device's format, swap the extension + * - Otherwise, append the device's extension to the filename + * @default false */ - outFile?: string; + autoExtension?: boolean; } -export interface ValidateDeveloperPasswordOptions { - /** The hostname or IP of the Roku device */ - host: string; - /** The developer password to check */ - password: string; - /** Defaults to `'rokudev'` */ - username?: string; - /** Defaults to `80` (the developer web-server port) */ - port?: number; - /** Milliseconds to wait for each HTTP round-trip. Defaults to `3000`. */ - timeout?: number; +export interface GetDeviceInfoOptions extends BaseEcpOptions { + /** + * Should the device-info be enhanced by camel-casing the property names and converting boolean strings to booleans and number strings to numbers? + * @default false + */ + enhance?: boolean; +} + +export type RokuKey = 'back' | 'backspace' | 'channeldown' | 'channelup' | 'down' | 'enter' | 'findremote' | 'fwd' | 'home' | 'info' | 'inputav1' | 'inputhdmi1' | 'inputhdmi2' | 'inputhdmi3' | 'inputhdmi4' | 'inputtuner' | 'instantreplay' | 'left' | 'play' | 'poweroff' | 'rev' | 'right' | 'search' | 'select' | 'up' | 'volumedown' | 'volumemute' | 'volumeup'; + +export interface SendKeyEventOptions extends BaseEcpOptions { + action?: 'keydown' | 'keypress' | 'keyup'; + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + key: RokuKey | string; +} + +export interface KeyUpOptions extends BaseEcpOptions { + key: RokuKey; +} + +export interface KeyDownOptions extends BaseEcpOptions { + key: RokuKey; +} + +export interface KeyPressOptions extends BaseEcpOptions { + key: RokuKey; +} + +export interface SendTextOptions extends BaseEcpOptions { + text: string; } -export interface GetDeviceInfoOptions { +export type CloseChannelOptions = BaseEcpOptions; + +export interface GetFilePathsOptions { + files: FileEntry[]; + rootDir: string; +} + +export interface StageOptions { + rootDir?: string; + files?: FileEntry[]; /** - * The hostname or IP address to use for the device-info URL + * The output directory where staged files will be placed */ - host: string; + out?: string; + cwd?: string; +} + +export interface ZipOptions { /** - * The port to use to send the device-info request (defaults to the standard 8060 ECP port) + * The directory containing the files to be zipped */ - remotePort?: number; + dir: string; /** - * The number of milliseconds at which point this request should timeout and return a rejected promise + * An optional array of file patterns to include in the zip. + * If not provided, defaults to all files (`**\/*`). */ + files?: FileEntry[]; + /** + * The output zip file path (e.g., './out/roku-deploy.zip') + */ + out?: string; + cwd?: string; +} + +type BaseSideloadOptions = BaseRequestOptions & BaseEcpOptions & { + appType?: 'channel' | 'dcl'; + close?: boolean; + remoteDebug?: boolean; + remoteDebugConnectEarly?: boolean; + failOnCompileError?: boolean; + deleteDevChannel?: boolean; + cwd?: string; + packageUploadOverrides?: PackageUploadOverridesOptions; +}; + +export type SideloadOptions = BaseSideloadOptions & ( + | { zip: string; dir?: never } + | { dir: string; zip?: never } +); + +export interface PackageUploadOverridesOptions { + route?: string; + formData?: Record; +} + +export interface BaseRequestOptions { + host: string; + username?: string; + password: string; + packagePort?: number; + timeout?: number; + logLevel?: LogLevel; +} + +export interface BaseEcpOptions { + host: string; + ecpPort?: number; timeout?: number; +} + +export type ConvertToSquashfsOptions = BaseRequestOptions; + +export interface RekeyDeviceOptions extends BaseRequestOptions { + pkg: string; + signingPassword: string; + devId: string; + cwd?: string; +} + +export interface CreateSignedPackageOptions extends BaseRequestOptions { + signingPassword: string; + appTitle?: string; + appVersion?: string; + manifestPath?: string; /** - * Should the device-info be enhanced by camel-casing the property names and converting boolean strings to booleans and number strings to numbers? - * @default false + * The output pkg file path (e.g., './out/roku-deploy.pkg') */ - enhance?: boolean; + out?: string; + /** + * If specified, signing will fail if the device's devId is different than this value + */ + devId?: string; + cwd?: string; +} + +export type DeleteDevChannelOptions = BaseRequestOptions; + +export type RebootDeviceOptions = BaseRequestOptions; + +export type CheckForUpdateOptions = BaseRequestOptions; + +export interface GetOutputZipFilePathOptions { + out?: string; + cwd?: string; } +export interface DeployOptions extends BaseRequestOptions { + files?: FileEntry[]; + rootDir?: string; + stagingDir?: string; + deleteDevChannel?: boolean; + out?: string; + cwd?: string; +} + +export type GetDevIdOptions = BaseEcpOptions; + +//create a new static instance of RokuDeploy, and export those functions for backwards compatibility +export const rokuDeploy = new RokuDeploy(); export type EcpNetworkAccessMode = 'enabled' | 'disabled' | 'limited' | 'permissive'; diff --git a/src/RokuDeployOptions.ts b/src/RokuDeployOptions.ts index 3f06a3da..0769df85 100644 --- a/src/RokuDeployOptions.ts +++ b/src/RokuDeployOptions.ts @@ -1,22 +1,10 @@ -import type { LogLevel } from './Logger'; +import type { LogLevel } from '@rokucommunity/logger'; export interface RokuDeployOptions { /** - * Path to a bsconfig.json project file + * The working directory where the command should be executed */ - project?: string; - - /** - * A full path to the folder where the zip/pkg package should be placed - * @default './out' - */ - outDir?: string; - - /** - * The base filename the zip/pkg file should be given (excluding the extension) - * @default 'roku-deploy' - */ - outFile?: string; + cwd?: string; /** * The root path to the folder holding your Roku project's source files (manifest, components/, source/ should be directly under this folder) @@ -44,31 +32,6 @@ export interface RokuDeployOptions { */ files?: FileEntry[]; - /** - * Set this to true to prevent the staging folder from being deleted after creating the package - * @default false - * @deprecated use `retainStagingDir` instead - */ - retainStagingFolder?: boolean; - - /** - * Set this to true to prevent the staging folder from being deleted after creating the package - * @default false - */ - retainStagingDir?: boolean; - - /** - * Should the zipped package be retained after deploying to a roku. If false, this will delete the zip after a deployment. - * @default true - */ - retainDeploymentArchive?: boolean; - - /** - * The path where roku-deploy should stage all of the files right before being zipped. defaults to ${outDir}/.roku-deploy-staging - * @deprecated since 3.9.0. use `stagingDir` instead - */ - stagingFolderPath?: string; - /** * The path where roku-deploy should stage all of the files right before being zipped. defaults to ${outDir}/.roku-deploy-staging */ @@ -110,7 +73,12 @@ export interface RokuDeployOptions { * This is mainly useful for things like emulators that use alternate ports, * or when sending commands through some type of port forwarding. */ - remotePort?: number; + ecpPort?: number; + + /** + * The directory where screenshots should be saved. Will use the OS temp directory by default + */ + screenshotDir?: string; /** * The request timeout duration in milliseconds. Defaults to 150000ms (2 minutes 30 seconds). @@ -139,23 +107,13 @@ export interface RokuDeployOptions { /** * Path to a copy of the signed package you want to use for rekeying */ - rekeySignedPackage?: string; + pkg?: string; /** * Dev ID we are expecting the device to have. If supplied we check that the dev ID returned after keying matches what we expected */ devId?: string; - /** - * If true we increment the build number to be a timestamp in the format yymmddHHMM - */ - incrementBuildNumber?: boolean; - - /** - * If true we convert to squashfs before creating the pkg file - */ - convertToSquashfs?: boolean; - /** * If true, the publish will fail on compile error */ @@ -170,7 +128,7 @@ export interface RokuDeployOptions { /** * If true, the previously installed dev channel will be deleted before installing the new one */ - deleteInstalledChannel?: boolean; + deleteDevChannel?: boolean; /** * Overrides for values used during the zip upload process. You probably don't need to change these... @@ -189,4 +147,4 @@ export interface RokuDeployOptions { }; } -export type FileEntry = (string | { src: string | string[]; dest?: string }); +export type FileEntry = (string | { src: string[] | string; dest?: string }); diff --git a/src/cli.spec.ts b/src/cli.spec.ts new file mode 100644 index 00000000..be16f1d1 --- /dev/null +++ b/src/cli.spec.ts @@ -0,0 +1,239 @@ +import * as childProcess from 'child_process'; +import { cwd, expectPathExists, rootDir, stagingDir, tempDir, outDir } from './testUtils.spec'; +import * as fsExtra from 'fs-extra'; +import { expect } from 'chai'; +import { createSandbox } from 'sinon'; +import { rokuDeploy } from './index'; +import { ConvertToSquashfsCommand } from './commands/ConvertToSquashfsCommand'; +import { RekeyDeviceCommand } from './commands/RekeyDeviceCommand'; +import { CreateSignedPackageCommand } from './commands/CreateSignedPackageCommand'; +import { DeleteDevChannelCommand } from './commands/DeleteDevChannelCommand'; +import { CaptureScreenshotCommand } from './commands/CaptureScreenshotCommand'; +import { GetDeviceInfoCommand } from './commands/GetDeviceInfoCommand'; +import { GetDevIdCommand } from './commands/GetDevIdCommand'; +import { standardizePath as s } from './util'; + +const sinon = createSandbox(); + +function execSync(command: string) { + const output = childProcess.execSync(command, { cwd: tempDir }); + process.stdout.write(output); + return output; +} +describe('cli', function cli() { + //all cli tests spawn `node dist/cli.js` via execSync, which can exceed the default 2s timeout + this.timeout(60_000); + + before(() => { + execSync('npm run build'); + }); + + beforeEach(() => { + fsExtra.emptyDirSync(tempDir); + //most tests depend on a manifest file existing, so write an empty one + fsExtra.outputFileSync(`${rootDir}/manifest`, ''); + sinon.restore(); + }); + afterEach(() => { + fsExtra.removeSync(tempDir); + sinon.restore(); + }); + + it('Successfully runs stage', () => { + //make the files + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + + expect(() => { + execSync(`node ${cwd}/dist/cli.js stage --out ${stagingDir} --rootDir ${rootDir}`); + }).to.not.throw(); + }); + + it('Successfully copies rootDir folder to staging folder', () => { + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + + execSync(`node ${cwd}/dist/cli.js stage --rootDir ${rootDir} --out ${stagingDir}`); + + expectPathExists(`${stagingDir}/source/main.brs`); + }); + + it('Converts to squashfs', async () => { + const stub = sinon.stub(rokuDeploy, 'convertToSquashfs').callsFake(async () => { + return Promise.resolve(); + }); + + const command = new ConvertToSquashfsCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Rekeys a device', async () => { + const stub = sinon.stub(rokuDeploy, 'rekeyDevice').callsFake(async () => { + return Promise.resolve(); + }); + + const command = new RekeyDeviceCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + pkg: `${tempDir}/testSignedPackage.pkg`, + signingPassword: '12345', + rootDir: rootDir, + devId: 'abcde' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + cwd: cwd, + host: '1.2.3.4', + password: '5536', + pkg: s`${tempDir}/testSignedPackage.pkg`, + signingPassword: '12345', + rootDir: rootDir, + devId: 'abcde' + }); + }); + + it('Signs an existing package', async () => { + const stub = sinon.stub(rokuDeploy, 'createSignedPackage').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new CreateSignedPackageCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + signingPassword: undefined, + stagingDir: stagingDir + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + cwd: cwd, + host: '1.2.3.4', + password: '5536', + signingPassword: undefined, + stagingDir: stagingDir + }); + }); + + it('Deletes an installed channel', async () => { + const stub = sinon.stub(rokuDeploy, 'deleteDevChannel').callsFake(async () => { + return Promise.resolve({ response: {}, body: {} }); + }); + + const command = new DeleteDevChannelCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Takes a screenshot', async () => { + const stub = sinon.stub(rokuDeploy, 'captureScreenshot').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new CaptureScreenshotCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + cwd: cwd, + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Device info arguments are correct', async () => { + const stub = sinon.stub(rokuDeploy, 'getDeviceInfo').callsFake(async () => { + return Promise.resolve({ + response: {}, + body: {} + }); + }); + + const command = new GetDeviceInfoCommand(); + await command.run({ + host: '1.2.3.4' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4' + }); + }); + + it('Prints device info to console', async () => { + let consoleOutput = ''; + sinon.stub(console, 'log').callsFake((...args) => { + consoleOutput += args.join(' ') + '\n'; + }); + sinon.stub(rokuDeploy, 'getDeviceInfo').returns(Promise.resolve({ + 'device-id': '1234', + 'serial-number': 'abcd' + })); + await new GetDeviceInfoCommand().run({ + host: '1.2.3.4' + }); + + // const consoleOutputObject: Record = { + // 'device-id': '1234', + // 'serial-number': 'abcd' + // }; + + expect(consoleOutput).to.eql([ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number abcd \n' + ].join('\n')); + }); + + it('Gets dev id', async () => { + const stub = sinon.stub(rokuDeploy, 'getDevId').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new GetDevIdCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Zips a folder', () => { + execSync(`node ${cwd}/dist/cli.js zip --dir ${rootDir} --out ${outDir}/roku-deploy.zip`); + + expectPathExists(`${outDir}/roku-deploy.zip`); + }); +}); diff --git a/src/cli.ts b/src/cli.ts old mode 100644 new mode 100755 index 063f5fdd..f7626a41 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,177 @@ #!/usr/bin/env node -import { deploy } from './index'; -deploy().then((...args) => { - console.log(...args); -}, (...args) => { - console.error(...args); -}); +import * as yargs from 'yargs'; +import { SendTextCommand } from './commands/SendTextCommand'; +import { StageCommand } from './commands/StageCommand'; +import { SideloadCommand } from './commands/SideloadCommand'; +import { ConvertToSquashfsCommand } from './commands/ConvertToSquashfsCommand'; +import { RekeyDeviceCommand } from './commands/RekeyDeviceCommand'; +import { CreateSignedPackageCommand } from './commands/CreateSignedPackageCommand'; +import { DeleteDevChannelCommand } from './commands/DeleteDevChannelCommand'; +import { CaptureScreenshotCommand } from './commands/CaptureScreenshotCommand'; +import { GetDeviceInfoCommand } from './commands/GetDeviceInfoCommand'; +import { GetDevIdCommand } from './commands/GetDevIdCommand'; +import { ZipCommand } from './commands/ZipCommand'; +import { KeyPressCommand } from './commands/KeyPressCommand'; +import { KeyUpCommand } from './commands/KeyUpCommand'; +import { KeyDownCommand } from './commands/KeyDownCommand'; +import { RemoteControlCommand } from './commands/RemoteControlCommand'; + +void yargs + + .command('sideload', 'Sideload a zip file or a folder to a remote Roku', (builder) => { + return builder + .option('zip', { type: 'string', description: 'The file to be sideloaded (instead of a folder), relative to cwd.', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The root folder to be sideloaded (instead of a zip file), relative to cwd.', demandOption: false }) + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands (like pressing the home button)', demandOption: false }) + .option('packagePort', { type: 'number', description: 'The port to use for sending a packaging to the device', demandOption: false }) + .option('close', { type: 'boolean', description: 'Close the channel before sideloading. Use --no-close to skip.', demandOption: false }) + .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }) + .option('remoteDebug', { type: 'boolean', description: 'Should the command be run in remote debug mode', demandOption: false }) + .option('remoteDebugConnectEarly', { type: 'boolean', description: 'Should the command connect to the debugger early', demandOption: false }) + .option('failOnCompileError', { type: 'boolean', description: 'Should the command fail if there is a compile error', demandOption: false }) + .option('deleteDevChannel', { type: 'boolean', description: 'Should the dev channel be deleted', demandOption: false }) + .option('appType', { type: 'string', description: 'The type of app to sideload. Use \'dcl\' for Device Component Libraries', choices: ['channel', 'dcl'], demandOption: false }) + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); + }, (args: any) => { + return new SideloadCommand().run(args); + }) + + .command('package', 'Create a signed package from an existing sideloaded dev channel', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }) + .option('signingPassword', { type: 'string', description: 'The password of the signing key', demandOption: false }) + .option('appTitle', { type: 'string', description: 'The title of the app to be signed', demandOption: false }) + .option('appVersion', { type: 'string', description: 'The version of the app to be signed', demandOption: false }) + .option('manifestPath', { type: 'string', description: 'The path to the manifest file, relative to cwd', demandOption: false }) + .option('out', { type: 'string', description: 'The location where the signed package will be saved, relative to cwd', demandOption: false, defaultDescription: './out/roku-deploy.pkg' }) + .option('devId', { type: 'string', description: 'The dev ID', demandOption: false }) + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); + }, (args: any) => { + return new CreateSignedPackageCommand().run(args); + }) + + .command('keyPress', 'send keypress command', (builder) => { + return builder + .option('key', { type: 'string', description: 'The key to send', demandOption: true }) + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) + .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); + }, (args: any) => { + return new KeyPressCommand().run(args); + }) + + .command('keyUp', 'send keyup command', (builder) => { + return builder + .option('key', { type: 'string', description: 'The key to send', demandOption: true }) + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) + .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); + }, (args: any) => { + return new KeyUpCommand().run(args); + }) + + .command('keyDown', 'send keydown command', (builder) => { + return builder + .option('key', { type: 'string', description: 'The key to send', demandOption: true }) + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) + .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); + }, (args: any) => { + return new KeyDownCommand().run(args); + }) + + .command('sendText', 'Send text command', (builder) => { + return builder + .option('text', { type: 'string', description: 'The text to send', demandOption: true }) + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) + .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); + }, (args: any) => { + return new SendTextCommand().run(args); + }) + + .command('remote-control', 'Provides a way to send a series of ECP key events similar to how Roku Remote Tool works but from the command line', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }); + }, (args: any) => { + return new RemoteControlCommand().run(args); + }) + + .command('stage', 'Copies all of the referenced files to the staging folder', (builder) => { + return builder + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: false }) + .option('files', { type: 'array', description: 'An array of source file paths indicating where the source files are', demandOption: false }) + .option('out', { type: 'string', description: 'The selected staging folder where all files will be copied to', demandOption: false }); + }, (args: any) => { + return new StageCommand().run(args); + }) + + .command('squash', 'Convert a pre-existing packaged zip file to a squashfs file', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }); + }, (args: any) => { + return new ConvertToSquashfsCommand().run(args); + }) + + .command('rekey', 'Rekey a device', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }) + .option('pkg', { type: 'string', description: 'The path to the signed package to be used for rekeying, relative to cwd', demandOption: false }) + .option('signingPassword', { type: 'string', description: 'The password of the signing key', demandOption: false }) + .option('devId', { type: 'string', description: 'The dev ID', demandOption: false }) + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); + }, (args: any) => { + return new RekeyDeviceCommand().run(args); + }) + + .command('deleteDevChannel', 'Delete an installed channel', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }); + }, (args: any) => { + return new DeleteDevChannelCommand().run(args); + }) + + .command('screenshot', 'Take a screenshot', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }) + .option('out', { type: 'string', description: 'The location where the screenshot will be saved relative to cwd', demandOption: false, defaultDescription: './out/roku-deploy.jpg' }) + .option('autoExtension', { type: 'boolean', description: 'Automatically handle file extension based on device response. When false (default), filename is used exactly as provided.', demandOption: false, default: false }) + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); + }, (args: any) => { + return new CaptureScreenshotCommand().run(args); + }) + + .command('getDeviceInfo', 'Get the `device-info` response from a Roku device', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }); + }, (args: any) => { + return new GetDeviceInfoCommand().run(args); + }) + + .command('getDevId', 'Get Dev ID', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }); + }, (args: any) => { + return new GetDevIdCommand().run(args); + }) + + .command('zip', 'Zip a folder into a package', (builder) => { + return builder + .option('dir', { type: 'string', description: 'The folder to be zipped', demandOption: true }) + .option('files', { type: 'array', description: 'Optional file patterns to filter which files are included (defaults to all files)', demandOption: false }) + .option('out', { type: 'string', description: 'The path to the zip file that will be created, relative to cwd', demandOption: false, alias: 'outZip' }) + .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); + }, (args: any) => { + return new ZipCommand().run(args); + }) + + .argv; diff --git a/src/commands/CaptureScreenshotCommand.ts b/src/commands/CaptureScreenshotCommand.ts new file mode 100644 index 00000000..ac3967cc --- /dev/null +++ b/src/commands/CaptureScreenshotCommand.ts @@ -0,0 +1,13 @@ +import { rokuDeploy, util } from '../index'; + +export class CaptureScreenshotCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.captureScreenshot(options); + } +} diff --git a/src/commands/ConvertToSquashfsCommand.ts b/src/commands/ConvertToSquashfsCommand.ts new file mode 100644 index 00000000..79ebf6ef --- /dev/null +++ b/src/commands/ConvertToSquashfsCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class ConvertToSquashfsCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.convertToSquashfs(options); + } +} diff --git a/src/commands/CreateSignedPackageCommand.ts b/src/commands/CreateSignedPackageCommand.ts new file mode 100644 index 00000000..e2d263df --- /dev/null +++ b/src/commands/CreateSignedPackageCommand.ts @@ -0,0 +1,13 @@ +import { rokuDeploy, util } from '../index'; + +export class CreateSignedPackageCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.createSignedPackage(options); + } +} diff --git a/src/commands/DeleteDevChannelCommand.ts b/src/commands/DeleteDevChannelCommand.ts new file mode 100644 index 00000000..5bfa2690 --- /dev/null +++ b/src/commands/DeleteDevChannelCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class DeleteDevChannelCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.deleteDevChannel(options); + } +} diff --git a/src/commands/GetDevIdCommand.ts b/src/commands/GetDevIdCommand.ts new file mode 100644 index 00000000..710dc923 --- /dev/null +++ b/src/commands/GetDevIdCommand.ts @@ -0,0 +1,12 @@ +import { rokuDeploy, util } from '../index'; + +export class GetDevIdCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + const devId = await rokuDeploy.getDevId(options); + console.log(devId); + } +} diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts new file mode 100644 index 00000000..3255fe3e --- /dev/null +++ b/src/commands/GetDeviceInfoCommand.ts @@ -0,0 +1,13 @@ +import { rokuDeploy } from '../index'; +import { util } from '../util'; + +export class GetDeviceInfoCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + const outputPath = await rokuDeploy.getDeviceInfo(options); + console.log(util.objectToTableString(outputPath)); + } +} diff --git a/src/commands/KeyDownCommand.ts b/src/commands/KeyDownCommand.ts new file mode 100644 index 00000000..ae05b912 --- /dev/null +++ b/src/commands/KeyDownCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class KeyDownCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.keyDown(options); + } +} diff --git a/src/commands/KeyPressCommand.ts b/src/commands/KeyPressCommand.ts new file mode 100644 index 00000000..4554df74 --- /dev/null +++ b/src/commands/KeyPressCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class KeyPressCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.keyPress(options); + } +} diff --git a/src/commands/KeyUpCommand.ts b/src/commands/KeyUpCommand.ts new file mode 100644 index 00000000..36b71ea3 --- /dev/null +++ b/src/commands/KeyUpCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class KeyUpCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.keyUp(options); + } +} diff --git a/src/commands/RekeyDeviceCommand.ts b/src/commands/RekeyDeviceCommand.ts new file mode 100644 index 00000000..ae4062a8 --- /dev/null +++ b/src/commands/RekeyDeviceCommand.ts @@ -0,0 +1,19 @@ +import { rokuDeploy, util } from '../index'; +import * as path from 'path'; + +export class RekeyDeviceCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + if (args.pkg) { + options.pkg = util.standardizePath( + path.resolve(args.cwd, args.pkg) + ); + } + await rokuDeploy.rekeyDevice(options); + } +} diff --git a/src/commands/RemoteControlCommand.ts b/src/commands/RemoteControlCommand.ts new file mode 100644 index 00000000..d03bc65d --- /dev/null +++ b/src/commands/RemoteControlCommand.ts @@ -0,0 +1,101 @@ +import * as readline from 'readline'; +import type { RokuKey } from '../index'; +import { rokuDeploy, util } from '../index'; + +export class RemoteControlCommand { + run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + + rokuDeploy.checkRequiredOptions(options, ['host']); + + readline.emitKeypressEvents(process.stdin); + process.stdin.setRawMode(true); + + process.stdin.on('keypress', (str, key) => { + const keyName = key.name as unknown; + let rokuDeployKeyName: RokuKey | undefined; + switch (keyName) { + case 'home': + rokuDeployKeyName = keyName; + break; + case 'escape': + rokuDeployKeyName = 'back'; + break; + case 'delete': + if (key.ctrl || key.meta || key.shift) { + rokuDeployKeyName = 'backspace'; + } + rokuDeployKeyName = 'back'; + break; + case 'backspace': + if (key.ctrl || key.meta || key.shift) { + rokuDeployKeyName = 'backspace'; + } else { + rokuDeployKeyName = 'instantreplay'; + } + break; + case 'end': + rokuDeployKeyName = 'play'; + break; + case 'return': + rokuDeployKeyName = 'select'; + break; + case 'up': + rokuDeployKeyName = 'up'; + if (key.shift) { + rokuDeployKeyName = 'volumeup'; + } + break; + case 'down': + rokuDeployKeyName = 'down'; + if (key.shift) { + rokuDeployKeyName = 'volumedown'; + } + break; + case 'left': + rokuDeployKeyName = 'left'; + if (key.shift) { + rokuDeployKeyName = 'rev'; + } + break; + case 'right': + rokuDeployKeyName = 'right'; + if (key.shift) { + rokuDeployKeyName = 'fwd'; + } + break; + default: + if (key.sequence === '*') { + rokuDeployKeyName = 'info'; + } else { + if (key.ctrl && key.name === 'c') { + process.exit(); // We provide a way to exit the program + } + + let text = key.name; + if (text === undefined) { + text = key.sequence; + } + + if (text === 'space') { + text = ' '; + } + + void rokuDeploy.sendText({ + text: text, ...options + }); + } + break; + } + + if (rokuDeployKeyName) { + void rokuDeploy.keyPress({ key: rokuDeployKeyName, ...options }); + } + }); + + console.log('Now receiving keyboard input. Press Ctrl+C to exit.\nescape=back, end=play, return=select, shift+left=rev, shift+right=fwd, shift+up=volumeup, shift+down=volumedown, *=options'); + } +} diff --git a/src/commands/SendTextCommand.ts b/src/commands/SendTextCommand.ts new file mode 100644 index 00000000..3e11810a --- /dev/null +++ b/src/commands/SendTextCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy, util } from '../index'; + +export class SendTextCommand { + async run(args) { + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.sendText(options); + } +} diff --git a/src/commands/SideloadCommand.ts b/src/commands/SideloadCommand.ts new file mode 100644 index 00000000..84d6232d --- /dev/null +++ b/src/commands/SideloadCommand.ts @@ -0,0 +1,14 @@ +import { rokuDeploy, util } from '../index'; + +export class SideloadCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + + await rokuDeploy.sideload(options); + } +} diff --git a/src/commands/StageCommand.ts b/src/commands/StageCommand.ts new file mode 100644 index 00000000..44217157 --- /dev/null +++ b/src/commands/StageCommand.ts @@ -0,0 +1,13 @@ +import { rokuDeploy, util } from '../index'; + +export class StageCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.stage(options); + } +} diff --git a/src/commands/ZipCommand.ts b/src/commands/ZipCommand.ts new file mode 100644 index 00000000..93e978ba --- /dev/null +++ b/src/commands/ZipCommand.ts @@ -0,0 +1,13 @@ +import { rokuDeploy, util } from '../index'; + +export class ZipCommand { + async run(args) { + args.cwd ??= process.cwd(); + + let options = { + ...util.getOptionsFromJson(args), + ...args + }; + await rokuDeploy.zip(options); + } +} diff --git a/src/device.spec.ts b/src/device.spec.ts index 660dc64a..98fdd133 100644 --- a/src/device.spec.ts +++ b/src/device.spec.ts @@ -1,26 +1,14 @@ -import * as assert from 'assert'; import * as fsExtra from 'fs-extra'; -import * as rokuDeploy from './index'; -import { cwd, expectPathExists, expectThrowsAsync, outDir, rootDir, tempDir, writeFiles } from './testUtils.spec'; +import { cwd, rootDir, tempDir, writeFiles } from './testUtils.spec'; import undent from 'undent'; //these tests are run against an actual roku device. These cannot be enabled when run on the CI server describe('device', function device() { - let options: rokuDeploy.RokuDeployOptions; beforeEach(() => { fsExtra.emptyDirSync(tempDir); fsExtra.ensureDirSync(rootDir); process.chdir(rootDir); - options = rokuDeploy.getOptions({ - outDir: outDir, - host: '192.168.1.93', - retainDeploymentArchive: true, - password: 'aaaa', - devId: 'c6fdc2019903ac3332f624b0b2c2fe2c733c3e74', - rekeySignedPackage: `${cwd}/testSignedPackage.pkg`, - signingPassword: 'drRCEVWP/++K5TYnTtuAfQ==' - }); writeFiles(rootDir, [ ['manifest', undent` @@ -61,59 +49,4 @@ describe('device', function device() { }); this.timeout(20000); - - describe('deploy', () => { - it('works', async () => { - options.retainDeploymentArchive = true; - let response = await rokuDeploy.deploy(options); - assert.equal(response.message, 'Successful deploy'); - }); - - it('Presents nice message for 401 unauthorized status code', async () => { - this.timeout(20000); - options.password = 'NOT_THE_PASSWORD'; - await expectThrowsAsync( - rokuDeploy.deploy(options), - `Unauthorized. Please verify credentials for host '${options.host}'` - ); - }); - }); - - describe('deployAndSignPackage', () => { - it('works', async () => { - await rokuDeploy.deleteInstalledChannel(options); - await rokuDeploy.rekeyDevice(options); - expectPathExists( - await rokuDeploy.deployAndSignPackage(options) - ); - }); - }); - - describe('validateDeveloperPassword', () => { - it('returns true when the password is correct', async () => { - const result = await rokuDeploy.rokuDeploy.validateDeveloperPassword({ - host: options.host, - password: options.password - }); - assert.strictEqual(result, true); - }); - - it('returns false when the password is wrong', async () => { - const result = await rokuDeploy.rokuDeploy.validateDeveloperPassword({ - host: options.host, - password: 'NOT_THE_PASSWORD' - }); - assert.strictEqual(result, false); - }); - - it('throws DeviceUnreachableError for an offline host', async () => { - await expectThrowsAsync(async () => { - await rokuDeploy.rokuDeploy.validateDeveloperPassword({ - host: '192.168.254.254', - password: 'aaaa', - timeout: 2000 - }); - }); - }); - }); }); diff --git a/src/index.ts b/src/index.ts index dff33ac6..2fc0ec77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,61 +1,6 @@ -import { RokuDeploy } from './RokuDeploy'; - //export everything from the RokuDeploy file export * from './RokuDeploy'; export * from './util'; export * from './RokuDeployOptions'; export * from './Errors'; export * from './DeviceInfo'; - -//create a new static instance of RokuDeploy, and export those functions for backwards compatibility -export const rokuDeploy = new RokuDeploy(); - -let createPackage = RokuDeploy.prototype.createPackage.bind(rokuDeploy); -let deleteInstalledChannel = RokuDeploy.prototype.deleteInstalledChannel.bind(rokuDeploy); -let deploy = RokuDeploy.prototype.deploy.bind(rokuDeploy); -let deployAndSignPackage = RokuDeploy.prototype.deployAndSignPackage.bind(rokuDeploy); -let getDestPath = RokuDeploy.prototype.getDestPath.bind(rokuDeploy); -let getDeviceInfo = RokuDeploy.prototype.getDeviceInfo.bind(rokuDeploy); -let getFilePaths = RokuDeploy.prototype.getFilePaths.bind(rokuDeploy); -let getOptions = RokuDeploy.prototype.getOptions.bind(rokuDeploy); -let getOutputPkgFilePath = RokuDeploy.prototype.getOutputPkgFilePath.bind(rokuDeploy); -let getOutputZipFilePath = RokuDeploy.prototype.getOutputZipFilePath.bind(rokuDeploy); -let normalizeFilesArray = RokuDeploy.prototype.normalizeFilesArray.bind(rokuDeploy); -let normalizeRootDir = RokuDeploy.prototype.normalizeRootDir.bind(rokuDeploy); -let parseManifest = RokuDeploy.prototype.parseManifest.bind(rokuDeploy); -let prepublishToStaging = RokuDeploy.prototype.prepublishToStaging.bind(rokuDeploy); -let pressHomeButton = RokuDeploy.prototype.pressHomeButton.bind(rokuDeploy); -let publish = RokuDeploy.prototype.publish.bind(rokuDeploy); -let rekeyDevice = RokuDeploy.prototype.rekeyDevice.bind(rokuDeploy); -let retrieveSignedPackage = RokuDeploy.prototype.retrieveSignedPackage.bind(rokuDeploy); -let signExistingPackage = RokuDeploy.prototype.signExistingPackage.bind(rokuDeploy); -let stringifyManifest = RokuDeploy.prototype.stringifyManifest.bind(rokuDeploy); -let takeScreenshot = RokuDeploy.prototype.takeScreenshot.bind(rokuDeploy); -let zipFolder = RokuDeploy.prototype.zipFolder.bind(rokuDeploy); -let zipPackage = RokuDeploy.prototype.zipPackage.bind(rokuDeploy); - -export { - createPackage, - deleteInstalledChannel, - deploy, - deployAndSignPackage, - getDestPath, - getDeviceInfo, - getFilePaths, - getOptions, - getOutputPkgFilePath, - getOutputZipFilePath, - normalizeFilesArray, - normalizeRootDir, - parseManifest, - prepublishToStaging, - pressHomeButton, - publish, - rekeyDevice, - retrieveSignedPackage, - signExistingPackage, - stringifyManifest, - takeScreenshot, - zipFolder, - zipPackage -}; diff --git a/src/util.spec.ts b/src/util.spec.ts index 714cc31b..1b3f2e78 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -1,7 +1,7 @@ -import { defer, util, standardizePath as s } from './util'; +import { util, standardizePath as s } from './util'; import { expect } from 'chai'; import * as fsExtra from 'fs-extra'; -import { tempDir } from './testUtils.spec'; +import { cwd, tempDir, rootDir } from './testUtils.spec'; import * as path from 'path'; import * as dns from 'dns'; import { createSandbox } from 'sinon'; @@ -14,6 +14,7 @@ describe('util', () => { }); afterEach(() => { + fsExtra.emptyDirSync(tempDir); sinon.restore(); }); @@ -26,14 +27,16 @@ describe('util', () => { }); }); - describe('toForwardSlashes', () => { - it('returns original value for non-strings', () => { - expect(util.toForwardSlashes(undefined)).to.be.undefined; - expect(util.toForwardSlashes(false)).to.be.false; + describe('standardizePathPosix', () => { + it('returns falsey value back unchanged', () => { + expect(util.standardizePathPosix(null)).to.eql(null); + expect(util.standardizePathPosix(undefined)).to.eql(undefined); + expect(util.standardizePathPosix(false as any)).to.eql(false); + expect(util.standardizePathPosix(0 as any)).to.eql(0); }); - it('converts mixed slashes to forward', () => { - expect(util.toForwardSlashes('a\\b/c\\d/e')).to.eql('a/b/c/d/e'); + it('always returns forward slashes', () => { + expect(util.standardizePathPosix('C:\\projects/some\\folder')).to.eql('C:/projects/some/folder'); }); }); @@ -107,10 +110,10 @@ describe('util', () => { }); describe('globAllByIndex', () => { - function writeFiles(filePaths: string[], cwd = tempDir) { + function writeFiles(filePaths: string[], dir = tempDir) { for (const filePath of filePaths) { fsExtra.outputFileSync( - path.resolve(cwd, filePath), + path.resolve(dir, filePath), '' ); } @@ -167,49 +170,6 @@ describe('util', () => { ]); }); - it('matches absolute glob paths case-insensitively on case-insensitive file systems', async function matchesAbsoluteGlobPathsCaseInsensitively() { - if (await util['getIsFileSystemCaseSensitive'](tempDir)) { - this.skip(); - } - writeFiles([ - 'components/ROKUtils/rokutils.brs' - ]); - await doTest([ - util.standardizePath(path.resolve(tempDir, 'components/ROKUtils/R*.brs')) - ], [ - [ - 'components/ROKUtils/rokutils.brs' - ] - ]); - }); - - it('caches file system case sensitivity by root path', async () => { - const previousCache = new Map(util['isFileSystemCaseSensitiveCache']); - util['isFileSystemCaseSensitiveCache'].clear(); - const outputFileSpy = sinon.spy(fsExtra, 'outputFile'); - const value1 = await util['getIsFileSystemCaseSensitive'](path.resolve(tempDir, 'folder1')); - const value2 = await util['getIsFileSystemCaseSensitive'](path.resolve(tempDir, 'folder2')); - expect(outputFileSpy.callCount).to.equal(1); - expect(value2).to.equal(value1); - outputFileSpy.restore(); - util['isFileSystemCaseSensitiveCache'].clear(); - for (const [key, value] of previousCache) { - util['isFileSystemCaseSensitiveCache'].set(key, value); - } - }); - - it('defaults to case-sensitive when the filesystem probe fails', async () => { - const previousCache = new Map(util['isFileSystemCaseSensitiveCache']); - util['isFileSystemCaseSensitiveCache'].clear(); - sinon.stub(fsExtra, 'outputFile').rejects(new Error('read-only filesystem')); - const value = await util['getIsFileSystemCaseSensitive'](path.resolve(tempDir, 'folder1')); - expect(value).to.equal(true); - util['isFileSystemCaseSensitiveCache'].clear(); - for (const [key] of previousCache) { - util['isFileSystemCaseSensitiveCache'].set(key, value); - } - }); - it('returns the same file path in multiple matches', async () => { writeFiles([ 'manifest', @@ -281,40 +241,6 @@ describe('util', () => { //shouldn't crash util['filterPaths']('*', [], '', 2); }); - - it('does not double-up the path when the negation pattern is absolute', () => { - const absoluteFile = s`${tempDir}/source/main.brs`; - const filesByIndex = [[absoluteFile]]; - // pattern is an absolute negation like `!C:/tempDir/source/main.brs` - util['filterPaths'](`!${absoluteFile}`, filesByIndex, s`${tempDir}`, 0); - // file should be filtered out — if path was doubled it would never match and the file would remain - expect(filesByIndex[0]).to.eql([]); - }); - }); - - describe('globAllByIndex absolute patterns', () => { - function writeFiles(filePaths: string[], cwd = tempDir) { - for (const filePath of filePaths) { - fsExtra.outputFileSync(path.resolve(cwd, filePath), ''); - } - } - - it('does not double-up path when pattern is absolute', async () => { - writeFiles(['source/main.brs']); - const absolutePattern = s`${tempDir}/source/main.brs`; - const results = await util.globAllByIndex([absolutePattern], tempDir); - expect(results[0]?.map(x => s(x))).to.eql([absolutePattern]); - }); - - it('correctly filters files when negation pattern is absolute', async () => { - writeFiles(['source/main.brs', 'source/lib.brs']); - const results = await util.globAllByIndex([ - '**/*.brs', - `!${s`${tempDir}/source/main.brs`}` - ], tempDir); - // main.brs should be filtered out; lib.brs should remain - expect(results[0]?.map(x => s(x)).sort()).to.eql([s`${tempDir}/source/lib.brs`]); - }); }); describe('dnsLookup', () => { @@ -352,84 +278,251 @@ describe('util', () => { }); }); - describe('defer', () => { - it('resolves the promise', async () => { - const deferred = defer(); - deferred.resolve(42); - expect(await deferred.promise).to.equal(42); - expect(deferred.isResolved).to.be.true; - expect(deferred.isCompleted).to.be.true; + describe('fileExistsCaseInsensitive', () => { + it('detects when a file does not exist inside a dir that does exist', async () => { + fsExtra.ensureDirSync(tempDir); + expect( + await util.fileExistsCaseInsensitive(s`${tempDir}/not-there`) + ).to.be.false; }); + }); - it('rejects the promise', async () => { - const deferred = defer(); - deferred.reject(new Error('boom')); - let caught: Error; - try { - await deferred.promise; - } catch (e) { - caught = e as Error; - } - expect(caught?.message).to.equal('boom'); - expect(deferred.isRejected).to.be.true; - expect(deferred.isCompleted).to.be.true; + describe('decodeHtmlEntities', () => { + it('decodes values properly', () => { + expect(util.decodeHtmlEntities(' ')).to.eql(' '); + expect(util.decodeHtmlEntities('&')).to.eql('&'); + expect(util.decodeHtmlEntities('"')).to.eql('"'); + expect(util.decodeHtmlEntities('<')).to.eql('<'); + expect(util.decodeHtmlEntities('>')).to.eql('>'); + expect(util.decodeHtmlEntities(''')).to.eql(`'`); }); + }); - it('tryResolve is a no-op once completed', async () => { - const deferred = defer(); - deferred.tryResolve(1); - deferred.tryResolve(2); - expect(await deferred.promise).to.equal(1); + describe('objectToTableString', () => { + it('should print an object to a table', () => { + const deviceInfo = { + 'device-id': '1234', + 'serial-number': 'abcd' + }; + + const result = util.objectToTableString(deviceInfo); + + const expectedOutput = [ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number abcd ' + ].join('\n'); + + expect(result).to.eql(expectedOutput); }); - it('tryReject is a no-op once completed', async () => { - const deferred = defer(); - deferred.tryResolve(1); - deferred.tryReject(new Error('should not surface')); - expect(await deferred.promise).to.equal(1); + it('should still print a table when a value is null', () => { + const deviceInfo = { + 'device-id': '1234', + 'serial-number': null + }; + + const result = util.objectToTableString(deviceInfo); + + const expectedOutput = [ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number undefined' + ].join('\n'); + + expect(result).to.eql(expectedOutput); }); + }); - it('tryReject rejects when not yet completed', async () => { - const deferred = defer(); - deferred.tryReject(new Error('boom')); - let caught: Error; - try { - await deferred.promise; - } catch (e) { - caught = e as Error; - } - expect(caught?.message).to.equal('boom'); + describe('normalizeRootDir', () => { + it('handles falsey values', () => { + expect(util.normalizeRootDir(null)).to.equal(cwd); + expect(util.normalizeRootDir(undefined)).to.equal(cwd); + expect(util.normalizeRootDir('')).to.equal(cwd); + expect(util.normalizeRootDir(' ')).to.equal(cwd); + expect(util.normalizeRootDir('\t')).to.equal(cwd); + }); + + it('handles non-falsey values', () => { + expect(util.normalizeRootDir(cwd)).to.equal(cwd); + expect(util.normalizeRootDir('./')).to.equal(cwd); + expect(util.normalizeRootDir('./testProject')).to.equal(path.join(cwd, 'testProject')); + }); + }); + + describe('getDestPath', () => { + it('handles unrelated exclusions properly', () => { + expect( + util.getDestPath( + s`${rootDir}/components/comp1/comp1.brs`, + [ + '**/*', + '!exclude.me' + ], + rootDir + ) + ).to.equal(s`components/comp1/comp1.brs`); + }); + + it('finds dest path for top-level path', () => { + expect( + util.getDestPath( + s`${rootDir}/components/comp1/comp1.brs`, + ['components/**/*'], + rootDir + ) + ).to.equal(s`components/comp1/comp1.brs`); + }); + + it('does not find dest path for non-matched top-level path', () => { + expect( + util.getDestPath( + s`${rootDir}/source/main.brs`, + ['components/**/*'], + rootDir + ) + ).to.be.undefined; }); - it('throws when reject is called twice', () => { - const deferred = defer(); - deferred.reject(new Error('first')); - //swallow the unhandled rejection - deferred.promise.catch(() => { }); - expect(() => deferred.reject(new Error('second'))).to.throw(/already rejected/); + it('excludes a file that is negated', () => { + expect( + util.getDestPath( + s`${rootDir}/source/main.brs`, + [ + 'source/**/*', + '!source/main.brs' + ], + rootDir + ) + ).to.be.undefined; + }); + + it('excludes file from non-rootdir top-level pattern', () => { + expect( + util.getDestPath( + s`${rootDir}/../externalDir/source/main.brs`, + [ + '!../externalDir/**/*' + ], + rootDir + ) + ).to.be.undefined; }); - it('throws when resolve is called twice', () => { - const deferred = defer(); - deferred.resolve(1); - expect(() => deferred.resolve(2)).to.throw(/already resolved/); + it('excludes a file that is negated in src;dest;', () => { + expect( + util.getDestPath( + s`${rootDir}/source/main.brs`, + [ + 'source/**/*', + { + src: '!source/main.brs' + } + ], + rootDir + ) + ).to.be.undefined; + }); + + it('works for brighterscript files', () => { + let destPath = util.getDestPath( + util.standardizePath(`${cwd}/src/source/main.bs`), + [ + 'manifest', + 'source/**/*.bs' + ], + s`${cwd}/src` + ); + expect(s`${destPath}`).to.equal(s`source/main.bs`); }); - it('throws when reject is called after resolve', () => { - const deferred = defer(); - deferred.resolve(1); - expect(() => deferred.reject(new Error('x'))).to.throw(/already resolved/); + it('excludes a file found outside the root dir', () => { + expect( + util.getDestPath( + s`${rootDir}/../source/main.brs`, + [ + '../source/**/*' + ], + rootDir + ) + ).to.be.undefined; }); }); - describe('decodeHtmlEntities', () => { - it('decodes values properly', () => { - expect(util.decodeHtmlEntities(' ')).to.eql(' '); - expect(util.decodeHtmlEntities('&')).to.eql('&'); - expect(util.decodeHtmlEntities('"')).to.eql('"'); - expect(util.decodeHtmlEntities('<')).to.eql('<'); - expect(util.decodeHtmlEntities('>')).to.eql('>'); - expect(util.decodeHtmlEntities(''')).to.eql(`'`); + describe('getOptionsFromJson', () => { + it('should fill in options from rokudeploy.json', () => { + fsExtra.outputJsonSync(s`${rootDir}/rokudeploy.json`, { password: 'password' }); + expect( + util.getOptionsFromJson({ cwd: rootDir }) + ).to.eql({ + password: 'password' + }); + }); + + it(`loads cwd from process`, () => { + try { + fsExtra.outputJsonSync(s`${process.cwd()}/rokudeploy.json`, { host: '1.2.3.4' }); + expect( + util.getOptionsFromJson() + ).to.eql({ + host: '1.2.3.4' + }); + } finally { + fsExtra.removeSync(s`${process.cwd()}/rokudeploy.json`); + } + }); + + it('catches invalid json with jsonc parser', () => { + fsExtra.writeJsonSync(s`${process.cwd()}/rokudeploy.json`, { host: '1.2.3.4' }); + sinon.stub(fsExtra, 'readFileSync').returns(` + { + "rootDir": "src" + `); + let ex; + try { + util.getOptionsFromJson(); + } catch (e) { + ex = e; + } + expect(ex).to.exist; + expect(ex.message.startsWith('Error parsing')).to.be.true; + fsExtra.removeSync(s`${process.cwd()}/rokudeploy.json`); + }); + + it('works when loading stagingDir from rokudeploy.json', () => { + sinon.stub(fsExtra, 'existsSync').callsFake((filePath) => { + return true; + }); + sinon.stub(fsExtra, 'readFileSync').returns(` + { + "stagingDir": "./staging-dir" + } + `); + let options = util.getOptionsFromJson(); + expect(options.stagingDir.endsWith('staging-dir')).to.be.true; + }); + + it('supports jsonc for rokudeploy.json', () => { + fsExtra.writeFileSync(s`${tempDir}/rokudeploy.json`, ` + //leading comment + { + //inner comment + "rootDir": "src" //trailing comment + } + //trailing comment + `); + let options = util.getOptionsFromJson({ cwd: tempDir }); + expect(options.rootDir).to.equal('src'); + }); + }); + + describe('computeFileDestPath', () => { + it('treats {src;dest} without dest as a top-level string', () => { + expect( + util['computeFileDestPath'](s`${rootDir}/source/main.brs`, { src: s`source/main.brs` } as any, rootDir) + ).to.eql(s`source/main.brs`); }); }); }); diff --git a/src/util.ts b/src/util.ts index 83b001aa..4442e30e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -6,6 +6,11 @@ import * as crypto from 'crypto'; import * as micromatch from 'micromatch'; // eslint-disable-next-line @typescript-eslint/no-require-imports import fastGlob = require('fast-glob'); +import type { FileEntry, RokuDeployOptions } from './RokuDeployOptions'; +import type { StandardizedFileEntry } from './RokuDeploy'; +import * as isGlob from 'is-glob'; +import * as picomatch from 'picomatch'; +import { parse as parseJsonc, printParseErrorCode, type ParseError } from 'jsonc-parser'; export class Util { //Map @@ -50,22 +55,21 @@ export class Util { if (!thePath) { return thePath; } - return path.normalize( - thePath.replace(/[\/\\]+/g, path.sep) - ); + return path.normalize(thePath).replace(/[\/\\]+/g, path.sep); } /** - * Convert all slashes to forward slashes + * Normalize path and replace all directory separators with current OS separators + * @param thePath */ - public toForwardSlashes(thePath: string) { - if (typeof thePath === 'string') { - return thePath.replace(/[\/\\]+/g, '/'); - } else { + public standardizePathPosix(thePath: string) { + if (!thePath) { return thePath; } + return path.normalize(thePath).replace(/[\/\\]+/g, '/'); } + /** * Do a case-insensitive string replacement * @param subject the string that will have its contents replaced @@ -145,8 +149,13 @@ export class Util { const isFileSystemCaseSensitive = await this.getIsFileSystemCaseSensitive(cwd); const globResults = patterns.map(async (pattern) => { - //force all windows-style slashes to unix style - pattern = pattern.replace(/\\/g, '/'); + //Canonicalize separators so callers can use either style: convert every backslash + //to a forward slash EXCEPT `\[` and `\]`. This is the one rule that disambiguates + //the overloaded Windows backslash without guessing: literal-bracket escapes are the + //only glob escape with no backslash-free alternative (use `[*]`/`[?]` for a literal + //`*`/`?`), and `path.*` joins never emit `\[`/`\]` on their own. So a surviving + //backslash can only be an intentional bracket escape; everything else was a separator. + pattern = pattern.replace(/\\(?![[\]])/g, '/'); //skip negated patterns (we will use them to filter later on) if (pattern.startsWith('!')) { return pattern; @@ -262,6 +271,273 @@ export class Util { }); } + /** + * Given an array of `FilesType`, normalize them each into a `StandardizedFileEntry`. + * Each entry in the array or inner `src` array will be extracted out into its own object. + * This makes it easier to reason about later on in the process. + * @param files + */ + /** + * Standardize a glob `src` pattern from the `files` array. fast-glob/micromatch require + * forward slashes as separators (a backslash is an escape char to them), so we normalize + * to posix slashes rather than the OS separator. Callers may pass either separator style + * (e.g. the backslashes that `path.join` produces on Windows); both canonicalize the same. + * Preserves: + * - the leading `!` glob-negation prefix that `path.normalize` would otherwise consume + * - literal-bracket glob escapes (`\[`, `\]`), the one escape with no backslash-free + * alternative, which `path.normalize` would otherwise collapse into path separators. + * (To match a literal `*`/`?` in a filename, use the `[*]`/`[?]` char-class form instead.) + */ + public standardizeSrcPattern(pattern: string) { + const isNegated = pattern.startsWith('!'); + const stripped = isNegated ? pattern.slice(1) : pattern; + //shield `\[` and `\]` escapes from path.normalize, then restore them afterward + const openEscape = '\0OPEN_BRACKET\0'; + const closeEscape = '\0CLOSE_BRACKET\0'; + const shielded = stripped + .replace(/\\\[/g, openEscape) + .replace(/\\\]/g, closeEscape); + const normalized = this.standardizePathPosix(shielded) + .replace(new RegExp(openEscape, 'g'), '\\[') + .replace(new RegExp(closeEscape, 'g'), '\\]'); + return isNegated ? '!' + normalized : normalized; + } + + public normalizeFilesArray(files: FileEntry[]) { + const result: Array = []; + + for (let i = 0; i < files.length; i++) { + let entry = files[i]; + //skip falsey and blank entries + if (!entry) { + continue; + + //string entries + } else if (typeof entry === 'string') { + result.push(this.standardizeSrcPattern(entry)); + + //objects with src: (string | string[]) + } else if ('src' in entry) { + //validate dest + if (entry.dest !== undefined && entry.dest !== null && typeof entry.dest !== 'string') { + throw new Error(`Invalid type for "dest" at index ${i} of files array`); + } + + //objects with src: string + if (typeof entry.src === 'string') { + result.push({ + src: this.standardizeSrcPattern(entry.src), + dest: util.standardizePath(entry.dest) + }); + + //objects with src:string[] + } else if ('src' in entry && Array.isArray(entry.src)) { + //create a distinct entry for each item in the src array + for (let srcEntry of entry.src) { + result.push({ + src: this.standardizeSrcPattern(srcEntry), + dest: util.standardizePath(entry.dest) + }); + } + } else { + throw new Error(`Invalid type for "src" at index ${i} of files array`); + } + } else { + throw new Error(`Invalid entry at index ${i} in files array`); + } + } + + return result; + } + + /** + * Given a full path to a file, determine its dest path + * @param srcPath the absolute path to the file. This MUST be a file path, and it is not verified to exist on the filesystem + * @param files the files array + * @param rootDir the absolute path to the root dir + * @param skipMatch - skip running the minimatch process (i.e. assume the file is a match + * @returns the RELATIVE path to the dest location for the file. + */ + public getDestPath(srcPathAbsolute: string, files: FileEntry[], rootDir: string, skipMatch = false) { + srcPathAbsolute = util.standardizePath(srcPathAbsolute); + rootDir = rootDir.replace(/\\+/g, '/'); + const entries = util.normalizeFilesArray(files); + + function makeGlobAbsolute(pattern: string) { + return path.resolve( + path.posix.join( + rootDir, + //remove leading exclamation point if pattern is negated + pattern + //coerce all slashes to forward + ) + ).replace(/\\/g, '/'); + } + + let result: string; + + //add the file into every matching cache bucket + for (let entry of entries) { + const pattern = (typeof entry === 'string' ? entry : entry.src); + //filter previous paths + if (pattern.startsWith('!')) { + const keepFile = picomatch('!' + makeGlobAbsolute(pattern.replace(/^!/, ''))); + if (!keepFile(srcPathAbsolute)) { + result = undefined; + } + } else { + const keepFile = picomatch(makeGlobAbsolute(pattern)); + if (keepFile(srcPathAbsolute)) { + try { + result = this.computeFileDestPath( + srcPathAbsolute, + entry, + util.standardizePath(rootDir) + ); + } catch { + //ignore errors...the file just has no dest path + } + } + } + } + return result; + } + + /** + * Compute the `dest` path. This accounts for magic globstars in the pattern, + * as well as relative paths based on the dest. This is only used internally. + * @param src an absolute, normalized path for a file + * @param dest the `dest` entry for this file. If omitted, files will derive their paths relative to rootDir. + * @param pattern the glob pattern originally used to find this file + * @param rootDir absolute normalized path to the rootDir + */ + public computeFileDestPath(srcPath: string, entry: StandardizedFileEntry | string, rootDir: string) { + let result: string; + let globstarIdx: number; + //files under rootDir with no specified dest + if (typeof entry === 'string') { + if (util.isParentOfPath(rootDir, srcPath, false)) { + //files that are actually relative to rootDir + result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + } else { + // result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + throw new Error('Cannot reference a file outside of rootDir when using a top-level string. Please use a src;des; object instead'); + } + + //non-glob-pattern explicit file reference + } else if (!isGlob(entry.src.replace(/\\/g, '/'), { strict: false })) { + let isEntrySrcAbsolute = path.isAbsolute(entry.src); + let entrySrcPathAbsolute = isEntrySrcAbsolute ? entry.src : util.standardizePath(`${rootDir}/${entry.src}`); + + let isSrcChildOfRootDir = util.isParentOfPath(rootDir, entrySrcPathAbsolute, false); + + let fileNameAndExtension = path.basename(entrySrcPathAbsolute); + + //no dest + if (entry.dest === null || entry.dest === undefined) { + //no dest, absolute path or file outside of rootDir + if (isEntrySrcAbsolute || isSrcChildOfRootDir === false) { + //copy file to root of staging folder + result = fileNameAndExtension; + + //no dest, relative path, lives INSIDE rootDir + } else { + //copy relative file structure to root of staging folder + let srcPathRelative = util.stringReplaceInsensitive(entrySrcPathAbsolute, rootDir, ''); + result = srcPathRelative; + } + + //assume entry.dest is the relative path to the folder AND file if applicable + } else if (entry.dest === '') { + result = fileNameAndExtension; + } else { + result = entry.dest; + } + //has a globstar + } else if ((globstarIdx = entry.src.indexOf('**')) > -1) { + const rootGlobstarPath = path.resolve(rootDir, entry.src.substring(0, globstarIdx)) + path.sep; + const srcPathRelative = util.stringReplaceInsensitive(srcPath, rootGlobstarPath, ''); + if (entry.dest) { + result = `${entry.dest}/${srcPathRelative}`; + } else { + result = srcPathRelative; + } + + //`pattern` is some other glob magic + } else { + const fileNameAndExtension = path.basename(srcPath); + if (entry.dest) { + result = util.standardizePath(`${entry.dest}/${fileNameAndExtension}`); + } else { + result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + } + } + + result = util.standardizePath( + //remove leading slashes + result.replace(/^[\/\\]+/, '') + ); + return result; + } + + /** + * Given a root directory, normalize it to a full path. + * Fall back to cwd if not specified + * @param rootDir + */ + public normalizeRootDir(rootDir: string) { + if (!rootDir || (typeof rootDir === 'string' && rootDir.trim().length === 0)) { + return process.cwd(); + } else { + return path.resolve(rootDir); + } + } + + public objectToTableString(deviceInfo: Record) { + const margin = 5; + const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; + const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '').toString().length)) + margin; + let table = []; + table.push('Name'.padEnd(keyWidth, ' ') + 'Value'.padEnd(keyWidth, ' ')); + table.push('-'.repeat(keyWidth + valueWidth)); + for (const [key, value] of Object.entries(deviceInfo)) { + table.push(key.padEnd(keyWidth, ' ') + value?.toString().padEnd(keyWidth, ' ')); + } + + return table.join('\n'); + } + + /** + * A function to fill in any missing arguments with JSON values + * Only run when CLI commands are used + */ + public getOptionsFromJson(options?: { cwd?: string; configPath?: string }) { + let fileOptions: RokuDeployOptions = {}; + const cwd = options?.cwd ?? process.cwd(); + const configPath = options?.configPath ?? path.join(cwd, 'rokudeploy.json'); + + if (fsExtra.existsSync(configPath)) { + let configFileText = fsExtra.readFileSync(configPath).toString(); + let parseErrors = [] as ParseError[]; + fileOptions = parseJsonc(configFileText, parseErrors, { + allowEmptyContent: true, + allowTrailingComma: true, + disallowComments: false + }); + if (parseErrors.length > 0) { + throw new Error(`Error parsing "${path.resolve(configPath)}": ` + JSON.stringify( + parseErrors.map(x => { + return { + message: printParseErrorCode(x.error), + offset: x.offset, + length: x.length + }; + }) + )); + } + } + return fileOptions; + } } export let util = new Util(); @@ -341,3 +617,16 @@ export function standardizePath(stringParts, ...expressions: any[]) { result.join('') ); } + +/** + * A tagged template literal function for standardizing the path and making all path separators forward slashes + */ +export function standardizePathPosix(stringParts, ...expressions: any[]) { + let result = []; + for (let i = 0; i < stringParts.length; i++) { + result.push(stringParts[i], expressions[i]); + } + return util.standardizePathPosix( + result.join('') + ); +} diff --git a/tsconfig.json b/tsconfig.json index 381ba267..5d1cd50a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ "include": [ "src/**/*.ts", "device.spec.ts" - ], +, "src/cli.ts" ], "ts-node": { "transpileOnly": true }