diff --git a/.eslintrc.yml b/.eslintrc.yml index 805981ea..6b2d1eab 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -213,3 +213,10 @@ overrides: - '@typescript-eslint' parserOptions: project: ./test_module/tsconfig.json + + - files: + - ./bench/**/*.{ts,js} + plugins: + - '@typescript-eslint' + parserOptions: + project: ./bench/tsconfig.json diff --git a/.github/workflows/linux-musl.yaml b/.github/workflows/linux-musl.yaml index 3bddee7d..c4ee0231 100644 --- a/.github/workflows/linux-musl.yaml +++ b/.github/workflows/linux-musl.yaml @@ -33,6 +33,9 @@ jobs: - name: 'Install node dependencies' run: docker run --rm -v $(pwd)/.cargo:/root/.cargo -v $(pwd):/napi-rs -w /napi-rs builder yarn + - name: 'Install swc musl' + run: docker run --rm -v $(pwd)/.cargo:/root/.cargo -v $(pwd):/napi-rs -w /napi-rs builder yarn add @swc/core-linux-musl --dev + - name: 'Build TypeScript' run: docker run --rm -v $(pwd)/.cargo:/root/.cargo -v $(pwd):/napi-rs -w /napi-rs builder yarn build diff --git a/Cargo.toml b/Cargo.toml index 21784d41..7610faa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ members = [ "./napi-derive-example", "./sys" ] -exclude = ["./test_module"] +exclude = ["./test_module", "./bench"] diff --git a/bench/Cargo.toml b/bench/Cargo.toml new file mode 100644 index 00000000..8392f495 --- /dev/null +++ b/bench/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bench" +version = "0.1.0" +authors = ["LongYinan "] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +futures = "0.3" +napi = { path = "../napi", features = ["libuv", "tokio_rt", "serde-json", "latin1"] } +napi-derive = { path = "../napi-derive" } +serde = "1" +serde_bytes = "0.11" +serde_derive = "1" +serde_json = "1" +tokio = { version = "0.2", features = ["default", "fs"]} + +[build-dependencies] +napi-build = { path = "../build" } + +[profile.release] +opt-level = 3 +lto = true diff --git a/bench/async.ts b/bench/async.ts new file mode 100644 index 00000000..bf9179a1 --- /dev/null +++ b/bench/async.ts @@ -0,0 +1,15 @@ +import b from 'benny' + +const { benchAsyncTask } = require('./index.node') + +const buffer = Buffer.from('hello 🚀 rust!') + +export const benchAsync = () => + b.suite( + 'Async task', + b.add('napi-rs', async () => { + await benchAsyncTask(buffer) + }), + b.cycle(), + b.complete(), + ) diff --git a/bench/bench.ts b/bench/bench.ts new file mode 100644 index 00000000..984fd4f2 --- /dev/null +++ b/bench/bench.ts @@ -0,0 +1,13 @@ +import { benchAsync } from './async' +import { benchNoop } from './noop' +import { benchPlus } from './plus' + +async function run() { + await benchNoop() + await benchPlus() + await benchAsync() +} + +run().catch((e) => { + console.error(e) +}) diff --git a/bench/build.rs b/bench/build.rs new file mode 100644 index 00000000..1f866b6a --- /dev/null +++ b/bench/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/bench/noop.ts b/bench/noop.ts new file mode 100644 index 00000000..0587be21 --- /dev/null +++ b/bench/noop.ts @@ -0,0 +1,19 @@ +import b from 'benny' + +const { noop: napiNoop } = require('./index.node') + +function noop() {} + +export const benchNoop = () => + b.suite( + 'noop', + b.add('napi-rs', () => { + napiNoop() + }), + b.add('JavaScript', () => { + noop() + }), + + b.cycle(), + b.complete(), + ) diff --git a/bench/package.json b/bench/package.json new file mode 100644 index 00000000..9c04c137 --- /dev/null +++ b/bench/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-module", + "version": "1.0.0", + "scripts": { + "build": "cargo build --release && node ../scripts/index.js build --release" + } +} diff --git a/bench/plus.ts b/bench/plus.ts new file mode 100644 index 00000000..c6313014 --- /dev/null +++ b/bench/plus.ts @@ -0,0 +1,21 @@ +import b from 'benny' + +const { plus } = require('./index.node') + +function plusJavascript(a: number, b: number) { + return a + b +} + +export const benchPlus = () => + b.suite( + 'Plus number', + b.add('napi-rs', () => { + plus(1, 100) + }), + b.add('JavaScript', () => { + plusJavascript(1, 100) + }), + + b.cycle(), + b.complete(), + ) diff --git a/bench/src/async_compute.rs b/bench/src/async_compute.rs new file mode 100644 index 00000000..7b50544d --- /dev/null +++ b/bench/src/async_compute.rs @@ -0,0 +1,30 @@ +use napi::{CallContext, Env, JsBuffer, JsNumber, JsObject, Module, Result, Task}; + +#[repr(transparent)] +struct BufferLength(&'static [u8]); + +impl Task for BufferLength { + type Output = usize; + type JsValue = JsNumber; + + fn compute(&mut self) -> Result { + Ok(self.0.len()) + } + + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result { + env.create_uint32(output as u32) + } +} + +#[js_function(1)] +fn bench_async_task(ctx: CallContext) -> Result { + let n = ctx.get::(0)?; + let task = BufferLength(n.data); + let async_promise = ctx.env.spawn(task)?; + Ok(async_promise.promise_object()) +} + +pub fn register_js(module: &mut Module) -> Result<()> { + module.create_named_method("benchAsyncTask", bench_async_task)?; + Ok(()) +} diff --git a/bench/src/lib.rs b/bench/src/lib.rs new file mode 100644 index 00000000..b1d9c318 --- /dev/null +++ b/bench/src/lib.rs @@ -0,0 +1,21 @@ +#[macro_use] +extern crate napi; +#[macro_use] +extern crate napi_derive; + +use napi::{Module, Result}; + +mod async_compute; +mod noop; +mod plus; + +register_module!(bench, init); + +fn init(module: &mut Module) -> Result<()> { + module.create_named_method("noop", noop::noop)?; + + async_compute::register_js(module)?; + plus::register_js(module)?; + + Ok(()) +} diff --git a/bench/src/noop.rs b/bench/src/noop.rs new file mode 100644 index 00000000..6e1fda57 --- /dev/null +++ b/bench/src/noop.rs @@ -0,0 +1,6 @@ +use napi::{ContextlessResult, Env, JsUndefined}; + +#[contextless_function] +pub fn noop(_env: Env) -> ContextlessResult { + Ok(None) +} diff --git a/bench/src/plus.rs b/bench/src/plus.rs new file mode 100644 index 00000000..5954630f --- /dev/null +++ b/bench/src/plus.rs @@ -0,0 +1,15 @@ +use std::convert::TryInto; + +use napi::{CallContext, JsNumber, Module, Result}; + +#[js_function(2)] +fn bench_plus(ctx: CallContext) -> Result { + let a: u32 = ctx.get::(0)?.try_into()?; + let b: u32 = ctx.get::(1)?.try_into()?; + ctx.env.create_uint32(a + b) +} + +pub fn register_js(module: &mut Module) -> Result<()> { + module.create_named_method("plus", bench_plus)?; + Ok(()) +} diff --git a/bench/tsconfig.json b/bench/tsconfig.json new file mode 100644 index 00000000..215b3b0d --- /dev/null +++ b/bench/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "target": "ESNext" + }, + "include": ["."], + "exclude": ["target", "src"] +} diff --git a/napi-derive/src/lib.rs b/napi-derive/src/lib.rs index 997c646a..9c201d84 100644 --- a/napi-derive/src/lib.rs +++ b/napi-derive/src/lib.rs @@ -1,5 +1,7 @@ extern crate proc_macro; +use std::mem; + use proc_macro::TokenStream; use proc_macro2::{Ident, Literal}; use quote::{format_ident, quote}; @@ -9,22 +11,25 @@ use syn::punctuated::Punctuated; use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token, Visibility}; struct ArgLength { - length: Option, + length: Literal, } impl Parse for ArgLength { fn parse(input: ParseStream) -> Result { let vars = Punctuated::::parse_terminated(input)?; Ok(ArgLength { - length: vars.first().map(|i| i.clone()), + length: vars + .first() + .map(|i| i.clone()) + .unwrap_or(Literal::usize_unsuffixed(0)), }) } } struct JsFunction { args: Vec, - name: Option, - signature: Option, + name: mem::MaybeUninit, + signature: mem::MaybeUninit, block: Vec, visibility: Visibility, } @@ -33,8 +38,8 @@ impl JsFunction { pub fn new() -> Self { JsFunction { args: vec![], - name: None, - signature: None, + name: mem::MaybeUninit::uninit(), + signature: mem::MaybeUninit::uninit(), visibility: Visibility::Inherited, block: vec![], } @@ -48,10 +53,10 @@ impl Fold for JsFunction { } fn fold_signature(&mut self, signature: Signature) -> Signature { - self.name = Some(format_ident!("{}", signature.ident)); + self.name = mem::MaybeUninit::new(format_ident!("{}", signature.ident)); let mut new_signature = signature.clone(); new_signature.ident = format_ident!("_{}", signature.ident); - self.signature = Some(new_signature); + self.signature = mem::MaybeUninit::new(new_signature); fold_signature(self, signature) } @@ -69,16 +74,18 @@ impl Fold for JsFunction { #[proc_macro_attribute] pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream { let arg_len = parse_macro_input!(attr as ArgLength); - let arg_len_span = arg_len.length.unwrap_or(Literal::usize_unsuffixed(0)); + let arg_len_span = arg_len.length; let input = parse_macro_input!(input as ItemFn); let mut js_fn = JsFunction::new(); js_fn.fold_item_fn(input); - let fn_name = js_fn.name.unwrap(); + let fn_name = unsafe { js_fn.name.assume_init() }; let fn_block = js_fn.block; - let signature = js_fn.signature.unwrap(); + let signature = unsafe { js_fn.signature.assume_init() }; let visibility = js_fn.visibility; let new_fn_name = signature.ident.clone(); + let execute_js_function = get_execute_js_code(new_fn_name, FunctionKind::JsFunction); let expanded = quote! { + #[inline] #signature #(#fn_block)* #visibility extern "C" fn #fn_name( @@ -112,30 +119,94 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream { let mut env = Env::from_raw(raw_env); let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, #arg_len_span, argc as usize); - match panic::catch_unwind(AssertUnwindSafe(move || #new_fn_name(ctx))).map_err(|e| { - let message = { - if let Some(string) = e.downcast_ref::() { - string.clone() - } else if let Some(string) = e.downcast_ref::<&str>() { - string.to_string() - } else { - format!("panic from Rust code: {:?}", e) - } - }; - Error::from_reason(message) - }).and_then(|v| v) - { - Ok(v) => v.raw_value(), - Err(e) => { - let message = format!("{}", e); - unsafe { - napi::sys::napi_throw_error(raw_env, ptr::null(), CString::from_vec_unchecked(message.into()).as_ptr() as *const c_char); - } - ptr::null_mut() - } - } + #execute_js_function } }; // Hand the output tokens back to the compiler TokenStream::from(expanded) } + +#[proc_macro_attribute] +pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + let mut js_fn = JsFunction::new(); + js_fn.fold_item_fn(input); + let fn_name = unsafe { js_fn.name.assume_init() }; + let fn_block = js_fn.block; + let signature = unsafe { js_fn.signature.assume_init() }; + let visibility = js_fn.visibility; + let new_fn_name = signature.ident.clone(); + let execute_js_function = get_execute_js_code(new_fn_name, FunctionKind::Contextless); + + let expanded = quote! { + #[inline] + #signature #(#fn_block)* + + #visibility extern "C" fn #fn_name( + raw_env: napi::sys::napi_env, + cb_info: napi::sys::napi_callback_info, + ) -> napi::sys::napi_value { + use std::io::Write; + use std::mem; + use std::os::raw::c_char; + use std::ptr; + use std::panic::{self, AssertUnwindSafe}; + use std::ffi::CString; + use napi::{Env, NapiValue, Error, Status}; + let mut has_error = false; + + let ctx = Env::from_raw(raw_env); + #execute_js_function + } + }; + // Hand the output tokens back to the compiler + TokenStream::from(expanded) +} + +enum FunctionKind { + Contextless, + JsFunction, +} + +#[inline] +fn get_execute_js_code( + new_fn_name: Ident, + function_kind: FunctionKind, +) -> proc_macro2::TokenStream { + let return_token_stream = match function_kind { + FunctionKind::Contextless => { + quote! { + Ok(Some(v)) => v.raw_value(), + Ok(None) => ptr::null_mut(), + } + } + FunctionKind::JsFunction => { + quote! { + Ok(v) => v.raw_value(), + } + } + }; + quote! { + match panic::catch_unwind(AssertUnwindSafe(move || #new_fn_name(ctx))).map_err(|e| { + let message = { + if let Some(string) = e.downcast_ref::() { + string.clone() + } else if let Some(string) = e.downcast_ref::<&str>() { + string.to_string() + } else { + format!("panic from Rust code: {:?}", e) + } + }; + Error::from_reason(message) + }).and_then(|v| v) { + #return_token_stream + Err(e) => { + let message = format!("{}", e); + unsafe { + napi::sys::napi_throw_error(raw_env, ptr::null(), CString::from_vec_unchecked(message.into()).as_ptr() as *const c_char); + } + ptr::null_mut() + } + } + } +} diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 663d39ac..586dfae0 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -126,6 +126,8 @@ pub use tokio_rt::shutdown as shutdown_tokio_rt; #[macro_use] extern crate serde; +pub type ContextlessResult = Result>; + /// register nodejs module /// /// ## Example diff --git a/package.json b/package.json index 9878a637..9048cf6b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,10 @@ ], "license": "MIT", "scripts": { + "bench": "cross-env SWC_NODE_PROJECT='./bench/tsconfig.json' node -r @swc-node/register bench/bench.ts", "build": "tsc -p tsconfig.json && chmod 777 scripts/index.js", + "build:bench": "yarn --cwd ./bench build", + "build:test": "yarn --cwd ./test_module build", "format": "run-p format:md format:json format:yaml format:source format:rs", "format:md": "prettier --parser markdown --write './**/*.md'", "format:json": "prettier --parser json --write './**/*.json'", @@ -55,12 +58,23 @@ "trailingComma": "all", "arrowParens": "always" }, - "files": ["scripts", "LICENSE"], + "files": [ + "scripts", + "LICENSE" + ], "lint-staged": { - "*.js": ["prettier --write"], - "*.@(yml|yaml)": ["prettier --parser yaml --write"], - "*.json": ["prettier --parser json --write"], - "*.md": ["prettier --parser markdown --write"] + "*.js": [ + "prettier --write" + ], + "*.@(yml|yaml)": [ + "prettier --parser yaml --write" + ], + "*.json": [ + "prettier --parser json --write" + ], + "*.md": [ + "prettier --parser markdown --write" + ] }, "husky": { "hooks": { @@ -77,6 +91,8 @@ "@typescript-eslint/eslint-plugin": "^4.3.0", "@typescript-eslint/parser": "^4.2.0", "ava": "^3.13.0", + "benny": "^3.6.14", + "cross-env": "^7.0.2", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-import": "^2.22.1", @@ -89,8 +105,5 @@ "source-map-support": "^0.5.19", "ts-node": "^9.0.0", "typescript": "^4.0.3" - }, - "optionalDependencies": { - "@swc/core-linux-musl": "^1.2.34" } } diff --git a/test_module/build.rs b/test_module/build.rs index 985e8b9b..1f866b6a 100644 --- a/test_module/build.rs +++ b/test_module/build.rs @@ -1,7 +1,5 @@ extern crate napi_build; fn main() { - use napi_build::setup; - - setup(); + napi_build::setup(); } diff --git a/tsconfig.json b/tsconfig.json index 309c5db5..cd3c23b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,5 @@ "lib": ["dom", "DOM.Iterable", "ES2019", "ES2020", "esnext"] }, "include": ["./src"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "bench"] } diff --git a/yarn.lock b/yarn.lock index 6a74caf5..7f9019f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,40 @@ # yarn lockfile v1 +"@arrows/array@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@arrows/array/-/array-1.4.0.tgz#4d0180b531da57e5de01f181d7f8abe6aea9b70f" + integrity sha512-kzd9z63lqqvyqkQ4kONGMTor3jl/zf4H7RvX4hoyG6mF7Ii01wx1JSHpDU5zhQIM+bqY7m4uaeqc+XADgKZLmA== + dependencies: + "@arrows/composition" "^1.0.0" + +"@arrows/composition@^1.0.0", "@arrows/composition@^1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@arrows/composition/-/composition-1.2.2.tgz#d0a213cac8f8c36c1c75856a1e6ed940c27e9169" + integrity sha512-9fh1yHwrx32lundiB3SlZ/VwuStPB4QakPsSLrGJFH6rCXvdrd060ivAZ7/2vlqPnEjBkPRRXOcG1YOu19p2GQ== + +"@arrows/dispatch@^1.0.2": + version "1.0.3" + resolved "https://registry.npmjs.org/@arrows/dispatch/-/dispatch-1.0.3.tgz#c4c06260f89e9dd4ce280df3712980aa2f3de976" + integrity sha512-v/HwvrFonitYZM2PmBlAlCqVqxrkIIoiEuy5bQgn0BdfvlL0ooSBzcPzTMrtzY8eYktPyYcHg8fLbSgyybXEqw== + dependencies: + "@arrows/composition" "^1.2.2" + +"@arrows/error@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@arrows/error/-/error-1.0.2.tgz#4e68036f901118ba6f1de88656ef6be49e650414" + integrity sha512-yvkiv1ay4Z3+Z6oQsUkedsQm5aFdyPpkBUQs8vejazU/RmANABx6bMMcBPPHI4aW43VPQmXFfBzr/4FExwWTEA== + +"@arrows/multimethod@^1.1.6": + version "1.1.7" + resolved "https://registry.npmjs.org/@arrows/multimethod/-/multimethod-1.1.7.tgz#bc7c26c3aa7703fc967e65da4f00718b1428eb4a" + integrity sha512-EjHD3XuGAV4G28rm7mu8k7zQJh/EOizh104/p9i2ofGcnL5mgKONFH/Bq6H3SJjM+WDAlKcR9WBpNhaAKCnH2g== + dependencies: + "@arrows/array" "^1.4.0" + "@arrows/composition" "^1.2.2" + "@arrows/error" "^1.0.2" + fast-deep-equal "^3.1.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -419,11 +453,6 @@ resolved "https://registry.npmjs.org/@swc/core-darwin/-/core-darwin-1.2.34.tgz#dd69b1a12b7a2cb9e43a5e8a78a8d2d7ff392f53" integrity sha512-OKOHOcqBgvBQiGC7doCa4CXKVDpO1QMIQogCFelqs82YbcQa6YEWvB6+ujiw1oUWaXP2fed1TFHDpHFJWorxIQ== -"@swc/core-linux-musl@^1.2.34": - version "1.2.34" - resolved "https://registry.npmjs.org/@swc/core-linux-musl/-/core-linux-musl-1.2.34.tgz#6853a747ce23dcb42754a04bdf753cb15f96a67d" - integrity sha512-et8kebSpVjWPNzuxqGALShhsIJymHzX63Auyqi9oyPiR9vEvMiIGvbTwH5eAHdM0Qc5iV0u8XyidHpoN2wtTYA== - "@swc/core-linux@^1.2.34": version "1.2.34" resolved "https://registry.npmjs.org/@swc/core-linux/-/core-linux-1.2.34.tgz#927fbdbfed1e842bbda4dc96d1407c5a0e67234d" @@ -662,6 +691,11 @@ ansi-colors@^4.1.1: resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.1" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -855,6 +889,30 @@ before-after-hook@^2.1.0: resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== +benchmark@^2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" + integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik= + dependencies: + lodash "^4.17.4" + platform "^1.3.3" + +benny@^3.6.14: + version "3.6.14" + resolved "https://registry.npmjs.org/benny/-/benny-3.6.14.tgz#961e9cfce2eae69585ad8a8aeeaff13fe6eff458" + integrity sha512-Y9O44pD4Woes+K6OQtIVh9FvghtAPr/CdncdYnc+p1bpQuRahU4GbtCGGwVQwxAbgR2CBpVJtf79cWv1ePcLWQ== + dependencies: + "@arrows/composition" "^1.0.0" + "@arrows/dispatch" "^1.0.2" + "@arrows/multimethod" "^1.1.6" + benchmark "^2.1.4" + fs-extra "^8.1.0" + json2csv "^4.5.3" + kleur "^3.0.3" + log-update "^3.3.0" + prettier "^1.18.2" + stats-median "^1.0.1" + binary-extensions@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" @@ -1012,6 +1070,13 @@ cli-boxes@^2.2.0: resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -1103,6 +1168,11 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^2.15.1: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^6.0.0: version "6.1.0" resolved "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc" @@ -1182,6 +1252,13 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cross-env@^7.0.2: + version "7.0.2" + resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" + integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== + dependencies: + cross-spawn "^7.0.1" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1193,7 +1270,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1788,6 +1865,15 @@ fromentries@^1.2.0: resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz#64c31665630479bc993cd800d53387920dc61b4d" integrity sha512-Xu2Qh8yqYuDhQGOhD5iJGninErSfI9A3FrriD3tjUgV5VbJFeH8vfgZ9HnC6jWN80QDVNQK5vmxRAmEAp7Mevw== +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1914,7 +2000,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -2372,6 +2458,15 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json2csv@^4.5.3: + version "4.5.4" + resolved "https://registry.npmjs.org/json2csv/-/json2csv-4.5.4.tgz#2b59c2869a137ec48cd2e243e0180466155f773f" + integrity sha512-YxBhY4Lmn8IvVZ36nqg5omxneLy9JlorkqW1j/EDCeqvmi+CQ4uM+wsvXlcIqvGDewIPXMC/O/oF8DX9EH5aoA== + dependencies: + commander "^2.15.1" + jsonparse "^1.3.1" + lodash.get "^4.4.2" + json5@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -2386,6 +2481,18 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -2393,6 +2500,11 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + latest-version@^5.0.0: version "5.1.0" resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -2507,7 +2619,12 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: version "4.17.20" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -2519,6 +2636,15 @@ log-symbols@^4.0.0: dependencies: chalk "^4.0.0" +log-update@^3.3.0: + version "3.4.0" + resolved "https://registry.npmjs.org/log-update/-/log-update-3.4.0.tgz#3b9a71e00ac5b1185cc193a36d654581c48f97b9" + integrity sha512-ILKe88NeMt4gmDvk/eb615U/IVn7K9KWGkoYbdatQ69Z65nj1ZzjM6fHXfcs0Uge+e+EGnMW7DY4T9yko8vWFg== + dependencies: + ansi-escapes "^3.2.0" + cli-cursor "^2.1.0" + wrap-ansi "^5.0.0" + log-update@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" @@ -2620,6 +2746,11 @@ mime-types@^2.1.21: dependencies: mime-db "1.44.0" +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -2815,6 +2946,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + onetime@^5.1.0: version "5.1.2" resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -3114,6 +3252,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +platform@^1.3.3: + version "1.3.6" + resolved "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -3145,6 +3288,11 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" +prettier@^1.18.2: + version "1.19.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + prettier@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" @@ -3329,6 +3477,14 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -3559,6 +3715,11 @@ stack-utils@^2.0.2: dependencies: escape-string-regexp "^2.0.0" +stats-median@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/stats-median/-/stats-median-1.0.1.tgz#ca8497cb1014d23d145db4d6fc93c8e815eed3ef" + integrity sha512-IYsheLg6dasD3zT/w9+8Iq9tcIQqqu91ZIpJOnIEM25C3X/g4Tl8mhXwW2ZQpbrsJISr9+wizEYgsibN5/b32Q== + string-argv@0.3.1: version "0.3.1" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -3622,7 +3783,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.1.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -3875,6 +4036,11 @@ universal-user-agent@^6.0.0: resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + update-notifier@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz#895fc8562bbe666179500f9f2cebac4f26323746" @@ -3981,6 +4147,15 @@ word-wrap@^1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wrap-ansi@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"