├── .gitignore ├── env.bat ├── src ├── pynode.hpp ├── main.cpp ├── helpers.h ├── jswrapper.h ├── worker.hpp ├── pywrapper.hpp ├── helpers.hpp ├── worker.cpp ├── pywrapper.cpp ├── jswrapper.c ├── pynode.cpp └── helpers.cpp ├── test_files ├── performance.py └── tools.py ├── .travis.yml ├── package.json ├── worker.js ├── CHANGELOG.md ├── worker_test.js ├── index.d.ts ├── binding.gyp ├── README.md ├── test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /Python-3.7.3 3 | /build 4 | /.vscode 5 | __pycache__ 6 | env.sh 7 | *.log -------------------------------------------------------------------------------- /env.bat: -------------------------------------------------------------------------------- 1 | set PATH=%USERPROFILE%\AppData\Local\Programs\Python\Python36;%PATH% 2 | set PYTHON=%USERPROFILE%\.windows-build-tools\python27\python.exe 3 | -------------------------------------------------------------------------------- /src/pynode.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_HPP 2 | #define PYNODE_HPP 3 | 4 | #include "napi.h" 5 | #include 6 | 7 | Napi::Object PyNodeInit(Napi::Env env, Napi::Object exports); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /test_files/performance.py: -------------------------------------------------------------------------------- 1 | from math import pow, atan, tan 2 | 3 | def generate_slow_number(num1, num2): 4 | base = num1 + num2 5 | result = 0 6 | for i in range(int(pow(base, 7))): 7 | result += atan(i) * tan(i) 8 | return result 9 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "pynode.hpp" 3 | #include 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | exports = PyNodeInit(env, exports); 7 | 8 | return exports; 9 | } 10 | 11 | NODE_API_MODULE(PyNode, Init) 12 | -------------------------------------------------------------------------------- /src/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_HELPERS_H 2 | #define PYNODE_HELPERS_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | PyObject * convert_napi_value_to_python(napi_env, napi_value); 11 | napi_value convert_python_to_napi_value(napi_env, PyObject *); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/jswrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_JSWRAPPER_HPP 2 | #define PYNODE_JSWRAPPER_HPP 3 | 4 | #include 5 | #include 6 | #include "helpers.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | PyMODINIT_FUNC PyInit_jswrapper(void); 13 | PyObject *WrappedJSObject_New(napi_env, napi_value); 14 | napi_value WrappedJSObject_get_napi_value(PyObject *); 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/worker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_WORKER_HPP 2 | #define PYNODE_WORKER_HPP 3 | 4 | #include "Python.h" 5 | #include "pywrapper.hpp" 6 | #include "helpers.hpp" 7 | #include "napi.h" 8 | 9 | class PyNodeWorker : public Napi::AsyncWorker { 10 | public: 11 | PyNodeWorker(Napi::Function &callback, PyObject *pyArgs, PyObject *pFunc); 12 | ~PyNodeWorker(); 13 | void Execute(); 14 | void OnOK(); 15 | void OnError(const Napi::Error &e); 16 | 17 | private: 18 | PyObject *pyArgs; 19 | PyObject *pFunc; 20 | PyObject *pValue; 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: node_js 3 | python: 4 | - '3.6' 5 | node_js: 6 | - '10' 7 | - '12' 8 | before_install: 9 | - sudo rm -f /usr/local/bin/python-config 10 | - sudo rm -f /usr/local/bin/python 11 | - sudo ln -s /opt/python/3.6.3/bin/python3-config /usr/local/bin/python-config 12 | - sudo ln -s /opt/python/3.6.3/bin/python3 /usr/local/bin/python 13 | - sudo ln -s /opt/python/3.6.3/include/python3.6m /usr/local/include/python3.6m 14 | - sudo ln -s /opt/python/3.6.3/lib/libpython3.6m.so /usr/local/lib/libpython3.6m.so 15 | - export LD_LIBRARY_PATH=/opt/python/3.6.3/lib:$LD_LIBRARY_PATH 16 | -------------------------------------------------------------------------------- /src/pywrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_PYWRAPPER_HPP 2 | #define PYNODE_PYWRAPPER_HPP 3 | 4 | #include "Python.h" 5 | #include "helpers.hpp" 6 | #include "napi.h" 7 | 8 | class PyNodeWrappedPythonObject : public Napi::ObjectWrap { 9 | public: 10 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 11 | PyNodeWrappedPythonObject(const Napi::CallbackInfo &info); 12 | static Napi::FunctionReference constructor; 13 | Napi::Value Call(const Napi::CallbackInfo &info); 14 | Napi::Value GetAttr(const Napi::CallbackInfo &info); 15 | Napi::Value Repr(const Napi::CallbackInfo &info); 16 | PyObject * getValue() { return _value; }; 17 | 18 | private: 19 | PyObject * _value; 20 | }; 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fridgerator/pynode", 3 | "version": "0.5.2", 4 | "description": "Node <-> Python interopability", 5 | "main": "build/Release/PyNode", 6 | "author": "Nick Franken ", 7 | "license": "MIT", 8 | "homepage": "https://github.com/fridgerator/PyNode", 9 | "keywords": [ 10 | "node.js", 11 | "python" 12 | ], 13 | "scripts": { 14 | "build": "node-gyp build", 15 | "compile": "node-gyp rebuild", 16 | "install": "node-gyp rebuild", 17 | "test": "yarn build && mocha" 18 | }, 19 | "dependencies": { 20 | "node-addon-api": "^1.7.1" 21 | }, 22 | "devDependencies": { 23 | "chai": "^4.2.0", 24 | "mocha": "^6.1.4" 25 | }, 26 | "gypfile": true, 27 | "types": "index.d.ts", 28 | "engines": { 29 | "node": ">=10" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads') 2 | const pynode = require('./build/Release/PyNode') 3 | 4 | const longRunningFunction = () => { 5 | return new Promise((resolve, reject) => { 6 | console.log('cwd : ', process.cwd()) 7 | 8 | pynode.startInterpreter() 9 | pynode.appendSysPath('./test_files') 10 | pynode.openFile('performance') 11 | 12 | pynode.call('generate_slow_number', 5, 7, (err, result) => { 13 | if (err) { 14 | reject(err) 15 | return 16 | } 17 | resolve(result) 18 | }) 19 | }) 20 | } 21 | 22 | longRunningFunction() 23 | .then(result => { 24 | console.log('result : ', result) 25 | parentPort.postMessage(result) 26 | }) 27 | .catch(e => { 28 | console.log('err : ', e) 29 | parentPort.postMessage({error: e}) 30 | }) 31 | -------------------------------------------------------------------------------- /test_files/tools.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from datetime import timedelta 3 | import random 4 | 5 | def time_series_data(): 6 | now = datetime.now() 7 | data = [] 8 | for _ in range(50): 9 | now = now + timedelta(minutes = 10) 10 | data.append([ 11 | str(now), 12 | random.random() 13 | ]) 14 | # print(data, flush=True) 15 | return data 16 | 17 | def sum_items(arr): 18 | return sum(arr) 19 | 20 | def multiply(a,b): 21 | return a * b 22 | 23 | def return_immediate(x): 24 | # print(x, type(x)) 25 | # print(x, flush=True) 26 | return x 27 | 28 | def return_none(): 29 | return None 30 | 31 | def merge_two_dicts(x, y): 32 | z = x.copy() 33 | z.update(y) 34 | return z 35 | 36 | def return_dict(): 37 | x = {'size': 71589, 'min': -99.6654762642, 'max': 879.08351843} 38 | return x 39 | 40 | def return_tuple(): 41 | x = (1, 2, 3) 42 | return x 43 | 44 | def causes_runtime_error(): 45 | first = 1 46 | second = 2 47 | return first + secon 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.4.2] - 2019-10-17 8 | ### Fixed 9 | * Fixed depreciation warnings, and differences between node 10 - 12 10 | 11 | ## [0.4.1] - 2019-10-16 12 | ### Fixed 13 | * Fixed multithreading bug, pynode can now run in workers (multiple threads created by node.js) 14 | 15 | ## [0.4.0] - 2019-10-11 16 | ### Added 17 | * Thread safety 18 | * Throw exception if function cannot be found 19 | 20 | ## [0.3.4] - 2019-10-07 21 | ### Fixed 22 | * Works in node 12 now. 23 | 24 | [0.4.2]: https://github.com/fridgerator/PyNode/compare/v0.4.1...v0.4.2 25 | [0.4.1]: https://github.com/fridgerator/PyNode/compare/v0.4.0...v0.4.1 26 | [0.4.0]: https://github.com/fridgerator/PyNode/compare/v0.3.4...v0.4.0 27 | [0.3.4]: https://github.com/fridgerator/PyNode/compare/v0.3.3...v0.3.4 28 | -------------------------------------------------------------------------------- /worker_test.js: -------------------------------------------------------------------------------- 1 | const { Worker, isMainThread, parentPort, workerData } = require('worker_threads') 2 | 3 | const TIMEOUT = 1000 4 | 5 | const threadCount = 3 6 | const threads = new Set() 7 | for (let i = 0; i < threadCount; i++) { 8 | setTimeout(() => { 9 | threads.add( 10 | new Worker(`${process.cwd()}/worker.js`) 11 | ) 12 | }, i * TIMEOUT) 13 | } 14 | 15 | setTimeout(() => { 16 | console.log('threads : ', threads.size) 17 | for (let worker of threads) { 18 | worker.on('message', msg => { 19 | console.log('msg : ', msg) 20 | }) 21 | worker.on('error', err => { 22 | console.log('worker err : ', err) 23 | threads.delete(worker) 24 | if (threads.size === 0) { 25 | console.log('done') 26 | process.exit(0) 27 | } 28 | }) 29 | worker.on('exit', () => { 30 | console.log('worker done') 31 | threads.delete(worker) 32 | if (threads.size === 0) { 33 | console.log('done') 34 | process.exit(0) 35 | } 36 | }) 37 | } 38 | }, (threadCount * TIMEOUT) + 100) 39 | 40 | setInterval(() => { 41 | console.log('.') 42 | }, 1000) 43 | -------------------------------------------------------------------------------- /src/helpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PYNODE_HELPERS_HPP 2 | #define PYNODE_HELPERS_HPP 3 | 4 | #include "napi.h" 5 | #include "pywrapper.hpp" 6 | #include "helpers.h" 7 | #include 8 | 9 | /* entry points to threads should grab a py_thread_context for the duration of the thread */ 10 | class py_thread_context { 11 | public: 12 | py_thread_context() { gstate = PyGILState_Ensure(); } 13 | 14 | ~py_thread_context() { 15 | PyGILState_Release(gstate); 16 | if (PyGILState_Check() == 1) 17 | pts = PyEval_SaveThread(); 18 | } 19 | 20 | private: 21 | PyGILState_STATE gstate; 22 | PyThreadState *pts; 23 | }; 24 | 25 | /* anywhere needing to call python functions (that isn't using py_thread_context) should create a py_ensure_gil object */ 26 | class py_ensure_gil { 27 | public: 28 | py_ensure_gil() { 29 | gstate = PyGILState_Ensure(); 30 | } 31 | 32 | ~py_ensure_gil() { 33 | PyGILState_Release(gstate); 34 | } 35 | 36 | private: 37 | PyGILState_STATE gstate; 38 | }; 39 | 40 | // v8 to Python 41 | PyObject *BuildPyArray(Napi::Env env, Napi::Value arg); 42 | PyObject *BuildPyDict(Napi::Env env, Napi::Value arg); 43 | PyObject *BuildWrappedJSObject(Napi::Env env, Napi::Value arg); 44 | PyObject *BuildPyArgs(const Napi::CallbackInfo &info, size_t start_index, size_t count); 45 | PyObject *ConvertToPython(Napi::Value); 46 | 47 | // Python to v8 48 | Napi::Array BuildV8Array(Napi::Env env, PyObject *obj); 49 | Napi::Object BuildV8Dict(Napi::Env env, PyObject *obj); 50 | Napi::Value ConvertFromPython(Napi::Env env, PyObject *obj); 51 | 52 | int Py_GetNumArguments(PyObject *pFunc); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@fridgerator/pynode' { 2 | /** 3 | * Fixes dyanmic linking issue in python 4 | * @param dlFile Python shared library file name 5 | */ 6 | export function dlOpen(dlFile: string): void; 7 | 8 | /** 9 | * Initialize the Python interpreter 10 | * 11 | * @param pythonpath Optionally set the python module search paths 12 | */ 13 | export function startInterpreter(pythonpath?: string): void; 14 | 15 | /** 16 | * Stops the Python interpreter 17 | */ 18 | // export function stopInterpreter(pythonpath?: string): void; 19 | 20 | /** 21 | * Add an additional path as a python module search path 22 | * 23 | * @param path Path to append to python `sys.path` 24 | */ 25 | export function appendSysPath(path: string): void; 26 | 27 | /** 28 | * Open (import) python file as the main PyNode module. 29 | * Only one file can be open at a time 30 | * 31 | * @param filename Python file to open, leave off .py extension 32 | */ 33 | export function openFile(filename: string): void; 34 | 35 | /** 36 | * Call a function from the opened python module 37 | * 38 | * @param functionName Name of function to call 39 | * @param args Arguments to python function 40 | * 41 | * @example 42 | * // in python file test.py 43 | * def add(a, b): 44 | * return a + b 45 | * 46 | * const pynode = require('@fridgerator/pynode') 47 | * pynode.startInterpreter() 48 | * pynode.openFile('test') 49 | * let x = pynode.call('add', 1, 2) 50 | * x === 3 // true 51 | */ 52 | export function call(functionName: string, ...args: any[]): void; 53 | namespace call { 54 | function __promisify__(functionName: string, ...args: any[]): Promise; 55 | } 56 | 57 | /** 58 | * Pass a string directly to the python interpreter. 59 | * Returns 0 if the call was successful, -1 if there was an exception. 60 | * @param statement String statement to be evaluated 61 | */ 62 | export function eval(statement: string): number; 63 | } 64 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "PyNode", 5 | "sources": [ 6 | "src/main.cpp", 7 | "src/helpers.cpp", 8 | "src/pynode.cpp", 9 | "src/worker.cpp", 10 | "src/pywrapper.cpp", 11 | "src/jswrapper.c" 12 | ], 13 | 'include_dirs': [ 14 | " 3 | #include 4 | #include 5 | 6 | PyNodeWorker::PyNodeWorker(Napi::Function &callback, PyObject *pyArgs, 7 | PyObject *pFunc) 8 | : Napi::AsyncWorker(callback), pyArgs(pyArgs), pFunc(pFunc){}; 9 | PyNodeWorker::~PyNodeWorker(){}; 10 | 11 | void PyNodeWorker::Execute() { 12 | { 13 | py_thread_context ctx; 14 | 15 | pValue = PyObject_CallObject(pFunc, pyArgs); 16 | Py_DECREF(pyArgs); 17 | PyObject *errOccurred = PyErr_Occurred(); 18 | 19 | if (errOccurred != NULL) { 20 | std::string error; 21 | PyObject *pType, *pValue, *pTraceback, *pTypeString; 22 | PyErr_Fetch(&pType, &pValue, &pTraceback); 23 | const char *value = PyUnicode_AsUTF8(pValue); 24 | pTypeString = PyObject_Str(pType); 25 | const char *type = PyUnicode_AsUTF8(pTypeString); 26 | PyTracebackObject *tb = (PyTracebackObject *)pTraceback; 27 | _frame *frame = tb->tb_frame; 28 | 29 | while (frame != NULL) { 30 | int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti); 31 | const char *filename = PyUnicode_AsUTF8(frame->f_code->co_filename); 32 | const char *funcname = PyUnicode_AsUTF8(frame->f_code->co_name); 33 | if (filename) { 34 | error.append("File \"" + std::string(filename) + "\""); 35 | } 36 | if (funcname) { 37 | error.append(" Line " + std::to_string(line) + ", in " + std::string(funcname) + 38 | "\n"); 39 | } 40 | error.append(std::string(type) + ": " + std::string(value)); 41 | frame = frame->f_back; 42 | } 43 | 44 | Py_DecRef(errOccurred); 45 | Py_DecRef(pTypeString); 46 | PyErr_Restore(pType, pValue, pTraceback); 47 | PyErr_Print(); 48 | SetError(error); 49 | } 50 | } 51 | } 52 | 53 | void PyNodeWorker::OnOK() { 54 | py_thread_context ctx; 55 | Napi::HandleScope scope(Env()); 56 | 57 | std::vector result = {Env().Null(), Env().Null()}; 58 | 59 | if (pValue != NULL) { 60 | result[1] = ConvertFromPython(Env(), pValue); 61 | Py_DECREF(pValue); 62 | } else { 63 | std::string error; 64 | error.append("Function call failed"); 65 | Py_DECREF(pFunc); 66 | PyErr_Print(); 67 | 68 | result[0] = Napi::Error::New(Env(), error).Value(); 69 | } 70 | Callback().Call({result[0], result[1]}); 71 | } 72 | 73 | void PyNodeWorker::OnError(const Napi::Error &e) { 74 | Callback().Call({Napi::Error::New(Env(), e.what()).Value()}); 75 | } 76 | -------------------------------------------------------------------------------- /src/pywrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "pywrapper.hpp" 2 | #include 3 | #include 4 | 5 | Napi::Object PyNodeWrappedPythonObject::Init(Napi::Env env, Napi::Object exports) { 6 | // This method is used to hook the accessor and method callbacks 7 | Napi::Function func = DefineClass(env, "PyNodeWrappedPythonObject", { 8 | InstanceMethod("call", &PyNodeWrappedPythonObject::Call), 9 | InstanceMethod("get", &PyNodeWrappedPythonObject::GetAttr), 10 | InstanceMethod("repr", &PyNodeWrappedPythonObject::Repr) 11 | }); 12 | 13 | // Create a peristent reference to the class constructor. This will allow 14 | // a function called on a class prototype and a function 15 | // called on instance of a class to be distinguished from each other. 16 | constructor = Napi::Persistent(func); 17 | // Call the SuppressDestruct() method on the static data prevent the calling 18 | // to this destructor to reset the reference when the environment is no longer 19 | // available. 20 | constructor.SuppressDestruct(); 21 | exports.Set("PyNodeWrappedPythonObject", func); 22 | return exports; 23 | } 24 | 25 | PyNodeWrappedPythonObject::PyNodeWrappedPythonObject(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { 26 | //Napi::Env env = info.Env(); 27 | // ... 28 | PyObject * value = info[0].As>().Data(); 29 | this->_value = value; 30 | Py_INCREF(this->_value); 31 | // TODO - Py_DECREF in destructor 32 | } 33 | 34 | Napi::FunctionReference PyNodeWrappedPythonObject::constructor; 35 | 36 | Napi::Value PyNodeWrappedPythonObject::GetAttr(const Napi::CallbackInfo &info){ 37 | py_ensure_gil ctx; 38 | Napi::Env env = info.Env(); 39 | std::string attrname = info[0].As().ToString(); 40 | PyObject * attr = PyObject_GetAttrString(this->_value, attrname.c_str()); 41 | if (attr == NULL) { 42 | std::string error("Attribute " + attrname + " not found."); 43 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 44 | return env.Null(); 45 | } 46 | Napi::Value returnval = ConvertFromPython(env, attr); 47 | return returnval; 48 | } 49 | 50 | Napi::Value PyNodeWrappedPythonObject::Call(const Napi::CallbackInfo &info){ 51 | py_ensure_gil ctx; 52 | Napi::Env env = info.Env(); 53 | int callable = PyCallable_Check(this->_value); 54 | if (! callable) { 55 | std::string error("This Python object is not callable."); 56 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 57 | return env.Null(); 58 | } 59 | PyObject * pArgs = BuildPyArgs(info, 0, info.Length()); 60 | PyObject * pReturnValue = PyObject_Call(this->_value, pArgs, NULL); 61 | Py_DECREF(pArgs); 62 | PyObject *error_occurred = PyErr_Occurred(); 63 | if (error_occurred != NULL) { 64 | // TODO - get the traceback string into Javascript 65 | std::string error("A Python error occurred."); 66 | PyErr_Print(); 67 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 68 | return env.Null(); 69 | } 70 | Napi::Value returnval = ConvertFromPython(env, pReturnValue); 71 | Py_DECREF(pReturnValue); 72 | return returnval; 73 | } 74 | 75 | Napi::Value PyNodeWrappedPythonObject::Repr(const Napi::CallbackInfo &info){ 76 | py_ensure_gil ctx; 77 | Napi::Env env = info.Env(); 78 | std::string attrname = info[0].As().ToString(); 79 | PyObject * repr = PyObject_Repr(this->_value); 80 | if (repr == NULL) { 81 | std::string error("repr() failed."); 82 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 83 | return env.Null(); 84 | } 85 | const char * repr_c = PyUnicode_AsUTF8(repr); 86 | Napi::Value result = Napi::String::New(env, repr_c); // (Napi takes ownership of repr_c) 87 | return result; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I'm not maintaining this project anymore. If I needed this solution today I would look into using [deno_python](https://github.com/denosaurs/deno_python) which runs on Deno and Bun 2 | 3 | # PyNode 4 | 5 | [![Build Status](https://travis-ci.org/fridgerator/PyNode.svg?branch=master)](https://travis-ci.org/fridgerator/PyNode) 6 | 7 | ### Call python code from node.js 8 | 9 | **Node v10 or above is required** 10 | 11 | **Tested with python 3.6 - 3.8.** 12 | 13 | ## Installation 14 | 15 | **BEFORE NPM INSTALL OR YARN INSTALL** 16 | 17 | * Make sure `python` in your system `PATH` is the correct one: `python --version`. You may use a virtualenv to do this. 18 | * Install gyp-next: `git clone https://github.com/nodejs/gyp-next`; `cd gyp-next`; `python setup.py install` 19 | * `yarn add @fridgerator/pynode` or 20 | `npm install @fridgerator/pynode` 21 | * If your default `python` is version 2.7, then you may have to yarn install using additional env variables: `PY_INCLUDE=$(python3.6 build_include.py) PY_LIBS=$(python3.6 build_ldflags.py) yarn` 22 | 23 | ## Usage 24 | 25 | ### Async API 26 | 27 | In a python file `example.py`: 28 | 29 | ```python 30 | def add(a, b): 31 | return a + b 32 | ``` 33 | in node: 34 | 35 | ```javascript 36 | const pynode = require('@fridgerator/pynode') 37 | 38 | // Workaround for linking issue in linux: 39 | // https://bugs.python.org/issue4434 40 | // if you get: `undefined symbol: PyExc_ValueError` or `undefined symbol: PyExc_SystemError` 41 | pynode.dlOpen('libpython3.6m.so') // your libpython shared library 42 | 43 | // optionally pass a path to use as Python module search path 44 | pynode.startInterpreter() 45 | 46 | // add current path as Python module search path, so it finds our test.py 47 | pynode.appendSysPath('./') 48 | 49 | // open the python file (module) 50 | pynode.openFile('example') 51 | 52 | // call the python function and get a return value 53 | pynode.call('add', 1, 2, (err, result) => { 54 | if (err) return console.log('error : ', err) 55 | result === 3 // true 56 | }) 57 | ``` 58 | 59 | ### Full Object API 60 | 61 | The Full Object API allows full interaction between JavaScript objects within Python code, and Python objects within JavaScript code. In Python, this works very transparently without needing to know if a value is a Python or JS object. In JavaScript, the interface is (currently) a bit more primitive, but supports getting object members and calling objects using `.get()` and `.call()`. 62 | 63 | Primitives are generally converted between JS and Python values, so passing strings, numbers, lists, and dicts generally works as expected. The wrappers described above only kick in for non-primitives. 64 | 65 | In a python file `test_files/objects.py`: 66 | 67 | ```python 68 | 69 | def does_things_with_js(jsobject): 70 | '''This function demonstrates that we can treat passed-in 71 | JavaScript objects (almost) as if they were native Python objects. 72 | ''' 73 | # We can print out JS objects; toString is called automatically 74 | # (it is mapped to Python's __str__): 75 | print("does_things_with_js:", jsobject) 76 | 77 | # We can call any function on the JS object. Objects passed 78 | # in are converted to JS if they are primitives, or wrapped in the 79 | # Full Object API if not, so JavaScript can also call back into Python: 80 | result = jsobject.arbitraryFunction("nostrongtypinghere") 81 | 82 | # The result from JavaScript is also converted back into Python: 83 | print(result) 84 | 85 | # The result will be converted back into JavaScript by PyNode: 86 | return result 87 | 88 | class PythonClass(object): 89 | '''A simple class to demonstrate creating and calling an instance from JavaScript.''' 90 | 91 | def __init__(self, a, b): 92 | '''Constructor. a and b are converted to Python types by PyNode''' 93 | self.a = a 94 | self.b = b 95 | 96 | def collate(self, callback): 97 | '''A function that is given a callback to call into. This can be a JavaScript function''' 98 | print('PythonClass.collate()') 99 | print('callback:', callback) 100 | 101 | # Arguments are converted into JavaScript objects, and the return value 102 | # from the callback is converted back into Python objects. 103 | return callback(self.a, self.b, "hello", 4) 104 | 105 | def __repr__(self): 106 | return 'PythonClass(a=%r, b=%r)' % (self.a, self.b) 107 | 108 | ``` 109 | 110 | In Node: 111 | 112 | ```javascript 113 | const pynode = require('@fridgerator/pynode') 114 | 115 | pynode.startInterpreter(); 116 | pynode.appendSysPath('./test_files/'); 117 | 118 | /* Modules are imported as JS objects, implementing the Full Object API */ 119 | const objectsmodule = pynode.import('objects'); /* test module in test_files */ 120 | const python_builtins = pynode.import('builtins'); /* access to python builtins such as str, all, etc, if you want them. */ 121 | 122 | /* Define an example JavaScript class: */ 123 | class JSClass { 124 | constructor(item) { 125 | this.item = item 126 | } 127 | arbitraryFunction(value) { 128 | console.log("arbitraryFunction called; item = " + this.item); 129 | console.log(" value = " + value); 130 | return value + 12; 131 | } 132 | toString() { 133 | return "JSClass{item=" + this.item + "}" 134 | } 135 | } 136 | 137 | /* Create an instance of JSClass which Python will have access to: */ 138 | jsclassinstance = new JSClass(['some', 'data']); 139 | console.log(jsclassinstance); 140 | 141 | /* Get the 'does_things_with_js' Python function from the objects module, 142 | and call it, passing in the JSClass object: */ 143 | result = objectsmodule.get('does_things_with_js').call(jsclassinstance); 144 | 145 | /* Python objects are automatically converted back to JS, or returned as 146 | PyNodeWrappedPythonObject instances (see below( if they are 147 | non-primitive types: 148 | */ 149 | console.log(result); 150 | 151 | /* Create a Python class. In Python this is done by calling the class 152 | object with constructor arguments (ie, don't use `new`): */ 153 | somedict = {'some': 'dict'}; 154 | pyclassinstance = objectsmodule.get('PythonClass').call(somedict, '2'); 155 | 156 | /* The resulting object is returned as a PyNodeWrappedPythonObject instance, 157 | which implements the Full Object API: */ 158 | console.log(pyclassinstance); 159 | 160 | /* The PyNodeWrappedPythonObject instance can have members retrieved from it 161 | and functions called. Below is the equivalent of (in Python) 162 | `getattr(pyclassinstance, 'collate').__call__()` 163 | or put simply: 164 | `pyclassinstance.collate()`. 165 | The JavaScript function is also converted to a callable Python object */ 166 | */ 167 | result = pyclassinstance.get('collate').call(function(a, b, c, d) { 168 | console.log(arguments); 169 | return c * d; /* JavaScript return values are converted back to Python types by PyNode */ 170 | }); 171 | console.log(result); 172 | ``` 173 | -------------------------------------------------------------------------------- /src/jswrapper.c: -------------------------------------------------------------------------------- 1 | #define PY_SSIZE_T_CLEAN 2 | #include "jswrapper.h" 3 | #include 4 | 5 | typedef struct { 6 | PyObject_HEAD 7 | /* Type-specific fields go here. */ 8 | napi_ref object_reference; 9 | napi_env env; 10 | napi_value bound; 11 | } WrappedJSObject; 12 | 13 | static void 14 | WrappedJSObject_dealloc(WrappedJSObject *self) 15 | { 16 | if (self->object_reference != NULL) { 17 | napi_delete_reference(self->env, self->object_reference); 18 | self->object_reference = NULL; 19 | } 20 | Py_TYPE(self)->tp_free((PyObject *) self); 21 | } 22 | 23 | napi_value WrappedJSObject_get_napi_value(PyObject *_self) { 24 | WrappedJSObject *self = (WrappedJSObject *)_self; 25 | napi_value wrapped; 26 | napi_get_reference_value(self->env, self->object_reference, &wrapped); 27 | return wrapped; 28 | } 29 | 30 | static void 31 | WrappedJSObject_assign_napi_value(WrappedJSObject *self, napi_env env, napi_value value) { 32 | self->env = env; 33 | if (self->object_reference != NULL) { 34 | napi_delete_reference(env, self->object_reference); 35 | self->object_reference = NULL; 36 | } 37 | napi_create_reference(env, value, 1, &(self->object_reference)); 38 | } 39 | 40 | static PyObject * 41 | WrappedJSObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 42 | WrappedJSObject *self; 43 | self = (WrappedJSObject *) type->tp_alloc(type, 0); 44 | if (self != NULL) { 45 | self->object_reference = NULL; 46 | self->env = NULL; 47 | self->bound = NULL; 48 | } 49 | return (PyObject *) self; 50 | } 51 | 52 | static int 53 | WrappedJSObject_init(WrappedJSObject *self, PyObject *args, PyObject *kwds) 54 | { 55 | if (!PyArg_ParseTuple(args, "")) 56 | return -1; 57 | return 0; 58 | } 59 | 60 | static PyMemberDef WrappedJSObject_members[] = { 61 | {NULL} /* Sentinel */ 62 | }; 63 | 64 | static PyObject * 65 | WrappedJSObject_something(WrappedJSObject *self, PyObject *unused) 66 | { 67 | //self->state++; 68 | //return PyLong_FromLong(self->state); 69 | return PyLong_FromLong(47.0); 70 | } 71 | 72 | static PyMethodDef WrappedJSObject_methods[] = { 73 | {"something", (PyCFunction) WrappedJSObject_something, METH_NOARGS, 74 | PyDoc_STR("Do something?")}, 75 | {NULL}, /* Sentinel */ 76 | }; 77 | 78 | static PyObject * 79 | WrappedJSObject_getattro(PyObject *_self, PyObject *attr) 80 | { 81 | WrappedJSObject *self = (WrappedJSObject*)_self; 82 | napi_value wrapped; 83 | napi_valuetype type; 84 | bool hasattr; 85 | const char * utf8name = PyUnicode_AsUTF8(attr); 86 | napi_value result; 87 | napi_get_reference_value(self->env, self->object_reference, &wrapped); 88 | napi_has_named_property(self->env, wrapped, utf8name, &hasattr); 89 | if (hasattr) { 90 | bool isfunc; 91 | napi_get_named_property(self->env, wrapped, utf8name, &result); 92 | napi_typeof(self->env, result, &type); 93 | isfunc = (type == napi_function); 94 | 95 | PyObject *pyval = convert_napi_value_to_python(self->env, result); 96 | if (pyval != NULL) { 97 | if (isfunc) { 98 | /* "bind" the method to its instance */ 99 | ((WrappedJSObject *)pyval)->bound = wrapped; 100 | } 101 | return pyval; 102 | } 103 | } 104 | PyErr_SetObject(PyExc_AttributeError, attr); 105 | return NULL; 106 | } 107 | 108 | static PyObject * 109 | WrappedJSObject_call(PyObject *_self, PyObject *args, PyObject *kwargs) 110 | { 111 | WrappedJSObject *self = (WrappedJSObject*)_self; 112 | PyObject * ret = NULL; 113 | PyObject * seq; 114 | Py_ssize_t len = 0, i; 115 | napi_value result; 116 | napi_value this; 117 | napi_status status; 118 | napi_value wrapped; 119 | napi_value * jsargs = NULL; 120 | 121 | seq = PySequence_Fast(args, "*args must be a sequence"); 122 | len = PySequence_Size(args); 123 | jsargs = calloc(len, sizeof(napi_value)); 124 | if (jsargs == NULL) { 125 | PyErr_SetString(PyExc_MemoryError, "Out of memory allocating JS args array"); 126 | goto finally; 127 | } 128 | for (i = 0; i < len; i++) { 129 | PyObject *arg = PySequence_Fast_GET_ITEM(seq, i); 130 | napi_value jsarg = convert_python_to_napi_value(self->env, arg); 131 | jsargs[i] = jsarg; 132 | } 133 | Py_DECREF(seq); 134 | 135 | napi_get_reference_value(self->env, self->object_reference, &wrapped); 136 | 137 | if (self->bound != NULL) { 138 | this = self->bound; 139 | } else { 140 | status = napi_get_global(self->env, &this); 141 | if (status != napi_ok) { 142 | PyErr_SetString(PyExc_RuntimeError, "Error getting JS global environment"); 143 | goto finally; 144 | } 145 | } 146 | 147 | status = napi_call_function(self->env, this, wrapped, len, jsargs, &result); 148 | if (status != napi_ok) { 149 | PyErr_SetString(PyExc_RuntimeError, "Error calling javascript function"); 150 | goto finally; 151 | } 152 | 153 | PyObject *pyval = convert_napi_value_to_python(self->env, result); 154 | if (pyval == NULL) { 155 | PyErr_SetString(PyExc_RuntimeError, "Error converting JS return value to Python"); 156 | goto finally; 157 | } 158 | ret = pyval; 159 | 160 | finally: 161 | if (jsargs != NULL) { 162 | free(jsargs); 163 | jsargs = NULL; 164 | } 165 | return ret; 166 | } 167 | 168 | PyObject * WrappedJSObject_str(PyObject *_self) { 169 | WrappedJSObject *self = (WrappedJSObject *)_self; 170 | napi_value global; 171 | napi_value result; 172 | napi_value wrapped; 173 | napi_status status; 174 | 175 | status = napi_get_global(self->env, &global); 176 | if (status != napi_ok) { 177 | PyErr_SetString(PyExc_RuntimeError, "Error getting JS global environment"); 178 | Py_RETURN_NONE; 179 | } 180 | 181 | napi_get_reference_value(self->env, self->object_reference, &wrapped); 182 | 183 | status = napi_coerce_to_string(self->env, wrapped, &result); 184 | if (status != napi_ok) { 185 | PyErr_SetString(PyExc_RuntimeError, "Error coercing javascript value to string"); 186 | Py_RETURN_NONE; 187 | } 188 | 189 | /* Result should just be a JavaScript string at this point */ 190 | PyObject *pyval = convert_napi_value_to_python(self->env, result); 191 | if (pyval == NULL) { 192 | PyErr_SetString(PyExc_RuntimeError, "Error converting JavaScript ToString item to Python"); 193 | Py_RETURN_NONE; 194 | } 195 | return pyval; 196 | } 197 | 198 | static PyTypeObject WrappedJSType = { 199 | PyVarObject_HEAD_INIT(NULL, 0) 200 | .tp_name = "pynode.WrappedJSObject", 201 | .tp_doc = "A JavaScript object", 202 | .tp_basicsize = sizeof(WrappedJSObject), 203 | .tp_itemsize = 0, 204 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 205 | .tp_new = WrappedJSObject_new, 206 | .tp_init = (initproc) WrappedJSObject_init, 207 | .tp_dealloc = (destructor) WrappedJSObject_dealloc, 208 | .tp_members = WrappedJSObject_members, 209 | .tp_methods = WrappedJSObject_methods, 210 | .tp_call = WrappedJSObject_call, 211 | .tp_getattro = WrappedJSObject_getattro, 212 | .tp_str = WrappedJSObject_str, 213 | }; 214 | 215 | PyObject *WrappedJSObject_New(napi_env env, napi_value value) { 216 | /* Pass an empty argument list */ 217 | PyObject *argList = Py_BuildValue("()"); 218 | 219 | /* Call the class object. */ 220 | PyObject *obj = PyObject_CallObject((PyObject *) &WrappedJSType, argList); 221 | if (obj == NULL) { 222 | PyErr_Print(); 223 | } 224 | 225 | /* Release the argument list. */ 226 | Py_DECREF(argList); 227 | 228 | WrappedJSObject_assign_napi_value((WrappedJSObject*)obj, env, value); 229 | return obj; 230 | } 231 | 232 | static PyModuleDef pynodemodule = { 233 | PyModuleDef_HEAD_INIT, 234 | .m_name = "pynode", 235 | .m_doc = "Python <3 JavaScript.", 236 | .m_size = -1, 237 | }; 238 | 239 | PyMODINIT_FUNC 240 | PyInit_jswrapper(void) 241 | { 242 | PyObject *m; 243 | if (PyType_Ready(&WrappedJSType) < 0) 244 | return NULL; 245 | 246 | m = PyModule_Create(&pynodemodule); 247 | if (m == NULL) 248 | return NULL; 249 | 250 | Py_INCREF(&WrappedJSType); 251 | if (PyModule_AddObject(m, "WrappedJSObject", (PyObject *) &WrappedJSType) < 0) { 252 | Py_DECREF(&WrappedJSType); 253 | Py_DECREF(m); 254 | return NULL; 255 | } 256 | 257 | return m; 258 | } 259 | -------------------------------------------------------------------------------- /src/pynode.cpp: -------------------------------------------------------------------------------- 1 | #include "pynode.hpp" 2 | #ifndef _WIN32 3 | #include 4 | #endif 5 | #include "helpers.hpp" 6 | #include "worker.hpp" 7 | #include "pywrapper.hpp" 8 | #include "jswrapper.h" 9 | #include 10 | 11 | PyObject *pModule; 12 | 13 | Napi::Value StartInterpreter(const Napi::CallbackInfo &info) { 14 | Napi::Env env = info.Env(); 15 | 16 | if (info.Length() == 1 && info[0].IsString()) { 17 | std::string pathString = info[0].As().ToString(); 18 | std::wstring path(pathString.length(), L'#'); 19 | mbstowcs(&path[0], pathString.c_str(), pathString.length()); 20 | Py_SetPath(path.c_str()); 21 | } 22 | 23 | PyImport_AppendInittab("pynode", &PyInit_jswrapper); 24 | 25 | int isInitialized = Py_IsInitialized(); 26 | if (isInitialized == 0) { 27 | Py_Initialize(); 28 | } 29 | 30 | int threadsInitialized = PyEval_ThreadsInitialized(); 31 | if (threadsInitialized == 0) 32 | PyEval_InitThreads(); 33 | 34 | /* Load PyNode's own module into Python. This makes WrappedJSObject instances 35 | behave better (eg, having attributes) */ 36 | PyObject *pName; 37 | pName = PyUnicode_DecodeFSDefault("pynode"); 38 | pModule = PyImport_Import(pName); 39 | Py_DECREF(pName); 40 | if (pModule == NULL) { 41 | PyErr_Print(); 42 | Napi::Error::New(env, "Failed to load the pynode module into the Python interpreter") 43 | .ThrowAsJavaScriptException(); 44 | } 45 | 46 | /* Release the GIL. The other entry points back into Python re-acquire it */ 47 | PyEval_SaveThread(); 48 | 49 | return env.Null(); 50 | } 51 | 52 | Napi::Value DlOpen(const Napi::CallbackInfo &info) { 53 | Napi::Env env = info.Env(); 54 | 55 | #ifdef _WIN32 56 | Napi::Error::New(env, "dlOpen does not work in windows") 57 | .ThrowAsJavaScriptException(); 58 | return env.Null(); 59 | #endif 60 | 61 | if (!info[0] || !info[0].IsString()) { 62 | Napi::Error::New(env, "Must pass a string to 'dlOpen'") 63 | .ThrowAsJavaScriptException(); 64 | return env.Null(); 65 | } 66 | 67 | std::string dlFile = info[0].As().ToString(); 68 | #ifndef _WIN32 69 | dlopen(dlFile.c_str(), RTLD_LAZY | RTLD_GLOBAL); 70 | #endif 71 | return env.Null(); 72 | } 73 | 74 | Napi::Value AppendSysPath(const Napi::CallbackInfo &info) { 75 | Napi::Env env = info.Env(); 76 | 77 | if (!info[0] || !info[0].IsString()) { 78 | Napi::Error::New(env, "Must pass a string to 'appendSysPath'") 79 | .ThrowAsJavaScriptException(); 80 | return env.Null(); 81 | } 82 | 83 | std::string pathName = info[0].As().ToString(); 84 | char *appendPathStr; 85 | size_t len = (size_t)snprintf(NULL, 0, "import sys;sys.path.append(r\"%s\")", 86 | pathName.c_str()); 87 | appendPathStr = (char *)malloc(len + 1); 88 | snprintf(appendPathStr, len + 1, "import sys;sys.path.append(r\"%s\")", 89 | pathName.c_str()); 90 | 91 | { 92 | py_ensure_gil ctx; 93 | PyRun_SimpleString(appendPathStr); 94 | free(appendPathStr); 95 | } 96 | 97 | return env.Null(); 98 | } 99 | 100 | Napi::Value OpenFile(const Napi::CallbackInfo &info) { 101 | Napi::Env env = info.Env(); 102 | 103 | if (!info[0] || !info[0].IsString()) { 104 | Napi::Error::New(env, "Must pass a string to 'openFile'") 105 | .ThrowAsJavaScriptException(); 106 | return env.Null(); 107 | } 108 | 109 | std::string fileName = info[0].As().ToString(); 110 | 111 | { 112 | py_ensure_gil ctx; 113 | 114 | PyObject *pName; 115 | pName = PyUnicode_DecodeFSDefault(fileName.c_str()); 116 | pModule = PyImport_Import(pName); 117 | Py_DECREF(pName); 118 | 119 | if (pModule == NULL) { 120 | PyErr_Print(); 121 | std::cerr << "Failed to load module: " << fileName << std::endl; 122 | Napi::Error::New(env, "Failed to load python module") 123 | .ThrowAsJavaScriptException(); 124 | } 125 | } 126 | 127 | return env.Null(); 128 | } 129 | 130 | Napi::Value ImportModule(const Napi::CallbackInfo &info) { 131 | Napi::Env env = info.Env(); 132 | 133 | if (!info[0] || !info[0].IsString()) { 134 | Napi::Error::New(env, "Must pass a string to 'import'") 135 | .ThrowAsJavaScriptException(); 136 | return env.Null(); 137 | } 138 | 139 | std::string moduleName = info[0].As().ToString(); 140 | 141 | { 142 | py_ensure_gil ctx; 143 | 144 | PyObject *pName; 145 | pName = PyUnicode_FromString(moduleName.c_str()); 146 | PyObject *module_object = PyImport_Import(pName); 147 | Py_DECREF(pName); 148 | 149 | if (module_object == NULL) { 150 | PyErr_Print(); 151 | std::cerr << "Failed to load module: " << moduleName << std::endl; 152 | Napi::Error::New(env, "Failed to load python module") 153 | .ThrowAsJavaScriptException(); 154 | } 155 | return ConvertFromPython(env, module_object); 156 | } 157 | 158 | return env.Null(); 159 | } 160 | 161 | Napi::Value Eval(const Napi::CallbackInfo &info) { 162 | Napi::Env env = info.Env(); 163 | 164 | if (!info[0] || !info[0].IsString()) { 165 | Napi::TypeError::New(env, "Must pass a string to 'eval'") 166 | .ThrowAsJavaScriptException(); 167 | return env.Null(); 168 | } 169 | 170 | std::string statement = info[0].As().ToString(); 171 | int response; 172 | { 173 | py_ensure_gil ctx; 174 | 175 | response = PyRun_SimpleString(statement.c_str()); 176 | } 177 | 178 | return Napi::Number::New(env, response); 179 | } 180 | 181 | Napi::Value Call(const Napi::CallbackInfo &info) { 182 | Napi::Env env = info.Env(); 183 | 184 | if (info.Length() == 0 || !info[0].IsString()) { 185 | std::cerr << "First argument to 'call' must be a string" << std::endl; 186 | Napi::Error::New(env, "First argument to 'call' must be a string") 187 | .ThrowAsJavaScriptException(); 188 | return env.Null(); 189 | } 190 | 191 | if (!info[info.Length() - 1].IsFunction()) { 192 | std::cerr << "Last argument to 'call' must be a function" << std::endl; 193 | Napi::Error::New(env, "Last argument to 'call' must be a function") 194 | .ThrowAsJavaScriptException(); 195 | return env.Null(); 196 | } 197 | 198 | std::string functionName = info[0].As().ToString(); 199 | 200 | PyObject *pFunc, *pArgs; 201 | 202 | { 203 | py_ensure_gil ctx; 204 | 205 | pFunc = PyObject_GetAttrString(pModule, functionName.c_str()); 206 | int callable = PyCallable_Check(pFunc); 207 | 208 | if (pFunc != NULL && callable == 1) { 209 | const int pythonArgsCount = Py_GetNumArguments(pFunc); 210 | const int passedArgsCount = info.Length() - 2; 211 | 212 | // Check if the passed args length matches the python function args length 213 | if (passedArgsCount != pythonArgsCount) { 214 | std::string error("The function '" + functionName + "' has " + 215 | std::to_string(pythonArgsCount) + " arguments, " + 216 | std::to_string(passedArgsCount) + " were passed"); 217 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 218 | return env.Null(); 219 | } 220 | 221 | // Arguments length minus 2: one for function name, one for js callback 222 | pArgs = BuildPyArgs(info, 1, info.Length() - 2); 223 | } else { 224 | Napi::Error::New(env, 225 | "Could not find function name / function not callable") 226 | .ThrowAsJavaScriptException(); 227 | return env.Null(); 228 | } 229 | } 230 | 231 | Napi::Function cb = info[info.Length() - 1].As(); 232 | PyNodeWorker *pnw = new PyNodeWorker(cb, pArgs, pFunc); 233 | pnw->Queue(); 234 | return env.Null(); 235 | } 236 | 237 | Napi::Object PyNodeInit(Napi::Env env, Napi::Object exports) { 238 | 239 | exports.Set(Napi::String::New(env, "dlOpen"), 240 | Napi::Function::New(env, DlOpen)); 241 | 242 | exports.Set(Napi::String::New(env, "startInterpreter"), 243 | Napi::Function::New(env, StartInterpreter)); 244 | 245 | exports.Set(Napi::String::New(env, "dlOpen"), 246 | Napi::Function::New(env, DlOpen)); 247 | 248 | exports.Set(Napi::String::New(env, "appendSysPath"), 249 | Napi::Function::New(env, AppendSysPath)); 250 | 251 | exports.Set(Napi::String::New(env, "openFile"), 252 | Napi::Function::New(env, OpenFile)); 253 | 254 | exports.Set(Napi::String::New(env, "import"), 255 | Napi::Function::New(env, ImportModule)); 256 | 257 | exports.Set(Napi::String::New(env, "eval"), Napi::Function::New(env, Eval)); 258 | 259 | exports.Set(Napi::String::New(env, "call"), Napi::Function::New(env, Call)); 260 | 261 | PyNodeWrappedPythonObject::Init(env, exports); 262 | 263 | return exports; 264 | } 265 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | const expect = chai.expect 3 | 4 | const nodePython = require('./build/Release/PyNode') 5 | const { promisify } = require('util') 6 | 7 | if (process.platform === 'linux') { 8 | nodePython.dlOpen('libpython3.10.so') 9 | nodePython.dlOpen('libpython3.6m.so') 10 | } 11 | nodePython.startInterpreter() 12 | nodePython.appendSysPath('./test_files') 13 | nodePython.openFile('tools') 14 | 15 | const call = promisify(nodePython.call) 16 | 17 | describe('nodePython', () => { 18 | describe('#eval', () => { 19 | it('successful eval should return 0', () => { 20 | let response = nodePython.eval('import sys') 21 | expect(response).to.equal(0) 22 | }) 23 | 24 | it('failed eval should return -1', () => { 25 | let response = nodePython.eval('import randommodulethatshouldnotexist11') 26 | expect(response).to.equal(-1) 27 | }) 28 | }) 29 | 30 | describe('#call', () => { 31 | it('should fail if the last parameter is not a function', () => { 32 | try { 33 | nodePython.call('return_immediate', 2) 34 | } catch (err) { 35 | expect(err.message).to.equal("Last argument to 'call' must be a function") 36 | } 37 | }) 38 | 39 | it('should return the stack trace', done => { 40 | call('causes_runtime_error') 41 | .then(result => { 42 | // exception not returned corretly 43 | expect(false).to.equal(true) 44 | }) 45 | .catch(err => { 46 | // exception returned correctly 47 | expect(true).to.equal(true) 48 | done() 49 | }) 50 | }) 51 | 52 | it('should return the time series data', done => { 53 | call('time_series_data') 54 | .then(result => { 55 | expect(typeof result[0][0]).to.equal('string') 56 | expect(typeof result[0][1]).to.equal('number') 57 | done() 58 | }) 59 | }) 60 | 61 | it('should throw an exception with the wrong number of arguments', done => { 62 | call('return_immediate', 9, 9, 9) 63 | .catch(e => { 64 | expect(e.message).to.equal("The function 'return_immediate' has 1 arguments, 3 were passed") 65 | done() 66 | }) 67 | }) 68 | 69 | it('should return the correct value when passing Int32', done => { 70 | call('return_immediate', 2) 71 | .then(result => { 72 | expect(result).to.equal(2) 73 | done() 74 | }) 75 | }) 76 | 77 | it('should return the correct value when passing Float', done => { 78 | call('return_immediate', 3.14) 79 | .then(result => { 80 | expect(result).to.equal(3.14) 81 | done() 82 | }) 83 | }) 84 | 85 | it('should return the correct value when passing String', done => { 86 | call('return_immediate', 'the string') 87 | .then(result => { 88 | expect(result).to.equal('the string') 89 | done() 90 | }) 91 | }) 92 | 93 | it('should return the correct value when passing bool', done => { 94 | call('return_immediate', true) 95 | .then(result => { 96 | expect(result).to.equal(true) 97 | done() 98 | }) 99 | }) 100 | 101 | it('should return the correct value when passing bool', done => { 102 | call('return_immediate', false) 103 | .then(result => { 104 | expect(result).to.equal(false) 105 | done() 106 | }) 107 | }) 108 | 109 | it('should return null when python returns None', done => { 110 | call('return_none') 111 | .then(result => { 112 | expect(result).to.equal(null) 113 | done() 114 | }) 115 | }) 116 | 117 | it.skip('should return the correct value when passing Date', done => { 118 | let d = new Date() 119 | call('return_immediate', d) 120 | .then(result => { 121 | expect(result).to.equal(d) 122 | done() 123 | }) 124 | }) 125 | 126 | describe('arrays', () => { 127 | it('should return the correct value when passing an empty array', done => { 128 | call('return_immediate', []) 129 | .then(result => { 130 | expect(result).to.deep.equal([]) 131 | done() 132 | }) 133 | }) 134 | 135 | it('should return the correct value when passing an array of ints', done => { 136 | call('return_immediate', [1, 2, 3]) 137 | .then(result => { 138 | expect(result).to.deep.equal([1, 2, 3]) 139 | done() 140 | }) 141 | }) 142 | 143 | it('should return the correct value when passing an array of strings', done => { 144 | call('return_immediate', ['a', 'b', 'c']) 145 | .then(result => { 146 | expect(result).to.deep.equal(['a', 'b', 'c']) 147 | done() 148 | }) 149 | }) 150 | 151 | it('should return the correct value when passing an array of mixed types', done => { 152 | call('return_immediate', ['a', 1, 6.7777]) 153 | .then(result => { 154 | expect(result).to.deep.equal(['a', 1, 6.7777]) 155 | done() 156 | }) 157 | }) 158 | 159 | it('should return the correct value when passing a nested array', done => { 160 | let x = [ 161 | [1, 2, 3], 162 | ['a', 'b', 'c'] 163 | ] 164 | call('return_immediate', x) 165 | .then(result => { 166 | expect(result).to.deep.equal(x) 167 | done() 168 | }) 169 | }) 170 | 171 | it('should return the correct value when passing arrays with objects', done => { 172 | let x = [ 173 | { array: [1, 2, 3] }, 174 | { string: 'ok', float: 8281.111 } 175 | ] 176 | call('return_immediate', x) 177 | .then(result => { 178 | expect(result).to.deep.equal(x) 179 | done() 180 | }) 181 | }) 182 | 183 | it('should return sum of numeric array input', done => { 184 | call('sum_items', [1, 2, 3]) 185 | .then(result => { 186 | expect(result).to.equals(6) 187 | done() 188 | }) 189 | }) 190 | }) 191 | 192 | describe('tuples', () => { 193 | it.skip('should return an array when a tuple is returned from python', done => { 194 | call('return_tuple') 195 | .then(result => { 196 | expect(result).to.deep.equal([1, 2, 3]) 197 | done() 198 | }) 199 | }) 200 | }) 201 | 202 | describe('objects / dicts', () => { 203 | it('should return an object from a python dict', done => { 204 | call('return_dict') 205 | .then(result => { 206 | expect(result).to.deep.equal({'size': 71589, 'min': -99.6654762642, 'max': 879.08351843}) 207 | done() 208 | }) 209 | }) 210 | 211 | it('should return the correct value when passing an object', done => { 212 | call('return_immediate', {}) 213 | .then(result => { 214 | expect(result).to.deep.equal({}) 215 | done() 216 | }) 217 | }) 218 | 219 | it('should return the correct value when passing an object', done => { 220 | call('return_immediate', { hey: 'guys' }) 221 | .then(result => { 222 | expect(result).to.deep.equal({ hey: 'guys' }) 223 | done() 224 | }) 225 | }) 226 | 227 | it('should return the correct value when passing an object', done => { 228 | call('return_immediate', { hey: 'guys', other: 1 }) 229 | .then(result => { 230 | expect(result).to.deep.equal({ hey: 'guys', other: 1 }) 231 | done() 232 | }) 233 | }) 234 | 235 | it('should return the correct value when passing an object', done => { 236 | let crazyObj = { 237 | hey: [1, 2, 3], 238 | no: "yes", 239 | x: { 240 | stuff: 'no', 241 | things: ['a', 1, 2.33], 242 | bro: { 243 | x: 1.233 244 | } 245 | } 246 | } 247 | call('return_immediate', crazyObj) 248 | .then(result => { 249 | expect(result).to.deep.equal(crazyObj) 250 | done() 251 | }) 252 | }) 253 | 254 | it('should merge two dicts', done => { 255 | let x = { 256 | hey: 'guys' 257 | } 258 | let y = { 259 | other: 1 260 | } 261 | call('merge_two_dicts', x, y) 262 | .then(result => { 263 | expect(result).to.deep.equal({ hey: 'guys', other: 1 }) 264 | done() 265 | }) 266 | }) 267 | }) 268 | }) 269 | 270 | // describe('stopInterpreter', () => { 271 | // it('should stop the interpreter', () => { 272 | // nodePython.stopInterpreter() 273 | // }) 274 | // }) 275 | }) 276 | -------------------------------------------------------------------------------- /src/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | #include "jswrapper.h" 3 | #include 4 | 5 | bool isNapiValueInt(Napi::Env &env, Napi::Value &num) { 6 | return env.Global() 7 | .Get("Number") 8 | .ToObject() 9 | .Get("isInteger") 10 | .As() 11 | .Call({num}) 12 | .ToBoolean() 13 | .Value(); 14 | } 15 | 16 | /* Returns true if the value given is (roughly) an object literal, 17 | * ie more appropriate as a Python dict than a WrappedJSObject. 18 | * 19 | * Based on https://stackoverflow.com/questions/5876332/how-can-i-differentiate-between-an-object-literal-other-javascript-objects 20 | */ 21 | bool isNapiValuePlainObject(Napi::Env &env, Napi::Value &obj) { 22 | napi_value result; 23 | napi_value plainobj_result; 24 | napi_status status; 25 | 26 | status = napi_get_prototype(env, obj, &result); 27 | if (status != napi_ok) { 28 | return false; 29 | } 30 | 31 | Napi::Object plainobj = Napi::Object::New(env); 32 | status = napi_get_prototype(env, plainobj, &plainobj_result); 33 | if (status != napi_ok) { 34 | return false; 35 | } 36 | 37 | bool equal; 38 | status = napi_strict_equals(env, result, plainobj_result, &equal); 39 | return equal; 40 | } 41 | 42 | bool isNapiValueWrappedPython(Napi::Env &env, Napi::Object obj) { 43 | return obj.InstanceOf(PyNodeWrappedPythonObject::constructor.Value()); 44 | } 45 | 46 | int Py_GetNumArguments(PyObject *pFunc) { 47 | PyObject *fc = PyObject_GetAttrString(pFunc, "__code__"); 48 | if (fc) { 49 | PyObject *ac = PyObject_GetAttrString(fc, "co_argcount"); 50 | if (ac) { 51 | long count = PyLong_AsLong(ac); 52 | Py_DECREF(ac); 53 | return count; 54 | } 55 | Py_DECREF(fc); 56 | } 57 | return 0; 58 | } 59 | 60 | PyObject *BuildPyArray(Napi::Env env, Napi::Value arg) { 61 | auto arr = arg.As(); 62 | PyObject *list = PyList_New(arr.Length()); 63 | 64 | for (size_t i = 0; i < arr.Length(); i++) { 65 | auto element = arr.Get(i); 66 | PyObject * pyval = ConvertToPython(element); 67 | if (pyval != NULL) { 68 | PyList_SetItem(list, i, pyval); 69 | } 70 | } 71 | 72 | return list; 73 | } 74 | 75 | PyObject *BuildPyDict(Napi::Env env, Napi::Value arg) { 76 | auto obj = arg.As(); 77 | auto keys = obj.GetPropertyNames(); 78 | PyObject *dict = PyDict_New(); 79 | for (size_t i = 0; i < keys.Length(); i++) { 80 | auto key = keys.Get(i); 81 | std::string keyStr = key.ToString(); 82 | Napi::Value val = obj.Get(key); 83 | PyObject *pykey = PyUnicode_FromString(keyStr.c_str()); 84 | PyObject *pyval = ConvertToPython(val); 85 | if (pyval != NULL) { 86 | PyDict_SetItem(dict, pykey, pyval); 87 | } 88 | } 89 | 90 | return dict; 91 | } 92 | 93 | PyObject *BuildWrappedJSObject(Napi::Env env, Napi::Value arg) { 94 | PyObject *pyobj = WrappedJSObject_New(env, arg); 95 | return pyobj; 96 | } 97 | 98 | PyObject *BuildPyArgs(const Napi::CallbackInfo &args, size_t start_index, size_t count) { 99 | PyObject *pArgs = PyTuple_New(count); 100 | for (size_t i = start_index; i < start_index + count; i++) { 101 | auto arg = args[i]; 102 | PyObject *pyobj = ConvertToPython(arg); 103 | if (pyobj != NULL) { 104 | PyTuple_SetItem(pArgs, i - start_index, pyobj); 105 | } 106 | } 107 | 108 | return pArgs; 109 | } 110 | 111 | PyObject * ConvertToPython(Napi::Value arg) { 112 | Napi::Env env = arg.Env(); 113 | if (arg.IsNumber()) { 114 | double num = arg.As().ToNumber(); 115 | if (isNapiValueInt(env, arg)) { 116 | return PyLong_FromLong(num); 117 | } else { 118 | return PyFloat_FromDouble(num); 119 | } 120 | } else if (arg.IsString()) { 121 | std::string str = arg.As().ToString(); 122 | return PyUnicode_FromString(str.c_str()); 123 | } else if (arg.IsBoolean()) { 124 | long b = arg.As().ToBoolean(); 125 | return PyBool_FromLong(b); 126 | // } else if (arg.IsDate()) { 127 | // printf("Dates dont work yet"); 128 | // // Nan::ThrowError("Dates dont work yet"); 129 | // throw Napi::Error::New(args.Env(), "Dates dont work yet"); 130 | } else if (arg.IsArray()) { 131 | return BuildPyArray(env, arg); 132 | } else if (arg.IsObject()) { 133 | if (isNapiValueWrappedPython(env, arg.ToObject())) { 134 | Napi::Object o = arg.ToObject(); 135 | PyNodeWrappedPythonObject *wrapper = Napi::ObjectWrap::Unwrap(o); 136 | PyObject* pyobj = wrapper->getValue(); 137 | return pyobj; 138 | } else if (isNapiValuePlainObject(env, arg)) { 139 | return BuildPyDict(env, arg); 140 | } else { 141 | return BuildWrappedJSObject(env, arg); 142 | } 143 | } else if (arg.IsNull() || arg.IsUndefined()) { 144 | Py_RETURN_NONE; 145 | } else { 146 | Napi::String string = arg.ToString(); 147 | std::cout << "Unknown arg type" << string.Utf8Value() << std::endl; 148 | throw Napi::Error::New(arg.Env(), "Unknown arg type"); 149 | } 150 | } 151 | 152 | extern "C" { 153 | PyObject * convert_napi_value_to_python(napi_env env, napi_value value) { 154 | Napi::Value cpp_value = Napi::Value(env, value); 155 | return ConvertToPython(cpp_value); 156 | } 157 | } 158 | 159 | Napi::Array BuildV8Array(Napi::Env env, PyObject *obj) { 160 | Py_ssize_t len = PyList_Size(obj); 161 | 162 | auto arr = Napi::Array::New(env); 163 | 164 | for (Py_ssize_t i = 0; i < len; i++) { 165 | PyObject *localObj; 166 | if (strcmp(obj->ob_type->tp_name, "list") == 0) { 167 | localObj = PyList_GetItem(obj, i); 168 | } else { 169 | localObj = PyTuple_GetItem(obj, i); 170 | } 171 | 172 | if (!localObj) 173 | continue; 174 | 175 | if (strcmp(localObj->ob_type->tp_name, "int") == 0) { 176 | double result = PyLong_AsDouble(localObj); 177 | arr.Set(i, result); 178 | } else if (strcmp(localObj->ob_type->tp_name, "str") == 0) { 179 | auto str = PyUnicode_AsUTF8(localObj); 180 | arr.Set(i, str); 181 | } else if (strcmp(localObj->ob_type->tp_name, "float") == 0) { 182 | double result = PyFloat_AsDouble(localObj); 183 | arr.Set(i, result); 184 | } else if (strcmp(localObj->ob_type->tp_name, "bytes") == 0) { 185 | auto str = PyBytes_AsString(localObj); 186 | arr.Set(i, str); 187 | } else if (strcmp(localObj->ob_type->tp_name, "bool") == 0) { 188 | bool b = PyObject_IsTrue(localObj); 189 | arr.Set(i, b); 190 | } else if (strcmp(localObj->ob_type->tp_name, "list") == 0) { 191 | auto innerArr = BuildV8Array(env, localObj); 192 | arr.Set(i, innerArr); 193 | } else if (strcmp(localObj->ob_type->tp_name, "dict") == 0) { 194 | auto innerDict = BuildV8Dict(env, localObj); 195 | arr.Set(i, innerDict); 196 | } 197 | } 198 | return arr; 199 | } 200 | 201 | std::string getKey(PyObject *key) { 202 | if (strcmp(key->ob_type->tp_name, "str") == 0) { 203 | return std::string(PyUnicode_AsUTF8(key)); 204 | } else { 205 | return std::string(PyBytes_AsString(key)); 206 | } 207 | } 208 | 209 | Napi::Object BuildV8Dict(Napi::Env env, PyObject *obj) { 210 | auto keys = PyDict_Keys(obj); 211 | auto size = PyList_GET_SIZE(keys); 212 | auto jsObj = Napi::Object::New(env); 213 | 214 | for (Py_ssize_t i = 0; i < size; i++) { 215 | auto key = PyList_GetItem(keys, i); 216 | auto val = PyDict_GetItem(obj, key); 217 | auto jsKey = Napi::String::New(env, getKey(key)); 218 | std::string kk = jsKey.As().ToString(); 219 | 220 | if (strcmp(val->ob_type->tp_name, "int") == 0) { 221 | double result = PyLong_AsDouble(val); 222 | jsObj.Set(jsKey, result); 223 | } else if (strcmp(val->ob_type->tp_name, "str") == 0) { 224 | auto str = PyUnicode_AsUTF8(val); 225 | jsObj.Set(jsKey, str); 226 | } else if (strcmp(val->ob_type->tp_name, "float") == 0) { 227 | double result = PyFloat_AsDouble(val); 228 | jsObj.Set(jsKey, result); 229 | } else if (strcmp(val->ob_type->tp_name, "bytes") == 0) { 230 | auto str = PyBytes_AsString(val); 231 | jsObj.Set(jsKey, str); 232 | } else if (strcmp(val->ob_type->tp_name, "bool") == 0) { 233 | bool b = PyObject_IsTrue(val); 234 | jsObj.Set(jsKey, b); 235 | } else if (strcmp(val->ob_type->tp_name, "list") == 0) { 236 | auto innerArr = BuildV8Array(env, val); 237 | jsObj.Set(jsKey, innerArr); 238 | } else if (strcmp(val->ob_type->tp_name, "dict") == 0) { 239 | auto innerDict = BuildV8Dict(env, val); 240 | jsObj.Set(jsKey, innerDict); 241 | } 242 | } 243 | 244 | return jsObj; 245 | } 246 | 247 | Napi::Value ConvertFromPython(Napi::Env env, PyObject * pValue) { 248 | Napi::Value result = env.Null(); 249 | if (strcmp(pValue->ob_type->tp_name, "NoneType") == 0) { 250 | // leave as null 251 | } else if (strcmp(pValue->ob_type->tp_name, "bool") == 0) { 252 | bool b = PyObject_IsTrue(pValue); 253 | result = Napi::Boolean::New(env, b); 254 | } else if (strcmp(pValue->ob_type->tp_name, "int") == 0) { 255 | double d = PyLong_AsDouble(pValue); 256 | result = Napi::Number::New(env, d); 257 | } else if (strcmp(pValue->ob_type->tp_name, "float") == 0) { 258 | double d = PyFloat_AsDouble(pValue); 259 | result = Napi::Number::New(env, d); 260 | } else if (strcmp(pValue->ob_type->tp_name, "bytes") == 0) { 261 | auto str = Napi::String::New(env, PyBytes_AsString(pValue)); 262 | result = str; 263 | } else if (strcmp(pValue->ob_type->tp_name, "str") == 0) { 264 | auto str = Napi::String::New(env, PyUnicode_AsUTF8(pValue)); 265 | result = str; 266 | } else if (strcmp(pValue->ob_type->tp_name, "list") == 0) { 267 | auto arr = BuildV8Array(env, pValue); 268 | result = arr; 269 | } else if (strcmp(pValue->ob_type->tp_name, "dict") == 0) { 270 | auto obj = BuildV8Dict(env, pValue); 271 | result = obj; 272 | } else if (strcmp(pValue->ob_type->tp_name, "pynode.WrappedJSObject") == 0) { 273 | auto obj = Napi::Value(env, WrappedJSObject_get_napi_value(pValue)); 274 | result = obj; 275 | } else { 276 | auto exp = Napi::External::New(env, pValue); 277 | auto obj = PyNodeWrappedPythonObject::constructor.New({exp}); 278 | result = obj; 279 | } 280 | return result; 281 | } 282 | 283 | extern "C" { 284 | napi_value convert_python_to_napi_value(napi_env env, PyObject * obj) { 285 | return ConvertFromPython(env, obj); 286 | } 287 | } 288 | 289 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-colors@3.2.3: 6 | version "3.2.3" 7 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" 8 | integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== 9 | 10 | ansi-regex@^2.0.0: 11 | version "2.1.1" 12 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 13 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 14 | 15 | ansi-regex@^3.0.0: 16 | version "3.0.0" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 18 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 19 | 20 | ansi-regex@^4.1.0: 21 | version "4.1.0" 22 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 23 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 24 | 25 | ansi-styles@^3.2.1: 26 | version "3.2.1" 27 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 28 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 29 | dependencies: 30 | color-convert "^1.9.0" 31 | 32 | argparse@^1.0.7: 33 | version "1.0.10" 34 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 35 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 36 | dependencies: 37 | sprintf-js "~1.0.2" 38 | 39 | assertion-error@^1.1.0: 40 | version "1.1.0" 41 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 42 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 43 | 44 | balanced-match@^1.0.0: 45 | version "1.0.0" 46 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 47 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 48 | 49 | brace-expansion@^1.1.7: 50 | version "1.1.11" 51 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 52 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 53 | dependencies: 54 | balanced-match "^1.0.0" 55 | concat-map "0.0.1" 56 | 57 | browser-stdout@1.3.1: 58 | version "1.3.1" 59 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 60 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 61 | 62 | camelcase@^5.0.0: 63 | version "5.3.1" 64 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 65 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 66 | 67 | chai@^4.2.0: 68 | version "4.2.0" 69 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" 70 | integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== 71 | dependencies: 72 | assertion-error "^1.1.0" 73 | check-error "^1.0.2" 74 | deep-eql "^3.0.1" 75 | get-func-name "^2.0.0" 76 | pathval "^1.1.0" 77 | type-detect "^4.0.5" 78 | 79 | chalk@^2.0.1: 80 | version "2.4.2" 81 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 82 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 83 | dependencies: 84 | ansi-styles "^3.2.1" 85 | escape-string-regexp "^1.0.5" 86 | supports-color "^5.3.0" 87 | 88 | check-error@^1.0.2: 89 | version "1.0.2" 90 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 91 | integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 92 | 93 | cliui@^4.0.0: 94 | version "4.1.0" 95 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" 96 | integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== 97 | dependencies: 98 | string-width "^2.1.1" 99 | strip-ansi "^4.0.0" 100 | wrap-ansi "^2.0.0" 101 | 102 | code-point-at@^1.0.0: 103 | version "1.1.0" 104 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 105 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 106 | 107 | color-convert@^1.9.0: 108 | version "1.9.3" 109 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 110 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 111 | dependencies: 112 | color-name "1.1.3" 113 | 114 | color-name@1.1.3: 115 | version "1.1.3" 116 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 117 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 118 | 119 | concat-map@0.0.1: 120 | version "0.0.1" 121 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 122 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 123 | 124 | cross-spawn@^6.0.0: 125 | version "6.0.5" 126 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" 127 | integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== 128 | dependencies: 129 | nice-try "^1.0.4" 130 | path-key "^2.0.1" 131 | semver "^5.5.0" 132 | shebang-command "^1.2.0" 133 | which "^1.2.9" 134 | 135 | debug@3.2.6: 136 | version "3.2.6" 137 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 138 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 139 | dependencies: 140 | ms "^2.1.1" 141 | 142 | decamelize@^1.2.0: 143 | version "1.2.0" 144 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 145 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 146 | 147 | deep-eql@^3.0.1: 148 | version "3.0.1" 149 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 150 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 151 | dependencies: 152 | type-detect "^4.0.0" 153 | 154 | define-properties@^1.1.2: 155 | version "1.1.3" 156 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 157 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 158 | dependencies: 159 | object-keys "^1.0.12" 160 | 161 | diff@3.5.0: 162 | version "3.5.0" 163 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 164 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 165 | 166 | emoji-regex@^7.0.1: 167 | version "7.0.3" 168 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 169 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 170 | 171 | end-of-stream@^1.1.0: 172 | version "1.4.1" 173 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" 174 | integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== 175 | dependencies: 176 | once "^1.4.0" 177 | 178 | es-abstract@^1.5.1: 179 | version "1.13.0" 180 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" 181 | integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== 182 | dependencies: 183 | es-to-primitive "^1.2.0" 184 | function-bind "^1.1.1" 185 | has "^1.0.3" 186 | is-callable "^1.1.4" 187 | is-regex "^1.0.4" 188 | object-keys "^1.0.12" 189 | 190 | es-to-primitive@^1.2.0: 191 | version "1.2.0" 192 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" 193 | integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== 194 | dependencies: 195 | is-callable "^1.1.4" 196 | is-date-object "^1.0.1" 197 | is-symbol "^1.0.2" 198 | 199 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: 200 | version "1.0.5" 201 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 202 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 203 | 204 | esprima@^4.0.0: 205 | version "4.0.1" 206 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 207 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 208 | 209 | execa@^1.0.0: 210 | version "1.0.0" 211 | resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" 212 | integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== 213 | dependencies: 214 | cross-spawn "^6.0.0" 215 | get-stream "^4.0.0" 216 | is-stream "^1.1.0" 217 | npm-run-path "^2.0.0" 218 | p-finally "^1.0.0" 219 | signal-exit "^3.0.0" 220 | strip-eof "^1.0.0" 221 | 222 | find-up@3.0.0, find-up@^3.0.0: 223 | version "3.0.0" 224 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 225 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 226 | dependencies: 227 | locate-path "^3.0.0" 228 | 229 | flat@^4.1.0: 230 | version "4.1.0" 231 | resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" 232 | integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== 233 | dependencies: 234 | is-buffer "~2.0.3" 235 | 236 | fs.realpath@^1.0.0: 237 | version "1.0.0" 238 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 239 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 240 | 241 | function-bind@^1.1.1: 242 | version "1.1.1" 243 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 244 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 245 | 246 | get-caller-file@^1.0.1: 247 | version "1.0.3" 248 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" 249 | integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== 250 | 251 | get-caller-file@^2.0.1: 252 | version "2.0.5" 253 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 254 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 255 | 256 | get-func-name@^2.0.0: 257 | version "2.0.0" 258 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 259 | integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 260 | 261 | get-stream@^4.0.0: 262 | version "4.1.0" 263 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" 264 | integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== 265 | dependencies: 266 | pump "^3.0.0" 267 | 268 | glob@7.1.3: 269 | version "7.1.3" 270 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 271 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 272 | dependencies: 273 | fs.realpath "^1.0.0" 274 | inflight "^1.0.4" 275 | inherits "2" 276 | minimatch "^3.0.4" 277 | once "^1.3.0" 278 | path-is-absolute "^1.0.0" 279 | 280 | growl@1.10.5: 281 | version "1.10.5" 282 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 283 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 284 | 285 | has-flag@^3.0.0: 286 | version "3.0.0" 287 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 288 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 289 | 290 | has-symbols@^1.0.0: 291 | version "1.0.0" 292 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" 293 | integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= 294 | 295 | has@^1.0.1, has@^1.0.3: 296 | version "1.0.3" 297 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 298 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 299 | dependencies: 300 | function-bind "^1.1.1" 301 | 302 | he@1.2.0: 303 | version "1.2.0" 304 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 305 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 306 | 307 | inflight@^1.0.4: 308 | version "1.0.6" 309 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 310 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 311 | dependencies: 312 | once "^1.3.0" 313 | wrappy "1" 314 | 315 | inherits@2: 316 | version "2.0.3" 317 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 318 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 319 | 320 | invert-kv@^2.0.0: 321 | version "2.0.0" 322 | resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" 323 | integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== 324 | 325 | is-buffer@~2.0.3: 326 | version "2.0.3" 327 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" 328 | integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== 329 | 330 | is-callable@^1.1.4: 331 | version "1.1.4" 332 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" 333 | integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== 334 | 335 | is-date-object@^1.0.1: 336 | version "1.0.1" 337 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" 338 | integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= 339 | 340 | is-fullwidth-code-point@^1.0.0: 341 | version "1.0.0" 342 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 343 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 344 | dependencies: 345 | number-is-nan "^1.0.0" 346 | 347 | is-fullwidth-code-point@^2.0.0: 348 | version "2.0.0" 349 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 350 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 351 | 352 | is-regex@^1.0.4: 353 | version "1.0.4" 354 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" 355 | integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= 356 | dependencies: 357 | has "^1.0.1" 358 | 359 | is-stream@^1.1.0: 360 | version "1.1.0" 361 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 362 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 363 | 364 | is-symbol@^1.0.2: 365 | version "1.0.2" 366 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" 367 | integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== 368 | dependencies: 369 | has-symbols "^1.0.0" 370 | 371 | isexe@^2.0.0: 372 | version "2.0.0" 373 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 374 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 375 | 376 | js-yaml@3.13.1: 377 | version "3.13.1" 378 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 379 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 380 | dependencies: 381 | argparse "^1.0.7" 382 | esprima "^4.0.0" 383 | 384 | lcid@^2.0.0: 385 | version "2.0.0" 386 | resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" 387 | integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== 388 | dependencies: 389 | invert-kv "^2.0.0" 390 | 391 | locate-path@^3.0.0: 392 | version "3.0.0" 393 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 394 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 395 | dependencies: 396 | p-locate "^3.0.0" 397 | path-exists "^3.0.0" 398 | 399 | lodash@^4.17.11: 400 | version "4.17.19" 401 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" 402 | integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== 403 | 404 | log-symbols@2.2.0: 405 | version "2.2.0" 406 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" 407 | integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== 408 | dependencies: 409 | chalk "^2.0.1" 410 | 411 | map-age-cleaner@^0.1.1: 412 | version "0.1.3" 413 | resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" 414 | integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== 415 | dependencies: 416 | p-defer "^1.0.0" 417 | 418 | mem@^4.0.0: 419 | version "4.3.0" 420 | resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" 421 | integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== 422 | dependencies: 423 | map-age-cleaner "^0.1.1" 424 | mimic-fn "^2.0.0" 425 | p-is-promise "^2.0.0" 426 | 427 | mimic-fn@^2.0.0: 428 | version "2.1.0" 429 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 430 | integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 431 | 432 | minimatch@3.0.4, minimatch@^3.0.4: 433 | version "3.0.4" 434 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 435 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 436 | dependencies: 437 | brace-expansion "^1.1.7" 438 | 439 | minimist@0.0.8: 440 | version "0.0.8" 441 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 442 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 443 | 444 | mkdirp@0.5.1: 445 | version "0.5.1" 446 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 447 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 448 | dependencies: 449 | minimist "0.0.8" 450 | 451 | mocha@^6.1.4: 452 | version "6.1.4" 453 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.1.4.tgz#e35fada242d5434a7e163d555c705f6875951640" 454 | integrity sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg== 455 | dependencies: 456 | ansi-colors "3.2.3" 457 | browser-stdout "1.3.1" 458 | debug "3.2.6" 459 | diff "3.5.0" 460 | escape-string-regexp "1.0.5" 461 | find-up "3.0.0" 462 | glob "7.1.3" 463 | growl "1.10.5" 464 | he "1.2.0" 465 | js-yaml "3.13.1" 466 | log-symbols "2.2.0" 467 | minimatch "3.0.4" 468 | mkdirp "0.5.1" 469 | ms "2.1.1" 470 | node-environment-flags "1.0.5" 471 | object.assign "4.1.0" 472 | strip-json-comments "2.0.1" 473 | supports-color "6.0.0" 474 | which "1.3.1" 475 | wide-align "1.1.3" 476 | yargs "13.2.2" 477 | yargs-parser "13.0.0" 478 | yargs-unparser "1.5.0" 479 | 480 | ms@2.1.1: 481 | version "2.1.1" 482 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 483 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 484 | 485 | ms@^2.1.1: 486 | version "2.1.2" 487 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 488 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 489 | 490 | nice-try@^1.0.4: 491 | version "1.0.5" 492 | resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 493 | integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 494 | 495 | node-addon-api@^1.7.1: 496 | version "1.7.1" 497 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" 498 | integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== 499 | 500 | node-environment-flags@1.0.5: 501 | version "1.0.5" 502 | resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" 503 | integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== 504 | dependencies: 505 | object.getownpropertydescriptors "^2.0.3" 506 | semver "^5.7.0" 507 | 508 | npm-run-path@^2.0.0: 509 | version "2.0.2" 510 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 511 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= 512 | dependencies: 513 | path-key "^2.0.0" 514 | 515 | number-is-nan@^1.0.0: 516 | version "1.0.1" 517 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 518 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 519 | 520 | object-keys@^1.0.11, object-keys@^1.0.12: 521 | version "1.1.1" 522 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 523 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 524 | 525 | object.assign@4.1.0: 526 | version "4.1.0" 527 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" 528 | integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== 529 | dependencies: 530 | define-properties "^1.1.2" 531 | function-bind "^1.1.1" 532 | has-symbols "^1.0.0" 533 | object-keys "^1.0.11" 534 | 535 | object.getownpropertydescriptors@^2.0.3: 536 | version "2.0.3" 537 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" 538 | integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= 539 | dependencies: 540 | define-properties "^1.1.2" 541 | es-abstract "^1.5.1" 542 | 543 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 544 | version "1.4.0" 545 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 546 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 547 | dependencies: 548 | wrappy "1" 549 | 550 | os-locale@^3.0.0, os-locale@^3.1.0: 551 | version "3.1.0" 552 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" 553 | integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== 554 | dependencies: 555 | execa "^1.0.0" 556 | lcid "^2.0.0" 557 | mem "^4.0.0" 558 | 559 | p-defer@^1.0.0: 560 | version "1.0.0" 561 | resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" 562 | integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= 563 | 564 | p-finally@^1.0.0: 565 | version "1.0.0" 566 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 567 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 568 | 569 | p-is-promise@^2.0.0: 570 | version "2.1.0" 571 | resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" 572 | integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== 573 | 574 | p-limit@^2.0.0: 575 | version "2.2.0" 576 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" 577 | integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== 578 | dependencies: 579 | p-try "^2.0.0" 580 | 581 | p-locate@^3.0.0: 582 | version "3.0.0" 583 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 584 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 585 | dependencies: 586 | p-limit "^2.0.0" 587 | 588 | p-try@^2.0.0: 589 | version "2.2.0" 590 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 591 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 592 | 593 | path-exists@^3.0.0: 594 | version "3.0.0" 595 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 596 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 597 | 598 | path-is-absolute@^1.0.0: 599 | version "1.0.1" 600 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 601 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 602 | 603 | path-key@^2.0.0, path-key@^2.0.1: 604 | version "2.0.1" 605 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 606 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 607 | 608 | pathval@^1.1.0: 609 | version "1.1.0" 610 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" 611 | integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= 612 | 613 | pump@^3.0.0: 614 | version "3.0.0" 615 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 616 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 617 | dependencies: 618 | end-of-stream "^1.1.0" 619 | once "^1.3.1" 620 | 621 | require-directory@^2.1.1: 622 | version "2.1.1" 623 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 624 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 625 | 626 | require-main-filename@^1.0.1: 627 | version "1.0.1" 628 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 629 | integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= 630 | 631 | require-main-filename@^2.0.0: 632 | version "2.0.0" 633 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 634 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 635 | 636 | semver@^5.5.0, semver@^5.7.0: 637 | version "5.7.0" 638 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" 639 | integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== 640 | 641 | set-blocking@^2.0.0: 642 | version "2.0.0" 643 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 644 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 645 | 646 | shebang-command@^1.2.0: 647 | version "1.2.0" 648 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 649 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 650 | dependencies: 651 | shebang-regex "^1.0.0" 652 | 653 | shebang-regex@^1.0.0: 654 | version "1.0.0" 655 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 656 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 657 | 658 | signal-exit@^3.0.0: 659 | version "3.0.2" 660 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 661 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 662 | 663 | sprintf-js@~1.0.2: 664 | version "1.0.3" 665 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 666 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 667 | 668 | string-width@^1.0.1: 669 | version "1.0.2" 670 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 671 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 672 | dependencies: 673 | code-point-at "^1.0.0" 674 | is-fullwidth-code-point "^1.0.0" 675 | strip-ansi "^3.0.0" 676 | 677 | "string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: 678 | version "2.1.1" 679 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 680 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 681 | dependencies: 682 | is-fullwidth-code-point "^2.0.0" 683 | strip-ansi "^4.0.0" 684 | 685 | string-width@^3.0.0: 686 | version "3.1.0" 687 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 688 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 689 | dependencies: 690 | emoji-regex "^7.0.1" 691 | is-fullwidth-code-point "^2.0.0" 692 | strip-ansi "^5.1.0" 693 | 694 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 695 | version "3.0.1" 696 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 697 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 698 | dependencies: 699 | ansi-regex "^2.0.0" 700 | 701 | strip-ansi@^4.0.0: 702 | version "4.0.0" 703 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 704 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 705 | dependencies: 706 | ansi-regex "^3.0.0" 707 | 708 | strip-ansi@^5.1.0: 709 | version "5.2.0" 710 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 711 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 712 | dependencies: 713 | ansi-regex "^4.1.0" 714 | 715 | strip-eof@^1.0.0: 716 | version "1.0.0" 717 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 718 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= 719 | 720 | strip-json-comments@2.0.1: 721 | version "2.0.1" 722 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 723 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 724 | 725 | supports-color@6.0.0: 726 | version "6.0.0" 727 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" 728 | integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== 729 | dependencies: 730 | has-flag "^3.0.0" 731 | 732 | supports-color@^5.3.0: 733 | version "5.5.0" 734 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 735 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 736 | dependencies: 737 | has-flag "^3.0.0" 738 | 739 | type-detect@^4.0.0, type-detect@^4.0.5: 740 | version "4.0.8" 741 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 742 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 743 | 744 | which-module@^2.0.0: 745 | version "2.0.0" 746 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 747 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 748 | 749 | which@1.3.1, which@^1.2.9: 750 | version "1.3.1" 751 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 752 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 753 | dependencies: 754 | isexe "^2.0.0" 755 | 756 | wide-align@1.1.3: 757 | version "1.1.3" 758 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 759 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 760 | dependencies: 761 | string-width "^1.0.2 || 2" 762 | 763 | wrap-ansi@^2.0.0: 764 | version "2.1.0" 765 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" 766 | integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= 767 | dependencies: 768 | string-width "^1.0.1" 769 | strip-ansi "^3.0.1" 770 | 771 | wrappy@1: 772 | version "1.0.2" 773 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 774 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 775 | 776 | "y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: 777 | version "4.0.0" 778 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 779 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== 780 | 781 | yargs-parser@13.0.0: 782 | version "13.0.0" 783 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b" 784 | integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw== 785 | dependencies: 786 | camelcase "^5.0.0" 787 | decamelize "^1.2.0" 788 | 789 | yargs-parser@^11.1.1: 790 | version "11.1.1" 791 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" 792 | integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== 793 | dependencies: 794 | camelcase "^5.0.0" 795 | decamelize "^1.2.0" 796 | 797 | yargs-parser@^13.0.0: 798 | version "13.1.1" 799 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" 800 | integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== 801 | dependencies: 802 | camelcase "^5.0.0" 803 | decamelize "^1.2.0" 804 | 805 | yargs-unparser@1.5.0: 806 | version "1.5.0" 807 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.5.0.tgz#f2bb2a7e83cbc87bb95c8e572828a06c9add6e0d" 808 | integrity sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw== 809 | dependencies: 810 | flat "^4.1.0" 811 | lodash "^4.17.11" 812 | yargs "^12.0.5" 813 | 814 | yargs@13.2.2: 815 | version "13.2.2" 816 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993" 817 | integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA== 818 | dependencies: 819 | cliui "^4.0.0" 820 | find-up "^3.0.0" 821 | get-caller-file "^2.0.1" 822 | os-locale "^3.1.0" 823 | require-directory "^2.1.1" 824 | require-main-filename "^2.0.0" 825 | set-blocking "^2.0.0" 826 | string-width "^3.0.0" 827 | which-module "^2.0.0" 828 | y18n "^4.0.0" 829 | yargs-parser "^13.0.0" 830 | 831 | yargs@^12.0.5: 832 | version "12.0.5" 833 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" 834 | integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== 835 | dependencies: 836 | cliui "^4.0.0" 837 | decamelize "^1.2.0" 838 | find-up "^3.0.0" 839 | get-caller-file "^1.0.1" 840 | os-locale "^3.0.0" 841 | require-directory "^2.1.1" 842 | require-main-filename "^1.0.1" 843 | set-blocking "^2.0.0" 844 | string-width "^2.0.0" 845 | which-module "^2.0.0" 846 | y18n "^3.2.1 || ^4.0.0" 847 | yargs-parser "^11.1.1" 848 | --------------------------------------------------------------------------------