diff --git a/Cargo.lock b/Cargo.lock index 4e00094a..b4224a36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,7 +371,7 @@ checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.4", ] [[package]] @@ -528,6 +528,15 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "cozo-node" +version = "0.1.6" +dependencies = [ + "cozo", + "lazy_static", + "neon", +] + [[package]] name = "cozo-swift" version = "0.1.0" @@ -1396,6 +1405,16 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1642,6 +1661,47 @@ dependencies = [ "tempfile", ] +[[package]] +name = "neon" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +dependencies = [ + "neon-build", + "neon-macros", + "neon-runtime", + "semver", + "smallvec", +] + +[[package]] +name = "neon-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" + +[[package]] +name = "neon-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +dependencies = [ + "quote", + "syn", + "syn-mid", +] + +[[package]] +name = "neon-runtime" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +dependencies = [ + "cfg-if 1.0.0", + "libloading 0.6.7", + "smallvec", +] + [[package]] name = "nom" version = "5.1.2" @@ -2632,6 +2692,21 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.147" @@ -2914,6 +2989,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-mid" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "target-lexicon" version = "0.12.5" diff --git a/Cargo.toml b/Cargo.toml index 1d13fe3e..17c211b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,14 @@ #lto = true [workspace] -members = ["cozorocks", "cozo-lib-c", "cozo-lib-java", "cozo-core", "cozoserver", "cozo-lib-wasm", "cozo-lib-swift", "cozo-lib-python"] +members = [ + "cozo-core", + "cozorocks", + "cozoserver", + "cozo-lib-c", + "cozo-lib-java", + "cozo-lib-wasm", + "cozo-lib-swift", + "cozo-lib-python", + "cozo-lib-nodejs" +] diff --git a/cozo-lib-nodejs/.gitignore b/cozo-lib-nodejs/.gitignore new file mode 100644 index 00000000..dd227287 --- /dev/null +++ b/cozo-lib-nodejs/.gitignore @@ -0,0 +1,9 @@ +target +native/index.node +**/node_modules +**/.DS_Store +npm-debug.log* +_test_db/ +build/ +native/ +.idea/ diff --git a/cozo-lib-nodejs/Cargo.toml b/cozo-lib-nodejs/Cargo.toml new file mode 100644 index 00000000..3e53ed95 --- /dev/null +++ b/cozo-lib-nodejs/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "cozo-node" +version = "0.1.6" +description = "Cozo database for NodeJS" +authors = ["Ziyang Hu"] +license = "MIT/Apache-2.0/BSD-3-Clause" +edition = "2021" +exclude = ["index.node"] + +[profile.release] +lto = true + +[lib] +crate-type = ["cdylib"] + +[features] +#! # Features + +## Enables the `minimal`, `requests` and `graph-algo` features +compact = ["minimal", "requests", "graph-algo", "rayon"] +## Enables the `storage-sqlite` and `graph-algo` features +mobile = ["storage-sqlite", "graph-algo", "rayon"] +## Enables the `minimal`, `requests` and `graph-algo` features in single threaded mode +compact-single-threaded = ["minimal", "requests", "graph-algo"] +## Enables the `storage-sqlite` feature +minimal = ["storage-sqlite"] +## Enables the [Sqlite](https://www.sqlite.org/index.html) backend, also allows backup and restore with Sqlite data files. +storage-sqlite = ["cozo/storage-sqlite"] +## Enables the [RocksDB](http://rocksdb.org/) backend +storage-rocksdb = ["cozo/storage-rocksdb"] +## Enables the graph algorithms +graph-algo = ["cozo/graph-algo"] +## Allows the utilities to make web requests to fetch data +requests = ["cozo/requests"] +## Uses jemalloc as the global allocator, can make a difference in performance +jemalloc = ["cozo/jemalloc"] +## Enables io-uring option for the RocksDB storage +io-uring = ["cozo/io-uring"] +## Allows threading and enables the use of the `rayon` library for parallelizing algorithms +rayon = ["cozo/rayon"] +## Disallows the use of threads +nothread = ["cozo/nothread"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cozo = { version = "0.1.7", path = "../cozo-core", default-features = false } +lazy_static = "1.4.0" + +[dependencies.neon] +version = "0.10" +default-features = false +features = ["napi-6", "channel-api"] diff --git a/cozo-lib-nodejs/LICENSE.txt b/cozo-lib-nodejs/LICENSE.txt new file mode 100644 index 00000000..82d84369 --- /dev/null +++ b/cozo-lib-nodejs/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Ziyang Hu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/cozo-lib-nodejs/README.md b/cozo-lib-nodejs/README.md new file mode 100644 index 00000000..8b0b36d5 --- /dev/null +++ b/cozo-lib-nodejs/README.md @@ -0,0 +1,30 @@ +# `cozo-node` + +[![cozo-node](https://img.shields.io/npm/v/cozo-node)](https://www.npmjs.com/package/cozo-node) + +Embedded [CozoDB](https://github.com/cozodb/cozo) for NodeJS. + +## Installation + +```bash +npm install --save cozo-node +``` + +If that doesn't work because there are no precompiled binaries for your platform, +scroll below to the building section. + +## Usage + +Refer to the main [docs](https://github.com/cozodb/cozo#nodejs). + +## Building + +Building `cozo-node` requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support). + +Refer to the [script for linux](build_linux.sh), the [script for mac](build_mac.sh), +or the [script for windows](build_win.ps1) for the commands required. + +After building, `npm install .` will install the package. + +This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon). +To learn more about Neon, see the [Neon documentation](https://neon-bindings.com). diff --git a/cozo-lib-nodejs/build_linux.sh b/cozo-lib-nodejs/build_linux.sh new file mode 100755 index 00000000..30f7183d --- /dev/null +++ b/cozo-lib-nodejs/build_linux.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +rm -fr native +mkdir -p native/6 +cross build --target=x86_64-unknown-linux-gnu --release +mv target/x86_64-unknown-linux-gnu/release/libcozo_node.so native/6/index.node +npm run package diff --git a/cozo-lib-nodejs/build_mac.sh b/cozo-lib-nodejs/build_mac.sh new file mode 100755 index 00000000..63a0b4c9 --- /dev/null +++ b/cozo-lib-nodejs/build_mac.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +rm -fr native +mkdir -p native/6 +cargo build --release +mv target/release/libcozo_node.dylib native/6/index.node +yarn package diff --git a/cozo-lib-nodejs/build_win.ps1 b/cozo-lib-nodejs/build_win.ps1 new file mode 100644 index 00000000..223109dd --- /dev/null +++ b/cozo-lib-nodejs/build_win.ps1 @@ -0,0 +1,9 @@ +$ErrorActionPreference="Stop" + +if (test-path native/6/index.node) { + Remove-Item native/6/index.node +} + +cargo build --release +cp target/release/cozo_node.dll native/6/index.node +yarn package diff --git a/cozo-lib-nodejs/index.js b/cozo-lib-nodejs/index.js new file mode 100644 index 00000000..962555cd --- /dev/null +++ b/cozo-lib-nodejs/index.js @@ -0,0 +1,84 @@ +const binary = require('@mapbox/node-pre-gyp'); +const path = require('path'); +const binding_path = binary.find(path.resolve(path.join(__dirname, './package.json'))); +const native = require(binding_path); + +class CozoDb { + constructor(path) { + this.db_id = native.open_db(path) + } + + close() { + native.close_db(this.db_id) + } + + run(script, params) { + return new Promise((resolve, reject) => { + const params_str = JSON.stringify(params || {}) + native.query_db(this.db_id, script, params_str, (result_str) => { + const result = JSON.parse(result_str); + if (result.ok) { + resolve(result) + } else { + reject(result) + } + }) + }) + } + + exportRelations(rels) { + return new Promise((resolve, reject) => { + const rels_str = JSON.stringify(rels); + native.export_relations(rels_str, (result_str) => { + const result = JSON.parse(result_str); + if (result.ok) { + resolve(result) + } else { + reject(result) + } + }) + }) + } + + importRelation(relation, data) { + return new Promise((resolve, reject) => { + const rels_str = JSON.stringify({relation, data}); + native.import_relation(rels_str, (result_str) => { + const result = JSON.parse(result_str); + if (result.ok) { + resolve(result) + } else { + reject(result) + } + }) + }) + } + + backup(path) { + return new Promise((resolve, reject) => { + native.backup_db(path, (result_str) => { + const result = JSON.parse(result_str); + if (result.ok) { + resolve(result) + } else { + reject(result) + } + }) + }) + } + + restore(path) { + return new Promise((resolve, reject) => { + native.restore_db(path, (result_str) => { + const result = JSON.parse(result_str); + if (result.ok) { + resolve(result) + } else { + reject(result) + } + }) + }) + } +} + +module.exports = {CozoDb: CozoDb} diff --git a/cozo-lib-nodejs/package.json b/cozo-lib-nodejs/package.json new file mode 100644 index 00000000..ba123b86 --- /dev/null +++ b/cozo-lib-nodejs/package.json @@ -0,0 +1,46 @@ +{ + "name": "cozo-node", + "version": "0.1.6", + "description": "Cozo database for NodeJS", + "main": "index", + "files": [ + "index.js", + "LICENSE.txt" + ], + "binary": { + "module_name": "index", + "host": "https://github.com/cozodb/cozo-lib-nodejs/releases/download/", + "remote_path": "{version}", + "package_name": "{napi_build_version}-{platform}-{arch}.tar.gz", + "module_path": "./native/{napi_build_version}", + "pkg_path": ".", + "napi_versions": [ + 6 + ] + }, + "scripts": { + "install": "node-pre-gyp install", + "package": "node-pre-gyp package" + }, + "author": "Ziyang Hu", + "license": "MIT", + "devDependencies": { + "cargo-cp-artifact": "^0.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cozodb/cozo-lib-nodejs.git" + }, + "keywords": [ + "database", + "datalog", + "graph" + ], + "bugs": { + "url": "https://github.com/cozodb/cozo/issues" + }, + "homepage": "https://github.com/cozodb/cozo#readme", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10" + } +} diff --git a/cozo-lib-nodejs/src/lib.rs b/cozo-lib-nodejs/src/lib.rs new file mode 100644 index 00000000..c3e76980 --- /dev/null +++ b/cozo-lib-nodejs/src/lib.rs @@ -0,0 +1,245 @@ +/* + * Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. + */ +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Mutex; + +use lazy_static::lazy_static; +use neon::prelude::*; + +use cozo::*; + +#[derive(Default)] +struct Handles { + current: AtomicU32, + dbs: Mutex>, +} + +lazy_static! { + static ref HANDLES: Handles = Handles::default(); +} + +fn open_db(mut cx: FunctionContext) -> JsResult { + let kind = cx.argument::(0)?.value(&mut cx); + let path = cx.argument::(1)?.value(&mut cx); + match DbInstance::new(&kind, &path, Default::default()) { + Ok(db) => { + let id = HANDLES.current.fetch_add(1, Ordering::AcqRel); + let mut dbs = HANDLES.dbs.lock().unwrap(); + dbs.insert(id, db); + Ok(cx.number(id)) + } + Err(err) => { + let s = cx.string(format!("{:?}", err)); + cx.throw(s) + } + } +} + +fn close_db(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let mut dbs = HANDLES.dbs.lock().unwrap(); + dbs.remove(&id) + }; + Ok(cx.boolean(db.is_some())) +} + +fn query_db(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&id).cloned() + }; + match db_ref { + None => { + let s = cx.string("database already closed"); + cx.throw(s)? + } + Some(db) => db, + } + }; + + let query = cx.argument::(1)?.value(&mut cx); + let params = cx.argument::(2)?.value(&mut cx); + + let callback = cx.argument::(3)?.root(&mut cx); + + let channel = cx.channel(); + + std::thread::spawn(move || { + let result = db.run_script_str(&query, ¶ms); + channel.send(move |mut cx| { + let callback = callback.into_inner(&mut cx); + let this = cx.undefined(); + let json_str = cx.string(result); + callback.call(&mut cx, this, vec![json_str.upcast()])?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) +} + +fn backup_db(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&id).cloned() + }; + match db_ref { + None => { + let s = cx.string("database already closed"); + cx.throw(s)? + } + Some(db) => db, + } + }; + + let path = cx.argument::(1)?.value(&mut cx); + + let callback = cx.argument::(2)?.root(&mut cx); + + let channel = cx.channel(); + + std::thread::spawn(move || { + let result = db.backup_db_str(&path); + channel.send(move |mut cx| { + let callback = callback.into_inner(&mut cx); + let this = cx.undefined(); + let json_str = cx.string(result); + callback.call(&mut cx, this, vec![json_str.upcast()])?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) +} + +fn restore_db(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&id).cloned() + }; + match db_ref { + None => { + let s = cx.string("database already closed"); + cx.throw(s)? + } + Some(db) => db, + } + }; + + let path = cx.argument::(1)?.value(&mut cx); + + let callback = cx.argument::(2)?.root(&mut cx); + + let channel = cx.channel(); + + std::thread::spawn(move || { + let result = db.restore_backup_str(&path); + channel.send(move |mut cx| { + let callback = callback.into_inner(&mut cx); + let this = cx.undefined(); + let json_str = cx.string(result); + callback.call(&mut cx, this, vec![json_str.upcast()])?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) +} + +fn export_relations(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&id).cloned() + }; + match db_ref { + None => { + let s = cx.string("database already closed"); + cx.throw(s)? + } + Some(db) => db, + } + }; + + let rels = cx.argument::(1)?.value(&mut cx); + + let callback = cx.argument::(2)?.root(&mut cx); + + let channel = cx.channel(); + + std::thread::spawn(move || { + let result = db.export_relations_str(&rels); + channel.send(move |mut cx| { + let callback = callback.into_inner(&mut cx); + let this = cx.undefined(); + let json_str = cx.string(result); + callback.call(&mut cx, this, vec![json_str.upcast()])?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) +} + +fn import_relation(mut cx: FunctionContext) -> JsResult { + let id = cx.argument::(0)?.value(&mut cx) as u32; + let db = { + let db_ref = { + let dbs = HANDLES.dbs.lock().unwrap(); + dbs.get(&id).cloned() + }; + match db_ref { + None => { + let s = cx.string("database already closed"); + cx.throw(s)? + } + Some(db) => db, + } + }; + + let data = cx.argument::(1)?.value(&mut cx); + + let callback = cx.argument::(2)?.root(&mut cx); + + let channel = cx.channel(); + + std::thread::spawn(move || { + let result = db.import_relation_str(&data); + channel.send(move |mut cx| { + let callback = callback.into_inner(&mut cx); + let this = cx.undefined(); + let json_str = cx.string(result); + callback.call(&mut cx, this, vec![json_str.upcast()])?; + + Ok(()) + }); + }); + + Ok(cx.undefined()) +} + +#[neon::main] +fn main(mut cx: ModuleContext) -> NeonResult<()> { + cx.export_function("open_db", open_db)?; + cx.export_function("close_db", close_db)?; + cx.export_function("query_db", query_db)?; + cx.export_function("backup_db", backup_db)?; + cx.export_function("restore_db", restore_db)?; + cx.export_function("export_relations", export_relations)?; + cx.export_function("import_relation", import_relation)?; + Ok(()) +} diff --git a/cozo-lib-nodejs/yarn.lock b/cozo-lib-nodejs/yarn.lock new file mode 100644 index 00000000..7419f1e1 --- /dev/null +++ b/cozo-lib-nodejs/yarn.lock @@ -0,0 +1,383 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@mapbox/node-pre-gyp@^1.0.10": + version "1.0.10" + resolved "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +abbrev@1: + version "1.1.1" + resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +cargo-cp-artifact@^0.1: + version "0.1.6" + resolved "https://registry.npmmirror.com/cargo-cp-artifact/-/cargo-cp-artifact-0.1.6.tgz#df1bc9dad036ae0f4230639a869182e1d5850f89" + integrity sha512-CQw0doK/aaF7j041666XzuilHxqMxaKkn+I5vmBsd8SAwS0cO5CqVEVp0xJwOKstyqWZ6WK4Ww3O6p26x/Goyg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +debug@4: + version "4.3.4" + resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmmirror.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minipass@^3.0.0: + version "3.3.4" + resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5: + version "7.3.8" + resolved "https://registry.npmmirror.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +tar@^6.1.11: + version "6.1.12" + resolved "https://registry.npmmirror.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" + integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==