├── .github └── workflows │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── patch ├── node.patch └── node │ └── src │ ├── node_embedding_api.cc │ └── node_embedding_api.h ├── scripts ├── archive.py ├── build.py ├── config.py ├── download.py ├── headers.py ├── patch.py ├── postproc.py └── test.py └── test ├── CMakeLists.txt ├── process_argv.cc └── simple.cc /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | nodeVersion: 7 | description: "Node.js version" 8 | required: true 9 | 10 | env: 11 | LIBNODE_NODE_VERSION: ${{ github.event.inputs.nodeVersion }} 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [macos-11, windows-2019, ubuntu-20.04] 19 | x86: [0, 1] 20 | small_icu: [0, 1] 21 | exclude: 22 | - os: macos-11 23 | x86: 1 24 | - os: ubuntu-20.04 25 | x86: 1 26 | runs-on: ${{ matrix.os }} 27 | env: 28 | LIBNODE_X86: ${{ matrix.x86 }} 29 | LIBNODE_SMALL_ICU: ${{ matrix.small_icu }} 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v2 33 | 34 | - uses: actions/setup-python@v2 35 | with: 36 | python-version: '3.x' 37 | 38 | - name: Install nasm on Windows 39 | run: choco install -y nasm 40 | if: runner.os == 'Windows' 41 | 42 | - name: Install GNU patch on Windows 43 | run: choco install -y patch 44 | if: runner.os == 'Windows' 45 | 46 | - name: Install ninja on macOS 47 | run: HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja 48 | if: runner.os == 'macOS' 49 | 50 | - name: Install ninja on Ubuntu 51 | run: sudo apt-get install -y ninja-build 52 | if: runner.os == 'Linux' 53 | 54 | - name: Download source code of Node.js 55 | run: python -m scripts.download 56 | 57 | - name: Patch source code of Node.js 58 | run: python -m scripts.patch 59 | 60 | - name: Build 61 | run: python -m scripts.build 62 | 63 | - name: Copy headers 64 | run: python -m scripts.headers 65 | 66 | - uses: ilammy/msvc-dev-cmd@v1 67 | - name: Postprocess 68 | run: python -m scripts.postproc 69 | 70 | - name: Archive 71 | run: | 72 | zipname=$(python -m scripts.archive) 73 | echo "::set-output name=zipname::$zipname" 74 | shell: bash 75 | id: archive 76 | 77 | - name: Upload artifact 78 | uses: actions/upload-artifact@v2 79 | with: 80 | name: ${{ steps.archive.outputs.zipname }} 81 | path: ${{ steps.archive.outputs.zipname }} 82 | 83 | - name: Test 84 | run: python -m scripts.test 85 | 86 | gh_release: 87 | needs: build 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Download artifacts 91 | uses: actions/download-artifact@v2 92 | - name: Compute checksums 93 | run: | 94 | mkdir zips 95 | mv ./*/*.zip ./zips 96 | cd zips 97 | echo '## SHA-1 Checksums' > ../release_notes.md 98 | echo '```' >> ../release_notes.md 99 | sha1sum --binary *.zip >> ../release_notes.md 100 | echo '```' >> ../release_notes.md 101 | - name: Create release 102 | id: create_release 103 | uses: actions/create-release@v1 104 | env: 105 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | with: 107 | tag_name: ${{ env.LIBNODE_NODE_VERSION }} 108 | release_name: ${{ env.LIBNODE_NODE_VERSION }} 109 | body_path: release_notes.md 110 | - name: Upload release assets 111 | uses: csexton/release-asset-action@v2 112 | with: 113 | pattern: "zips/*.zip" 114 | github-token: ${{ secrets.GITHUB_TOKEN }} 115 | release-url: ${{ steps.create_release.outputs.upload_url }} 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .DS_Store 3 | /release 4 | /node-* 5 | /cmake-build-* 6 | /node_headers.tar.gz 7 | .idea 8 | build 9 | .vs 10 | /libnode 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(libnode) 4 | 5 | file(GLOB LIBNODE_ARCHIVES ${CMAKE_CURRENT_LIST_DIR}/lib/*) 6 | set(LIBNODE_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include/node) 7 | 8 | add_library(libnode STATIC ${CMAKE_CURRENT_LIST_DIR}/dummy.c) 9 | 10 | if(UNIX AND NOT APPLE) 11 | target_link_libraries(libnode INTERFACE "-Wl,--start-group" ${LIBNODE_ARCHIVES} "-Wl,--end-group" "-pthread" "-ldl" "-lrt" "-lm" "-rdynamic") 12 | elseif(WIN32) 13 | target_link_libraries(libnode INTERFACE ${LIBNODE_ARCHIVES} ws2_32 dbghelp crypt32 winmm iphlpapi psapi userenv) 14 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 15 | # Nodejs is compiled with /SAFESEH:NO with when using MSVC targeting 32bit: https://github.com/nodejs/node/commit/2f477bd34de128683c8d183e759b8f098205b1c9 16 | target_link_options(libnode INTERFACE "-SAFESEH:NO") 17 | endif() 18 | else() 19 | target_link_libraries(libnode INTERFACE ${LIBNODE_ARCHIVES}) 20 | list(JOIN LIBNODE_ARCHIVES " " LIBNODE_PRELINK_FLAGS) 21 | set_target_properties(libnode PROPERTIES 22 | XCODE_ATTRIBUTE_OTHER_LIBTOOLFLAGS ${LIBNODE_PRELINK_FLAGS} 23 | ) 24 | endif() 25 | 26 | target_include_directories(libnode INTERFACE ${LIBNODE_INCLUDE}) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Chi Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libnode 2 | 3 | [![release.yml workflow status](https://github.com/patr0nus/libnode/workflows/Release/badge.svg)](https://github.com/patr0nus/libnode/actions/workflows/release.yml) 4 | 5 | This repo contains the scripts that build [Node.js](http://nodejs.org/) as a static library for embedding in [DeskGap](https://deskgap.com/). 6 | 7 | ## Usage 8 | 9 | ### Configuring 10 | 11 | #### Specify the Node version: 12 | ```sh 13 | export LIBNODE_NODE_VERSION=v15.11.0 14 | ``` 15 | 16 | #### Remove `Intl` support to reduce the size (optional): 17 | ```sh 18 | export LIBNODE_CONFIG_FLAGS=--without-intl 19 | export LIBNODE_ZIP_SUFFIX=-nointl 20 | ``` 21 | 22 | #### Build the x86 version (optional, Windows only): 23 | ```sh 24 | export LIBNODE_X86=1 25 | ``` 26 | 27 | ### Downloading the source code of Node.js: 28 | ```sh 29 | python3 -m scripts.download 30 | ``` 31 | 32 | ### Patch the source code: 33 | ```sh 34 | python3 -m scripts.patch 35 | ``` 36 | 37 | ### Building Node.js: 38 | ```sh 39 | python3 -m scripts.build 40 | ``` 41 | 42 | ### Postprocessing the static library files: 43 | ```sh 44 | python3 -m scripts.postproc 45 | ``` 46 | 47 | ### Copying the headers: 48 | ```sh 49 | python3 -m scripts.headers 50 | ``` 51 | 52 | ### Testing the library: 53 | ```sh 54 | python3 -m scripts.test 55 | ``` 56 | 57 | ### Archiving: 58 | ```sh 59 | python3 -m scripts.archive 60 | ``` 61 | -------------------------------------------------------------------------------- /patch/node.patch: -------------------------------------------------------------------------------- 1 | --- a/node.gyp 2 | +++ b/node.gyp 3 | @@ -575,6 +575,8 @@ 4 | ], 5 | 6 | 'sources': [ 7 | + 'src/node_embedding_api.cc', 8 | + 'src/node_embedding_api.h', 9 | 'src/api/async_resource.cc', 10 | 'src/api/callback.cc', 11 | 'src/api/embed_helpers.cc', 12 | --- a/tools/install.py 13 | +++ b/tools/install.py 14 | @@ -174,6 +174,7 @@ def headers(action): 15 | 'config.gypi', 16 | 'src/node.h', 17 | 'src/node_api.h', 18 | + 'src/node_embedding_api.h', 19 | 'src/js_native_api.h', 20 | 'src/js_native_api_types.h', 21 | 'src/node_api_types.h', 22 | -------------------------------------------------------------------------------- /patch/node/src/node_embedding_api.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "node.h" 7 | #include "node_embedding_api.h" 8 | #include "uv.h" 9 | 10 | #include "v8.h" 11 | 12 | namespace { 13 | char* join_errors(const std::vector& errors) { 14 | std::string joined_error; 15 | for (std::size_t i = 0; i < errors.size(); ++i) { 16 | if (i > 0) { 17 | joined_error += '\n'; 18 | } 19 | joined_error += errors[i]; 20 | } 21 | char* c_result = (char*)malloc(joined_error.size() + 1); 22 | joined_error.copy(c_result, joined_error.size()); 23 | c_result[joined_error.size()] = '\0'; 24 | return c_result; 25 | } 26 | 27 | std::vector create_arg_vec(int argc, const char* const* argv) { 28 | std::vector vec; 29 | if (argc > 0) { 30 | vec.reserve(argc); 31 | for (int i = 0; i < argc; ++i) { 32 | vec.emplace_back(argv[i]); 33 | } 34 | } 35 | return vec; 36 | } 37 | 38 | node_run_result_t RunNodeInstance( 39 | node::MultiIsolatePlatform* platform, 40 | const std::vector& args, 41 | const std::vector& exec_args, 42 | napi_addon_register_func napi_reg_func 43 | ) { 44 | std::vector errors; 45 | std::unique_ptr setup = 46 | node::CommonEnvironmentSetup::Create( 47 | platform, &errors, args, exec_args, 48 | static_cast( 49 | node::EnvironmentFlags::kDefaultFlags | 50 | node::EnvironmentFlags::kNoGlobalSearchPaths 51 | ) 52 | ); 53 | 54 | if (!setup) { 55 | return { 1, join_errors(errors) }; 56 | } 57 | 58 | v8::Isolate* isolate = setup->isolate(); 59 | node::Environment* env = setup->env(); 60 | 61 | node_run_result_t result { 0, nullptr }; 62 | node::SetProcessExitHandler(env, [&](node::Environment* env, int exit_code) { 63 | result.exit_code = exit_code; 64 | node::Stop(env); 65 | }); 66 | 67 | { 68 | v8::Locker locker(isolate); 69 | v8::Isolate::Scope isolate_scope(isolate); 70 | v8::HandleScope handle_scope(isolate); 71 | v8::Context::Scope context_scope(setup->context()); 72 | 73 | node::AddLinkedBinding(env, napi_module { 74 | NAPI_MODULE_VERSION, 75 | node::ModuleFlags::kLinked, 76 | nullptr, 77 | napi_reg_func, 78 | "__embedder_mod", 79 | nullptr, 80 | {0}, 81 | }); 82 | 83 | v8::MaybeLocal loadenv_ret = node::LoadEnvironment( 84 | env, 85 | "globalThis.require = require('module').createRequire(process.execPath);" 86 | "process._linkedBinding('__embedder_mod');" 87 | ); 88 | 89 | if (loadenv_ret.IsEmpty()) { // There has been a JS exception. 90 | result.exit_code = 1; 91 | } 92 | else { 93 | int evtloop_ret = node::SpinEventLoop(env).FromMaybe(1); 94 | if (result.exit_code == 0) { 95 | result.exit_code = evtloop_ret; 96 | } 97 | } 98 | node::Stop(env); 99 | } 100 | 101 | return result; 102 | } 103 | } 104 | 105 | extern "C" { 106 | node_run_result_t node_run(node_options_t options) { 107 | std::vector process_args = create_arg_vec(options.process_argc, options.process_argv); 108 | if (process_args.empty()) { 109 | return { 1, join_errors({ "process args is empty" })}; 110 | } 111 | std::vector args { process_args[0] }; 112 | 113 | std::vector exec_args; 114 | std::vector errors; 115 | int exit_code = node::InitializeNodeWithArgs( 116 | &args, &exec_args, &errors, 117 | static_cast( 118 | node::ProcessFlags::kDisableCLIOptions | 119 | node::ProcessFlags::kDisableNodeOptionsEnv 120 | ) 121 | ); 122 | 123 | if (exit_code != 0) { 124 | return { exit_code, join_errors(errors) }; 125 | } 126 | std::unique_ptr platform = node::MultiIsolatePlatform::Create(4); 127 | v8::V8::InitializePlatform(platform.get()); 128 | v8::V8::Initialize(); 129 | 130 | node_run_result_t result = RunNodeInstance(platform.get(), process_args, exec_args, options.napi_reg_func); 131 | 132 | v8::V8::Dispose(); 133 | v8::V8::ShutdownPlatform(); 134 | 135 | return result; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /patch/node/src/node_embedding_api.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_EMBEDDING_API_H 2 | #define NODE_EMBEDDING_API_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct { 11 | int process_argc; 12 | const char* const * process_argv; 13 | 14 | napi_addon_register_func napi_reg_func; 15 | } node_options_t; 16 | 17 | typedef struct { 18 | int exit_code; 19 | char* error; 20 | } node_run_result_t; 21 | 22 | node_run_result_t node_run(node_options_t); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /scripts/archive.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import shutil 4 | import sys 5 | 6 | from . import config 7 | 8 | zipBasename = 'libnode-{}-{}-{}{}'.format( 9 | config.nodeVersion, 10 | sys.platform, 11 | 'x86' if config.x86 else 'x64', 12 | config.zipBasenameSuffix 13 | ) 14 | 15 | shutil.make_archive(zipBasename, 'zip', base_dir='libnode') 16 | 17 | print(zipBasename + '.zip') 18 | -------------------------------------------------------------------------------- /scripts/build.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import sys 4 | import os 5 | import subprocess 6 | import shutil 7 | 8 | from . import config 9 | 10 | os.chdir('node-{}'.format(config.nodeVersion)) 11 | 12 | configureArgvs = [ '--enable-static' ] + config.configFlags 13 | 14 | if sys.platform == 'win32': 15 | env = os.environ.copy() 16 | env['config_flags'] = ' '.join(configureArgvs) 17 | 18 | subprocess.check_call( 19 | ['cmd', '/c', 'vcbuild.bat'] + (['x86'] if config.x86 else []), 20 | env=env 21 | ) 22 | else: 23 | subprocess.check_call([ sys.executable, 'configure.py' ] + configureArgvs) 24 | subprocess.check_call(['make', '-j4']) 25 | -------------------------------------------------------------------------------- /scripts/config.py: -------------------------------------------------------------------------------- 1 | assert __name__ != "__main__" 2 | 3 | import os 4 | 5 | nodeVersion = os.environ['LIBNODE_NODE_VERSION'] 6 | configFlags = (os.environ.get('LIBNODE_CONFIG_FLAGS') or '').split() 7 | x86 = os.environ.get('LIBNODE_X86') == '1' 8 | zipBasenameSuffix = os.environ.get('LIBNODE_ZIP_SUFFIX', '') 9 | 10 | if os.environ.get('LIBNODE_SMALL_ICU', '') == '1': 11 | configFlags += ['--with-intl=small-icu'] 12 | zipBasenameSuffix += '-smallicu' 13 | -------------------------------------------------------------------------------- /scripts/download.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import urllib.request 4 | import tarfile 5 | import os 6 | import subprocess 7 | 8 | from . import config 9 | 10 | 11 | url = 'https://nodejs.org/dist/{}/node-{}.tar.gz'.format(config.nodeVersion, config.nodeVersion) 12 | 13 | print('Downloading...') 14 | urllib.request.urlretrieve(url, filename="node_src.tar.gz") 15 | 16 | print("Extracting...") 17 | with tarfile.open('node_src.tar.gz') as srcTarball: 18 | srcTarball.extractall() 19 | 20 | os.remove('node_src.tar.gz') 21 | -------------------------------------------------------------------------------- /scripts/headers.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import sys 4 | import os 5 | import subprocess 6 | 7 | from . import config 8 | 9 | os.chdir('node-{}'.format(config.nodeVersion)) 10 | 11 | 12 | env = os.environ.copy() 13 | env['HEADERS_ONLY'] = '1' 14 | 15 | subprocess.check_call([ sys.executable, 'tools/install.py', 'install', '.', '' ], env=env) 16 | -------------------------------------------------------------------------------- /scripts/patch.py: -------------------------------------------------------------------------------- 1 | from . import config 2 | import os 3 | import subprocess 4 | import shutil 5 | 6 | os.chdir('node-{}'.format(config.nodeVersion)) 7 | 8 | shutil.copytree('../patch/node', '.', dirs_exist_ok=True) 9 | subprocess.check_call(['patch', '-p1', '-i', '../patch/node.patch']) 10 | -------------------------------------------------------------------------------- /scripts/postproc.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import sys 4 | import os 5 | import shutil 6 | import subprocess 7 | import glob 8 | 9 | from . import config 10 | 11 | 12 | 13 | nodeSrcFolder = 'node-{}'.format(config.nodeVersion) 14 | resultFolder = 'libnode' 15 | 16 | libFolder = os.path.join(resultFolder, 'lib') 17 | 18 | shutil.rmtree(resultFolder, ignore_errors=True) 19 | 20 | os.mkdir(resultFolder) 21 | os.mkdir(libFolder) 22 | 23 | def filterLibFile(filename): 24 | return 'gtest' not in filename and 'v8_nosnapshot' not in filename and 'v8_init' not in filename and 'icutools' not in filename 25 | 26 | if sys.platform == 'win32': 27 | for libFile in os.scandir(nodeSrcFolder + '\\out\\Release\\lib'): 28 | if libFile.is_file() and libFile.name.endswith('.lib') and filterLibFile(libFile.name): 29 | print('Copying', libFile.name) 30 | shutil.copy(libFile.path, libFolder) 31 | elif sys.platform == 'darwin': 32 | for libFile in os.scandir(nodeSrcFolder + '/out/Release'): 33 | if libFile.is_file() and libFile.name.endswith('.a') and filterLibFile(libFile.name): 34 | print('Copying', libFile.name) 35 | shutil.copy(libFile.path, libFolder) 36 | print('Striping', libFile.name) 37 | subprocess.check_call(['strip', '-x', os.path.join(libFolder, libFile.name)]) 38 | elif sys.platform == 'linux': 39 | for dirname, _, basenames in os.walk(nodeSrcFolder + '/out/Release/obj.target'): 40 | for basename in basenames: 41 | if basename.endswith('.a') and filterLibFile(basename): 42 | subprocess.run( 43 | 'ar -t {} | xargs ar rs {}'.format( 44 | os.path.join(dirname, basename), 45 | os.path.join(libFolder, basename) 46 | ), 47 | check=True, shell=True 48 | ) 49 | 50 | additional_obj_glob = nodeSrcFolder + '/out/Release/obj.target/node/gen/*.o' 51 | if sys.platform == 'win32': 52 | additional_obj_glob = nodeSrcFolder + '/out/Release/obj/node_mksnapshot/src/*.obj' 53 | 54 | if sys.platform == 'win32': 55 | subprocess.check_call([ 56 | 'lib', '/OUT:' + os.path.join(libFolder, "libnode_snapshot.lib") 57 | ] + 58 | glob.glob(additional_obj_glob) + 59 | glob.glob(nodeSrcFolder + '/out/Release/obj/node_mksnapshot/tools/msvs/pch/*.obj') 60 | ) 61 | else: 62 | subprocess.check_call([ 63 | 'ar', 'cr', 64 | os.path.join(libFolder, "libnode_snapshot.a") 65 | ] + glob.glob(additional_obj_glob)) 66 | 67 | shutil.copytree(os.path.join(nodeSrcFolder, 'include'), os.path.join(resultFolder, 'include')) 68 | shutil.copyfile('CMakeLists.txt', os.path.join(resultFolder, 'CMakeLists.txt')) 69 | 70 | with open(os.path.join(resultFolder, 'dummy.c'), "w") as dummy_c_file: 71 | print("void libnode_dummy_func() { }", file=dummy_c_file) 72 | -------------------------------------------------------------------------------- /scripts/test.py: -------------------------------------------------------------------------------- 1 | assert __name__ == "__main__" 2 | 3 | import sys 4 | import os 5 | import subprocess 6 | import shutil 7 | 8 | from . import config 9 | 10 | os.chdir('test') 11 | shutil.rmtree('build', ignore_errors=True) 12 | os.mkdir('build') 13 | os.chdir('build') 14 | if sys.platform == 'win32': 15 | subprocess.check_call(['cmake', '-G', 'Visual Studio 16 2019', '-A', ('Win32' if config.x86 else 'x64'), '-S', '..']) 16 | else: 17 | subprocess.check_call(['cmake', '-G', 'Unix Makefiles', '-DCMAKE_BUILD_TYPE=Release', '-S', '..']) 18 | 19 | subprocess.check_call(['cmake', '--build', '.', '--config', 'Release']) 20 | 21 | tests = [ 22 | # executable args expected_output 23 | ["simple", ["console.log(require('http').STATUS_CODES[418])"], ["I'm a Teapot", "exit code: 0"]], 24 | ["simple", ["process.exit(12)"], ["exit code: 12"]], 25 | ["simple", ["invalid javascript"], ["napi_run_script failed", "exit code: 1"]], 26 | ["process_argv", [], ["hello node", "exit code: 0"]], 27 | ] 28 | 29 | failed = False 30 | 31 | for test in tests: 32 | [exec_name, args, expected_output] = test 33 | exec_path = f'Release\\{exec_name}.exe' if sys.platform == 'win32' else f'./{exec_name}' 34 | output = subprocess.check_output([exec_path] + args).decode().strip().splitlines() 35 | if output != expected_output: 36 | print("Assertion Failed. Expected:", expected_output, "Actual:", output) 37 | failed = True 38 | 39 | if failed: 40 | exit(1) 41 | 42 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(CMAKE_CXX_STANDARD 11) 4 | 5 | 6 | project(libnode_test) 7 | 8 | add_subdirectory(${PROJECT_SOURCE_DIR}/../libnode libnode_out) 9 | 10 | add_executable(simple simple.cc) 11 | target_link_libraries(simple libnode) 12 | 13 | add_executable(process_argv process_argv.cc) 14 | target_link_libraries(process_argv libnode) 15 | 16 | if(WIN32) 17 | target_compile_options(simple PRIVATE "/MT") 18 | target_compile_options(process_argv PRIVATE "/MT") 19 | endif() 20 | -------------------------------------------------------------------------------- /test/process_argv.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static napi_value napi_entry(napi_env env, napi_value exports) { 6 | napi_value script_string; 7 | if (napi_ok != napi_create_string_utf8(env, 8 | "console.log(process.argv[1], process.argv[2])", 9 | NAPI_AUTO_LENGTH, 10 | &script_string)) { 11 | printf("napi_create_string_utf8 failed\n"); 12 | return nullptr; 13 | } 14 | 15 | napi_value result; 16 | if (napi_ok != napi_run_script(env, script_string, &result)) { 17 | printf("napi_run_script failed\n"); 18 | return nullptr; 19 | } 20 | return nullptr; 21 | } 22 | 23 | 24 | int main(int argc, char** argv) { 25 | const char* process_args[] = { argv[0], "hello", "node" }; 26 | node_run_result_t res = node_run(node_options_t { 3, (const char* const*)process_args, napi_entry }); 27 | if (res.error) { 28 | printf("%s\n", res.error); 29 | } 30 | printf("exit code: %d\n", res.exit_code); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /test/simple.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static const char* script; 6 | 7 | static napi_value napi_entry(napi_env env, napi_value exports) { 8 | 9 | napi_value script_string; 10 | if (napi_ok != napi_create_string_utf8(env, 11 | script, 12 | strlen(script), 13 | &script_string)) { 14 | printf("napi_create_string_utf8 failed\n"); 15 | return nullptr; 16 | } 17 | 18 | napi_value result; 19 | if (napi_ok != napi_run_script(env, script_string, &result)) { 20 | printf("napi_run_script failed\n"); 21 | return nullptr; 22 | } 23 | return nullptr; 24 | } 25 | 26 | int main(int argc, const char** argv) { 27 | if (argc <= 1) { 28 | printf("No script arg\n"); 29 | return 1; 30 | } 31 | script = argv[1]; 32 | node_run_result_t res = node_run({ argc, (const char* const*)argv, napi_entry }); 33 | if (res.error) { 34 | printf("%s\n", res.error); 35 | } 36 | printf("exit code: %d\n", res.exit_code); 37 | return 0; 38 | } 39 | --------------------------------------------------------------------------------