├── .github └── workflows │ └── test.yaml ├── .gitignore ├── LICENSE ├── README.md ├── README.zhCN.md ├── package.json ├── pnpm-lock.yaml ├── src ├── compiler │ ├── bytesource.ts │ └── index.ts ├── index.ts ├── loader │ ├── index.ts │ └── utils.ts └── walker │ └── index.ts ├── test ├── app │ ├── foobar │ │ └── foo.js │ ├── index.js │ └── sub │ │ ├── index.js │ │ └── textfile.txt └── test.js └── tsconfig.json /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test Suite 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14.x, 16.x, 18.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: pnpm/action-setup@v2.2.2 21 | with: 22 | version: 7 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'pnpm' 28 | - name: Install dependencies 29 | run: pnpm install 30 | - run: npm run build --if-present 31 | - run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | test/dist 4 | coverage/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WangLei 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 | # little-byte 2 | 3 | Compile Node.js code into bytecode. 4 | 5 | [中文文档 | Chinese Docs](https://github.com/Nihiue/little-byte/blob/main/README.zhCN.md) 6 | 7 | ![typescript](https://img.shields.io/npm/types/scrub-js.svg) 8 | [![npm version](https://badge.fury.io/js/little-byte.svg)](https://www.npmjs.com/package/little-byte) 9 | [![Test Suite](https://github.com/Nihiue/little-byte/actions/workflows/test.yaml/badge.svg)](https://github.com/Nihiue/little-byte/actions/workflows/test.yaml) 10 | 11 | ## Install 12 | 13 | ```bash 14 | $ npm install --save-dev little-byte 15 | ``` 16 | 17 | ## Compile App 18 | 19 | ### Prepare Build Script 20 | 21 | Create build/index.js 22 | 23 | ```javascript 24 | const { walker } = require('little-byte').default; 25 | const path = require('path'); 26 | 27 | walker.start({ 28 | inputDir: path.join(__dirname, '../src'), 29 | outputDir: path.join(__dirname, '../dist'), 30 | onFile(fileInfo, defaultAction) { 31 | if (fileInfo.relativePath.startsWith('foobar/')) { 32 | return 'ignore'; 33 | } 34 | 35 | if (fileInfo.ext === '.jpg') { 36 | return 'ignore'; 37 | } 38 | 39 | if (fileInfo.name === 'my-dog.txt') { 40 | return 'ignore'; 41 | } 42 | 43 | if (fileInfo.isScript) { 44 | return 'compile'; 45 | } else { 46 | // copy none-js files to dist folder 47 | return 'copy'; 48 | } 49 | } 50 | }); 51 | ``` 52 | 53 | ### Build 54 | 55 | ```bash 56 | $ node build/index.js 57 | ``` 58 | 59 | ## Run Compiled App 60 | 61 | Create app-entry.js 62 | 63 | ```javascript 64 | 65 | require('little-byte'); 66 | 67 | // now you can require *.bytecode 68 | require('./dist/index'); 69 | 70 | ``` 71 | 72 | ## Limitations 73 | 74 | ### Using same Node.js version for building and running bytecode 75 | 76 | The format of bytecode might change over Node.js versions. 77 | 78 | ### Bytecode does not protect constant values 79 | 80 | It's possible to recover constant strings from bytecode with hex editor. 81 | 82 | ## API 83 | 84 | ``` typescript 85 | 86 | littleByte.compiler.compileFile(filePath: string, outputDir?: string): Promise 87 | 88 | littleByte.loader.loadBytecode(filePath: string): vm.Script; 89 | 90 | littleByte.loader.execByteCode(filePath: string): any; 91 | 92 | 93 | type WalkAction = 'ignore' | 'compile' | 'copy'; 94 | 95 | type FileInfo = { 96 | path: string; 97 | relativePath: string; 98 | name: string; 99 | ext: string; 100 | isScript: boolean; 101 | }; 102 | 103 | interface WalkOptions { 104 | silent?: boolean; 105 | inputDir: string; 106 | outputDir: string; 107 | onFile: (fileinfo: FileInfo, defaultAction: WalkAction) => WalkAction; 108 | } 109 | 110 | littleByte.walker.start(options: WalkOptions): Promise; 111 | 112 | ``` 113 | 114 | ## Related Articles 115 | 116 | [Principles of protecting Node.js source code through bytecode](https://translate.google.com/website?sl=auto&tl=en&hl&u=https://zhuanlan.zhihu.com/p/359235114) 117 | 118 | [Source code and Bytecode performance test](https://github-com.translate.goog/Nihiue/little-byte-demo/blob/main/benchmark.md?_x_tr_sl=auto&_x_tr_tl=en) 119 | 120 | [Node.js bytecode source related issues completed](https://translate.google.com/website?sl=auto&tl=en&hl&u=https://zhuanlan.zhihu.com/p/419591875) 121 | 122 | Powered By Google Translate 123 | -------------------------------------------------------------------------------- /README.zhCN.md: -------------------------------------------------------------------------------- 1 | # little-byte 2 | 3 | 将 Node.js 应用代码编译为字节码 4 | 5 | [英文文档 | English Docs](https://github.com/Nihiue/little-byte/blob/main/README.md) 6 | 7 | ![typescript](https://img.shields.io/npm/types/scrub-js.svg) 8 | [![npm version](https://badge.fury.io/js/little-byte.svg)](https://www.npmjs.com/package/little-byte) 9 | [![Test Suite](https://github.com/Nihiue/little-byte/actions/workflows/test.yaml/badge.svg)](https://github.com/Nihiue/little-byte/actions/workflows/test.yaml) 10 | 11 | ## 安装 12 | 13 | ```bash 14 | $ npm install --save-dev little-byte 15 | ``` 16 | 17 | ## 编译应用 18 | 19 | ### 准备编译脚本 20 | 21 | 创建 build/index.js 22 | 23 | ```javascript 24 | const { walker } = require('little-byte').default; 25 | const path = require('path'); 26 | 27 | walker.start({ 28 | inputDir: path.join(__dirname, '../src'), 29 | outputDir: path.join(__dirname, '../dist'), 30 | onFile(fileInfo, defaultAction) { 31 | if (fileInfo.relativePath.startsWith('foobar/')) { 32 | return 'ignore'; 33 | } 34 | 35 | if (fileInfo.ext === '.jpg') { 36 | return 'ignore'; 37 | } 38 | 39 | if (fileInfo.name === 'my-dog.txt') { 40 | return 'ignore'; 41 | } 42 | 43 | if (fileInfo.isScript) { 44 | return 'compile'; 45 | } else { 46 | // copy none-js files to dist folder 47 | return 'copy'; 48 | } 49 | } 50 | }); 51 | ``` 52 | 53 | ### 编译 54 | 55 | ```bash 56 | $ node build/index.js 57 | ``` 58 | 59 | ## 运行编译后的应用 60 | 61 | 创建 app-entry.js 62 | 63 | ```javascript 64 | 65 | require('little-byte'); 66 | 67 | // now you can require *.bytecode 68 | require('./dist/index'); 69 | 70 | ``` 71 | 72 | ## 限制 73 | 74 | ### 编译和运行字节码需要使用相同版本的 Node.js 75 | 76 | 不同版本的 Node.js 可能使用不同的字节码格式 77 | 78 | ### 字节码不能保护代码中的常量值 79 | 80 | 可以使用16进制编辑器从字节码中恢复出字符串等常量的值 81 | 82 | 83 | ## API 84 | 85 | ``` typescript 86 | 87 | littleByte.compiler.compileFile(filePath: string, outputDir?: string): Promise 88 | 89 | littleByte.loader.loadBytecode(filePath: string): vm.Script; 90 | 91 | littleByte.loader.execByteCode(filePath: string): any; 92 | 93 | 94 | type WalkAction = 'ignore' | 'compile' | 'copy'; 95 | 96 | type FileInfo = { 97 | path: string; 98 | relativePath: string; 99 | name: string; 100 | ext: string; 101 | isScript: boolean; 102 | }; 103 | 104 | interface WalkOptions { 105 | silent?: boolean; 106 | inputDir: string; 107 | outputDir: string; 108 | onFile: (fileinfo: FileInfo, defaultAction: WalkAction) => WalkAction; 109 | } 110 | 111 | littleByte.walker.start(options: WalkOptions): Promise; 112 | 113 | ``` 114 | 115 | ## 原理 & 设计思路 116 | 117 | [通过字节码保护Node.js源码之原理篇](https://zhuanlan.zhihu.com/p/359235114) 118 | 119 | [源代码与 Bytecode 性能测试](./benchmark.md) 120 | 121 | [Node.js 字节码源码相关问题补完](https://zhuanlan.zhihu.com/p/419591875) 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "little-byte", 3 | "version": "0.1.4", 4 | "description": "Node.js bytecode compiler", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/**", 8 | "README.zhCN.md" 9 | ], 10 | "scripts": { 11 | "pub": "npm publish --registry=https://registry.npmjs.org", 12 | "build": "tsc -d", 13 | "test": "c8 mocha" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Nihiue/little-byte.git" 18 | }, 19 | "keywords": [ 20 | "node.js", 21 | "bytecode" 22 | ], 23 | "author": "Wanglei (nihiue@gmail.com)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/Nihiue/little-byte/issues" 27 | }, 28 | "homepage": "https://github.com/Nihiue/little-byte#readme", 29 | "dependencies": { 30 | "@babel/parser": "^7.16.12" 31 | }, 32 | "devDependencies": { 33 | "@babel/types": "^7.16.8", 34 | "@types/node": "^17.0.10", 35 | "c8": "^7.12.0", 36 | "mocha": "^10.0.0", 37 | "typescript": "^4.5.5" 38 | }, 39 | "engines": { 40 | "node": ">=12 <=16" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | '@babel/parser': ^7.16.12 5 | '@babel/types': ^7.16.8 6 | '@types/node': ^17.0.10 7 | c8: ^7.12.0 8 | mocha: ^10.0.0 9 | typescript: ^4.5.5 10 | 11 | dependencies: 12 | '@babel/parser': 7.18.13 13 | 14 | devDependencies: 15 | '@babel/types': 7.18.13 16 | '@types/node': 17.0.45 17 | c8: 7.12.0 18 | mocha: 10.0.0 19 | typescript: 4.7.4 20 | 21 | packages: 22 | 23 | /@babel/helper-string-parser/7.18.10: 24 | resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} 25 | engines: {node: '>=6.9.0'} 26 | 27 | /@babel/helper-validator-identifier/7.18.6: 28 | resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} 29 | engines: {node: '>=6.9.0'} 30 | 31 | /@babel/parser/7.18.13: 32 | resolution: {integrity: sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==} 33 | engines: {node: '>=6.0.0'} 34 | hasBin: true 35 | dependencies: 36 | '@babel/types': 7.18.13 37 | dev: false 38 | 39 | /@babel/types/7.18.13: 40 | resolution: {integrity: sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==} 41 | engines: {node: '>=6.9.0'} 42 | dependencies: 43 | '@babel/helper-string-parser': 7.18.10 44 | '@babel/helper-validator-identifier': 7.18.6 45 | to-fast-properties: 2.0.0 46 | 47 | /@bcoe/v8-coverage/0.2.3: 48 | resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} 49 | dev: true 50 | 51 | /@istanbuljs/schema/0.1.3: 52 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 53 | engines: {node: '>=8'} 54 | dev: true 55 | 56 | /@jridgewell/resolve-uri/3.1.0: 57 | resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} 58 | engines: {node: '>=6.0.0'} 59 | dev: true 60 | 61 | /@jridgewell/sourcemap-codec/1.4.14: 62 | resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} 63 | dev: true 64 | 65 | /@jridgewell/trace-mapping/0.3.15: 66 | resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==} 67 | dependencies: 68 | '@jridgewell/resolve-uri': 3.1.0 69 | '@jridgewell/sourcemap-codec': 1.4.14 70 | dev: true 71 | 72 | /@types/istanbul-lib-coverage/2.0.4: 73 | resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} 74 | dev: true 75 | 76 | /@types/node/17.0.45: 77 | resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} 78 | dev: true 79 | 80 | /@ungap/promise-all-settled/1.1.2: 81 | resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} 82 | dev: true 83 | 84 | /ansi-colors/4.1.1: 85 | resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} 86 | engines: {node: '>=6'} 87 | dev: true 88 | 89 | /ansi-regex/5.0.1: 90 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 91 | engines: {node: '>=8'} 92 | dev: true 93 | 94 | /ansi-styles/4.3.0: 95 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 96 | engines: {node: '>=8'} 97 | dependencies: 98 | color-convert: 2.0.1 99 | dev: true 100 | 101 | /anymatch/3.1.2: 102 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} 103 | engines: {node: '>= 8'} 104 | dependencies: 105 | normalize-path: 3.0.0 106 | picomatch: 2.3.1 107 | dev: true 108 | 109 | /argparse/2.0.1: 110 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 111 | dev: true 112 | 113 | /balanced-match/1.0.2: 114 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 115 | dev: true 116 | 117 | /binary-extensions/2.2.0: 118 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 119 | engines: {node: '>=8'} 120 | dev: true 121 | 122 | /brace-expansion/1.1.11: 123 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 124 | dependencies: 125 | balanced-match: 1.0.2 126 | concat-map: 0.0.1 127 | dev: true 128 | 129 | /brace-expansion/2.0.1: 130 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 131 | dependencies: 132 | balanced-match: 1.0.2 133 | dev: true 134 | 135 | /braces/3.0.2: 136 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 137 | engines: {node: '>=8'} 138 | dependencies: 139 | fill-range: 7.0.1 140 | dev: true 141 | 142 | /browser-stdout/1.3.1: 143 | resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} 144 | dev: true 145 | 146 | /c8/7.12.0: 147 | resolution: {integrity: sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==} 148 | engines: {node: '>=10.12.0'} 149 | hasBin: true 150 | dependencies: 151 | '@bcoe/v8-coverage': 0.2.3 152 | '@istanbuljs/schema': 0.1.3 153 | find-up: 5.0.0 154 | foreground-child: 2.0.0 155 | istanbul-lib-coverage: 3.2.0 156 | istanbul-lib-report: 3.0.0 157 | istanbul-reports: 3.1.5 158 | rimraf: 3.0.2 159 | test-exclude: 6.0.0 160 | v8-to-istanbul: 9.0.1 161 | yargs: 16.2.0 162 | yargs-parser: 20.2.9 163 | dev: true 164 | 165 | /camelcase/6.3.0: 166 | resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} 167 | engines: {node: '>=10'} 168 | dev: true 169 | 170 | /chalk/4.1.2: 171 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 172 | engines: {node: '>=10'} 173 | dependencies: 174 | ansi-styles: 4.3.0 175 | supports-color: 7.2.0 176 | dev: true 177 | 178 | /chokidar/3.5.3: 179 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 180 | engines: {node: '>= 8.10.0'} 181 | dependencies: 182 | anymatch: 3.1.2 183 | braces: 3.0.2 184 | glob-parent: 5.1.2 185 | is-binary-path: 2.1.0 186 | is-glob: 4.0.3 187 | normalize-path: 3.0.0 188 | readdirp: 3.6.0 189 | optionalDependencies: 190 | fsevents: 2.3.2 191 | dev: true 192 | 193 | /cliui/7.0.4: 194 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} 195 | dependencies: 196 | string-width: 4.2.3 197 | strip-ansi: 6.0.1 198 | wrap-ansi: 7.0.0 199 | dev: true 200 | 201 | /color-convert/2.0.1: 202 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 203 | engines: {node: '>=7.0.0'} 204 | dependencies: 205 | color-name: 1.1.4 206 | dev: true 207 | 208 | /color-name/1.1.4: 209 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 210 | dev: true 211 | 212 | /concat-map/0.0.1: 213 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 214 | dev: true 215 | 216 | /convert-source-map/1.8.0: 217 | resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} 218 | dependencies: 219 | safe-buffer: 5.1.2 220 | dev: true 221 | 222 | /cross-spawn/7.0.3: 223 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 224 | engines: {node: '>= 8'} 225 | dependencies: 226 | path-key: 3.1.1 227 | shebang-command: 2.0.0 228 | which: 2.0.2 229 | dev: true 230 | 231 | /debug/4.3.4_supports-color@8.1.1: 232 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 233 | engines: {node: '>=6.0'} 234 | peerDependencies: 235 | supports-color: '*' 236 | peerDependenciesMeta: 237 | supports-color: 238 | optional: true 239 | dependencies: 240 | ms: 2.1.2 241 | supports-color: 8.1.1 242 | dev: true 243 | 244 | /decamelize/4.0.0: 245 | resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} 246 | engines: {node: '>=10'} 247 | dev: true 248 | 249 | /diff/5.0.0: 250 | resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} 251 | engines: {node: '>=0.3.1'} 252 | dev: true 253 | 254 | /emoji-regex/8.0.0: 255 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 256 | dev: true 257 | 258 | /escalade/3.1.1: 259 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 260 | engines: {node: '>=6'} 261 | dev: true 262 | 263 | /escape-string-regexp/4.0.0: 264 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 265 | engines: {node: '>=10'} 266 | dev: true 267 | 268 | /fill-range/7.0.1: 269 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 270 | engines: {node: '>=8'} 271 | dependencies: 272 | to-regex-range: 5.0.1 273 | dev: true 274 | 275 | /find-up/5.0.0: 276 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 277 | engines: {node: '>=10'} 278 | dependencies: 279 | locate-path: 6.0.0 280 | path-exists: 4.0.0 281 | dev: true 282 | 283 | /flat/5.0.2: 284 | resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} 285 | hasBin: true 286 | dev: true 287 | 288 | /foreground-child/2.0.0: 289 | resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} 290 | engines: {node: '>=8.0.0'} 291 | dependencies: 292 | cross-spawn: 7.0.3 293 | signal-exit: 3.0.7 294 | dev: true 295 | 296 | /fs.realpath/1.0.0: 297 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 298 | dev: true 299 | 300 | /fsevents/2.3.2: 301 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 302 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 303 | os: [darwin] 304 | requiresBuild: true 305 | dev: true 306 | optional: true 307 | 308 | /get-caller-file/2.0.5: 309 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 310 | engines: {node: 6.* || 8.* || >= 10.*} 311 | dev: true 312 | 313 | /glob-parent/5.1.2: 314 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 315 | engines: {node: '>= 6'} 316 | dependencies: 317 | is-glob: 4.0.3 318 | dev: true 319 | 320 | /glob/7.2.0: 321 | resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} 322 | dependencies: 323 | fs.realpath: 1.0.0 324 | inflight: 1.0.6 325 | inherits: 2.0.4 326 | minimatch: 3.1.2 327 | once: 1.4.0 328 | path-is-absolute: 1.0.1 329 | dev: true 330 | 331 | /glob/7.2.3: 332 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 333 | dependencies: 334 | fs.realpath: 1.0.0 335 | inflight: 1.0.6 336 | inherits: 2.0.4 337 | minimatch: 3.1.2 338 | once: 1.4.0 339 | path-is-absolute: 1.0.1 340 | dev: true 341 | 342 | /has-flag/4.0.0: 343 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 344 | engines: {node: '>=8'} 345 | dev: true 346 | 347 | /he/1.2.0: 348 | resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 349 | hasBin: true 350 | dev: true 351 | 352 | /html-escaper/2.0.2: 353 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 354 | dev: true 355 | 356 | /inflight/1.0.6: 357 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 358 | dependencies: 359 | once: 1.4.0 360 | wrappy: 1.0.2 361 | dev: true 362 | 363 | /inherits/2.0.4: 364 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 365 | dev: true 366 | 367 | /is-binary-path/2.1.0: 368 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 369 | engines: {node: '>=8'} 370 | dependencies: 371 | binary-extensions: 2.2.0 372 | dev: true 373 | 374 | /is-extglob/2.1.1: 375 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 376 | engines: {node: '>=0.10.0'} 377 | dev: true 378 | 379 | /is-fullwidth-code-point/3.0.0: 380 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 381 | engines: {node: '>=8'} 382 | dev: true 383 | 384 | /is-glob/4.0.3: 385 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 386 | engines: {node: '>=0.10.0'} 387 | dependencies: 388 | is-extglob: 2.1.1 389 | dev: true 390 | 391 | /is-number/7.0.0: 392 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 393 | engines: {node: '>=0.12.0'} 394 | dev: true 395 | 396 | /is-plain-obj/2.1.0: 397 | resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} 398 | engines: {node: '>=8'} 399 | dev: true 400 | 401 | /is-unicode-supported/0.1.0: 402 | resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} 403 | engines: {node: '>=10'} 404 | dev: true 405 | 406 | /isexe/2.0.0: 407 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 408 | dev: true 409 | 410 | /istanbul-lib-coverage/3.2.0: 411 | resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} 412 | engines: {node: '>=8'} 413 | dev: true 414 | 415 | /istanbul-lib-report/3.0.0: 416 | resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} 417 | engines: {node: '>=8'} 418 | dependencies: 419 | istanbul-lib-coverage: 3.2.0 420 | make-dir: 3.1.0 421 | supports-color: 7.2.0 422 | dev: true 423 | 424 | /istanbul-reports/3.1.5: 425 | resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} 426 | engines: {node: '>=8'} 427 | dependencies: 428 | html-escaper: 2.0.2 429 | istanbul-lib-report: 3.0.0 430 | dev: true 431 | 432 | /js-yaml/4.1.0: 433 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 434 | hasBin: true 435 | dependencies: 436 | argparse: 2.0.1 437 | dev: true 438 | 439 | /locate-path/6.0.0: 440 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 441 | engines: {node: '>=10'} 442 | dependencies: 443 | p-locate: 5.0.0 444 | dev: true 445 | 446 | /log-symbols/4.1.0: 447 | resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} 448 | engines: {node: '>=10'} 449 | dependencies: 450 | chalk: 4.1.2 451 | is-unicode-supported: 0.1.0 452 | dev: true 453 | 454 | /make-dir/3.1.0: 455 | resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 456 | engines: {node: '>=8'} 457 | dependencies: 458 | semver: 6.3.0 459 | dev: true 460 | 461 | /minimatch/3.1.2: 462 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 463 | dependencies: 464 | brace-expansion: 1.1.11 465 | dev: true 466 | 467 | /minimatch/5.0.1: 468 | resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} 469 | engines: {node: '>=10'} 470 | dependencies: 471 | brace-expansion: 2.0.1 472 | dev: true 473 | 474 | /mocha/10.0.0: 475 | resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==} 476 | engines: {node: '>= 14.0.0'} 477 | hasBin: true 478 | dependencies: 479 | '@ungap/promise-all-settled': 1.1.2 480 | ansi-colors: 4.1.1 481 | browser-stdout: 1.3.1 482 | chokidar: 3.5.3 483 | debug: 4.3.4_supports-color@8.1.1 484 | diff: 5.0.0 485 | escape-string-regexp: 4.0.0 486 | find-up: 5.0.0 487 | glob: 7.2.0 488 | he: 1.2.0 489 | js-yaml: 4.1.0 490 | log-symbols: 4.1.0 491 | minimatch: 5.0.1 492 | ms: 2.1.3 493 | nanoid: 3.3.3 494 | serialize-javascript: 6.0.0 495 | strip-json-comments: 3.1.1 496 | supports-color: 8.1.1 497 | workerpool: 6.2.1 498 | yargs: 16.2.0 499 | yargs-parser: 20.2.4 500 | yargs-unparser: 2.0.0 501 | dev: true 502 | 503 | /ms/2.1.2: 504 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 505 | dev: true 506 | 507 | /ms/2.1.3: 508 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 509 | dev: true 510 | 511 | /nanoid/3.3.3: 512 | resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} 513 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 514 | hasBin: true 515 | dev: true 516 | 517 | /normalize-path/3.0.0: 518 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 519 | engines: {node: '>=0.10.0'} 520 | dev: true 521 | 522 | /once/1.4.0: 523 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 524 | dependencies: 525 | wrappy: 1.0.2 526 | dev: true 527 | 528 | /p-limit/3.1.0: 529 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 530 | engines: {node: '>=10'} 531 | dependencies: 532 | yocto-queue: 0.1.0 533 | dev: true 534 | 535 | /p-locate/5.0.0: 536 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 537 | engines: {node: '>=10'} 538 | dependencies: 539 | p-limit: 3.1.0 540 | dev: true 541 | 542 | /path-exists/4.0.0: 543 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 544 | engines: {node: '>=8'} 545 | dev: true 546 | 547 | /path-is-absolute/1.0.1: 548 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 549 | engines: {node: '>=0.10.0'} 550 | dev: true 551 | 552 | /path-key/3.1.1: 553 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 554 | engines: {node: '>=8'} 555 | dev: true 556 | 557 | /picomatch/2.3.1: 558 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 559 | engines: {node: '>=8.6'} 560 | dev: true 561 | 562 | /randombytes/2.1.0: 563 | resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} 564 | dependencies: 565 | safe-buffer: 5.2.1 566 | dev: true 567 | 568 | /readdirp/3.6.0: 569 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 570 | engines: {node: '>=8.10.0'} 571 | dependencies: 572 | picomatch: 2.3.1 573 | dev: true 574 | 575 | /require-directory/2.1.1: 576 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 577 | engines: {node: '>=0.10.0'} 578 | dev: true 579 | 580 | /rimraf/3.0.2: 581 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 582 | hasBin: true 583 | dependencies: 584 | glob: 7.2.3 585 | dev: true 586 | 587 | /safe-buffer/5.1.2: 588 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 589 | dev: true 590 | 591 | /safe-buffer/5.2.1: 592 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 593 | dev: true 594 | 595 | /semver/6.3.0: 596 | resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} 597 | hasBin: true 598 | dev: true 599 | 600 | /serialize-javascript/6.0.0: 601 | resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} 602 | dependencies: 603 | randombytes: 2.1.0 604 | dev: true 605 | 606 | /shebang-command/2.0.0: 607 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 608 | engines: {node: '>=8'} 609 | dependencies: 610 | shebang-regex: 3.0.0 611 | dev: true 612 | 613 | /shebang-regex/3.0.0: 614 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 615 | engines: {node: '>=8'} 616 | dev: true 617 | 618 | /signal-exit/3.0.7: 619 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 620 | dev: true 621 | 622 | /string-width/4.2.3: 623 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 624 | engines: {node: '>=8'} 625 | dependencies: 626 | emoji-regex: 8.0.0 627 | is-fullwidth-code-point: 3.0.0 628 | strip-ansi: 6.0.1 629 | dev: true 630 | 631 | /strip-ansi/6.0.1: 632 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 633 | engines: {node: '>=8'} 634 | dependencies: 635 | ansi-regex: 5.0.1 636 | dev: true 637 | 638 | /strip-json-comments/3.1.1: 639 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 640 | engines: {node: '>=8'} 641 | dev: true 642 | 643 | /supports-color/7.2.0: 644 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 645 | engines: {node: '>=8'} 646 | dependencies: 647 | has-flag: 4.0.0 648 | dev: true 649 | 650 | /supports-color/8.1.1: 651 | resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 652 | engines: {node: '>=10'} 653 | dependencies: 654 | has-flag: 4.0.0 655 | dev: true 656 | 657 | /test-exclude/6.0.0: 658 | resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} 659 | engines: {node: '>=8'} 660 | dependencies: 661 | '@istanbuljs/schema': 0.1.3 662 | glob: 7.2.3 663 | minimatch: 3.1.2 664 | dev: true 665 | 666 | /to-fast-properties/2.0.0: 667 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 668 | engines: {node: '>=4'} 669 | 670 | /to-regex-range/5.0.1: 671 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 672 | engines: {node: '>=8.0'} 673 | dependencies: 674 | is-number: 7.0.0 675 | dev: true 676 | 677 | /typescript/4.7.4: 678 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} 679 | engines: {node: '>=4.2.0'} 680 | hasBin: true 681 | dev: true 682 | 683 | /v8-to-istanbul/9.0.1: 684 | resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} 685 | engines: {node: '>=10.12.0'} 686 | dependencies: 687 | '@jridgewell/trace-mapping': 0.3.15 688 | '@types/istanbul-lib-coverage': 2.0.4 689 | convert-source-map: 1.8.0 690 | dev: true 691 | 692 | /which/2.0.2: 693 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 694 | engines: {node: '>= 8'} 695 | hasBin: true 696 | dependencies: 697 | isexe: 2.0.0 698 | dev: true 699 | 700 | /workerpool/6.2.1: 701 | resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} 702 | dev: true 703 | 704 | /wrap-ansi/7.0.0: 705 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 706 | engines: {node: '>=10'} 707 | dependencies: 708 | ansi-styles: 4.3.0 709 | string-width: 4.2.3 710 | strip-ansi: 6.0.1 711 | dev: true 712 | 713 | /wrappy/1.0.2: 714 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 715 | dev: true 716 | 717 | /y18n/5.0.8: 718 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 719 | engines: {node: '>=10'} 720 | dev: true 721 | 722 | /yargs-parser/20.2.4: 723 | resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} 724 | engines: {node: '>=10'} 725 | dev: true 726 | 727 | /yargs-parser/20.2.9: 728 | resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} 729 | engines: {node: '>=10'} 730 | dev: true 731 | 732 | /yargs-unparser/2.0.0: 733 | resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} 734 | engines: {node: '>=10'} 735 | dependencies: 736 | camelcase: 6.3.0 737 | decamelize: 4.0.0 738 | flat: 5.0.2 739 | is-plain-obj: 2.1.0 740 | dev: true 741 | 742 | /yargs/16.2.0: 743 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} 744 | engines: {node: '>=10'} 745 | dependencies: 746 | cliui: 7.0.4 747 | escalade: 3.1.1 748 | get-caller-file: 2.0.5 749 | require-directory: 2.1.1 750 | string-width: 4.2.3 751 | y18n: 5.0.8 752 | yargs-parser: 20.2.4 753 | dev: true 754 | 755 | /yocto-queue/0.1.0: 756 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 757 | engines: {node: '>=10'} 758 | dev: true 759 | -------------------------------------------------------------------------------- /src/compiler/bytesource.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '@babel/parser'; 2 | import type { Node, ArrowFunctionExpression, FunctionDeclaration, FunctionExpression, ClassDeclaration, ClassMethod, Statement } from '@babel/types'; 3 | 4 | type FuncNode = ArrowFunctionExpression | FunctionDeclaration | FunctionExpression | ClassDeclaration | ClassMethod; 5 | type SourceToken = [number, string]; 6 | 7 | const isNode = (t: any): t is Node => (t && t.constructor.name === 'Node'); 8 | const isFunction = (node: Node): node is FuncNode => { 9 | return [ 10 | 'ArrowFunctionExpression', 11 | 'FunctionDeclaration', 12 | 'FunctionExpression', 13 | 'ClassDeclaration', 14 | 'ClassMethod', 15 | ].includes(node.type); 16 | } 17 | 18 | function walkNode(roots: Statement[], codeStr: string) { 19 | const ret: [number, string][] = []; 20 | 21 | const todo: Node[] = roots.filter(item => isNode(item)); 22 | let node: Node; 23 | while (node = todo.shift()) { 24 | if (isFunction(node)) { 25 | if (node.body.type === 'BlockStatement' || node.body.type === 'ClassBody') { 26 | ret.push([node.start, codeStr.slice(node.start, node.body.start + 1)]); 27 | ret.push([node.body.end - 1, codeStr.slice(node.body.end - 1, node.body.end)]); 28 | } else { 29 | ret.push([node.start, codeStr.slice(node.start, node.body.start)]); 30 | } 31 | } 32 | Object.keys(node).forEach((k: keyof (Node)) => { 33 | const item: any = node[k]; 34 | if (item && item.forEach) { 35 | item.forEach((t: any) => (isNode(t) && todo.push(t))); 36 | } else if (isNode(item)) { 37 | todo.push(item); 38 | } 39 | }); 40 | } 41 | return ret; 42 | } 43 | 44 | function padString(len: number) { 45 | return len <= 0 ? '' : ('\u200b'.repeat(len)); 46 | } 47 | 48 | function findLineBreaks(codeStr: string) { 49 | const lineBreaks: SourceToken[] = []; 50 | let lastBr = -1; 51 | while (true) { 52 | lastBr = codeStr.indexOf('\n', lastBr + 1); 53 | if (lastBr === -1) { 54 | return lineBreaks; 55 | } 56 | lineBreaks.push([lastBr, '\n']); 57 | } 58 | } 59 | 60 | function generateSource(tokens: SourceToken[], targetLength: number) { 61 | const ret = []; 62 | let len = 0; 63 | 64 | tokens.forEach(([start, content]) => { 65 | if (len < start) { 66 | ret.push(padString(start - len)); 67 | len = start; 68 | } 69 | ret.push(content); 70 | len += content.length; 71 | }); 72 | if (len < targetLength) { 73 | ret.push(padString(targetLength - len)); 74 | } 75 | return ret.join(''); 76 | } 77 | 78 | export default function getByteSource(codeStr: string) { 79 | const ast = parse(codeStr); 80 | const tokens = walkNode(ast.program.body, codeStr) 81 | .concat(findLineBreaks(codeStr)) 82 | .sort((a, b) => a[0] - b[0]); 83 | return generateSource(tokens, codeStr.length); 84 | }; 85 | -------------------------------------------------------------------------------- /src/compiler/index.ts: -------------------------------------------------------------------------------- 1 | import * as vm from 'vm'; 2 | import * as fs from 'fs'; 3 | import * as _module from 'module'; 4 | import * as v8 from 'v8'; 5 | import * as path from 'path'; 6 | import getByteSource from './bytesource'; 7 | 8 | v8.setFlagsFromString('--no-lazy'); 9 | 10 | export async function compileFile(filePath: string, outputDir = '') { 11 | outputDir = outputDir || path.dirname(filePath); 12 | const prefix = path.join(outputDir, path.basename(filePath).replace(/\.js$/i, '')); 13 | 14 | const code = await fs.promises.readFile(filePath, 'utf-8'); 15 | const wrappedCode = _module.wrap(code); 16 | 17 | const script = new vm.Script(wrappedCode, { 18 | filename: filePath 19 | }); 20 | 21 | const bytecode = script.createCachedData(); 22 | await fs.promises.writeFile(prefix + '.bytecode', bytecode); 23 | 24 | if (code.length < 10 * 1024 * 1024) { 25 | // disable bytesource for really big file 26 | const souceMap = getByteSource(wrappedCode); 27 | await fs.promises.writeFile(prefix + '.bytesource', souceMap, 'utf-8'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | const supportVersions = ['v12', 'v13', 'v14', 'v15', 'v16', 'v17', 'v18']; 2 | if (!supportVersions.some(v => process.version.startsWith(v))) { 3 | throw new Error('bytecode: unsupported node version'); 4 | } 5 | 6 | import * as compiler from './compiler/index'; 7 | import * as loader from './loader/index'; 8 | import * as walker from './walker/index'; 9 | export default { 10 | compiler, 11 | loader, 12 | walker 13 | }; -------------------------------------------------------------------------------- /src/loader/index.ts: -------------------------------------------------------------------------------- 1 | import * as _module from 'module'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import * as v8 from 'v8'; 5 | import * as vm from 'vm'; 6 | 7 | v8.setFlagsFromString('--no-flush-bytecode'); 8 | 9 | import { headerUtils, getReferenceFlagHash, makeRequireFunction } from './utils'; 10 | 11 | type ExtendModule = typeof _module & { 12 | _extensions: { [key: string]: Function }; 13 | } 14 | 15 | export function loadBytecode(filename: string) { 16 | // 这里要求是同步的 17 | const byteBuffer = fs.readFileSync(filename, null); 18 | let bytesource = ''; 19 | 20 | try { 21 | bytesource = fs.readFileSync(filename.replace(/\.bytecode$/i, '.bytesource'), 'utf-8'); 22 | } catch (e) { } 23 | 24 | headerUtils.set(byteBuffer, 'flag_hash', getReferenceFlagHash()); 25 | 26 | const sourceLength = headerUtils.buf2num(headerUtils.get(byteBuffer, 'source_hash')); 27 | const dummySource = bytesource.length === sourceLength ? bytesource : '\u200b'.repeat(sourceLength); 28 | const script = new vm.Script(dummySource, { 29 | filename: filename, 30 | cachedData: byteBuffer 31 | }); 32 | 33 | if (script.cachedDataRejected) { 34 | throw new Error('cannot load bytecode, check node version'); 35 | } 36 | return script; 37 | } 38 | 39 | export function execByteCode(filename: string) { 40 | const script = loadBytecode(filename); 41 | return script.runInThisContext(); 42 | } 43 | 44 | (_module as ExtendModule)._extensions['.bytecode'] = function loadModule(module: NodeJS.Module, filename: string) { 45 | const wrapperFn = execByteCode(filename); 46 | const require = makeRequireFunction(module); 47 | wrapperFn.bind(module.exports)(module.exports, require, module, filename, path.dirname(filename)); 48 | }; 49 | -------------------------------------------------------------------------------- /src/loader/utils.ts: -------------------------------------------------------------------------------- 1 | import * as vm from 'vm'; 2 | 3 | const HeaderOffsetMap = { 4 | 'magic': 0, 5 | 'version_hash': 4, 6 | 'source_hash': 8, 7 | 'flag_hash': 12 8 | }; 9 | 10 | type headerType = keyof (typeof HeaderOffsetMap); 11 | 12 | export const headerUtils = { 13 | set(targetBuffer: Buffer, type: headerType, sourceBuffer: Buffer) { 14 | sourceBuffer.copy(targetBuffer, HeaderOffsetMap[type]); 15 | }, 16 | get(buffer: Buffer, type: headerType) { 17 | const offset = HeaderOffsetMap[type]; 18 | return buffer.slice(offset, offset + 4); 19 | }, 20 | buf2num(buf: Buffer) { 21 | // 注意字节序问题 22 | let ret = 0; 23 | ret |= buf[3] << 24; 24 | ret |= buf[2] << 16; 25 | ret |= buf[1] << 8; 26 | ret |= buf[0]; 27 | return ret; 28 | } 29 | }; 30 | 31 | let _flag_buf: Buffer | undefined; 32 | 33 | export function getReferenceFlagHash() { 34 | if (!_flag_buf) { 35 | const script = new vm.Script(''); 36 | _flag_buf = headerUtils.get(script.createCachedData(), 'flag_hash'); 37 | } 38 | return _flag_buf; 39 | } 40 | 41 | function validateString(value: string, name: string) { 42 | if (typeof value !== 'string') { 43 | throw new Error(`${name} is not string`); 44 | } 45 | } 46 | 47 | export function makeRequireFunction(mod: NodeJS.Module) { 48 | // see node.js lib/internal/modules/cjs/helpers.js 49 | const Module: any = mod.constructor; 50 | 51 | const require = function require(path: string) { 52 | return mod.require(path); 53 | }; 54 | 55 | require.resolve = function resolve(request: string, options: any) { 56 | validateString(request, 'request'); 57 | return Module._resolveFilename(request, mod, false, options); 58 | } as any; 59 | 60 | require.resolve.paths = function paths(request: string) { 61 | validateString(request, 'request'); 62 | return Module._resolveLookupPaths(request, mod); 63 | }; 64 | 65 | require.main = process.mainModule; 66 | require.extensions = Module._extensions; 67 | require.cache = Module._cache; 68 | 69 | return require; 70 | } 71 | -------------------------------------------------------------------------------- /src/walker/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import { compileFile } from '../compiler/index'; 4 | 5 | type WalkAction = 'ignore' | 'compile' | 'copy'; 6 | 7 | type FileInfo = { 8 | path: string; 9 | relativePath: string; 10 | name: string; 11 | ext: string; 12 | isScript: boolean; 13 | }; 14 | 15 | interface WalkOptions { 16 | silent?: boolean; 17 | inputDir: string; 18 | outputDir: string; 19 | onFile: (fileinfo: FileInfo, defaultAction: WalkAction) => WalkAction; 20 | }; 21 | 22 | async function ensureDir(dirPath: string) { 23 | try { 24 | await fs.promises.stat(dirPath); 25 | } catch (e) { 26 | await fs.promises.mkdir(dirPath, { 27 | recursive: true 28 | }); 29 | } 30 | } 31 | 32 | async function processFile(fileInfo: FileInfo, action: WalkAction, outputDir: string, silent = false) { 33 | const targetDir = path.dirname(path.join(outputDir, fileInfo.relativePath)); 34 | switch (action) { 35 | case 'copy': 36 | silent || console.log(` - Copy: ${fileInfo.relativePath}`); 37 | await ensureDir(targetDir); 38 | await fs.promises.copyFile(fileInfo.path, path.join(targetDir, fileInfo.name)); 39 | break; 40 | case 'ignore': 41 | break; 42 | case 'compile': 43 | silent || console.log(` - Compile: ${fileInfo.relativePath}`); 44 | await ensureDir(targetDir); 45 | await compileFile(fileInfo.path, targetDir); 46 | break; 47 | default: 48 | throw new Error(`little-byte: invalid action ${action} for file ${fileInfo.relativePath}`); 49 | } 50 | } 51 | 52 | export async function start({ inputDir, outputDir, onFile, silent }: WalkOptions) { 53 | const dirs: string[] = [inputDir]; 54 | 55 | let currentDir; 56 | 57 | while (currentDir = dirs.shift()) { 58 | const files = await fs.promises.readdir(currentDir, { 59 | withFileTypes: true 60 | }); 61 | for (let i = 0; i < files.length; i += 1) { 62 | const file = files[i]; 63 | const currentPath = path.join(currentDir, file.name); 64 | if (file.isDirectory()) { 65 | dirs.push(currentPath); 66 | continue; 67 | } 68 | const fileExt = path.extname(file.name).toLowerCase(); 69 | const fileInfo: FileInfo = Object.freeze({ 70 | path: currentPath, 71 | relativePath: path.relative(inputDir, currentPath), 72 | name: file.name, 73 | ext: fileExt, 74 | isScript: fileExt === '.js' 75 | }); 76 | 77 | try { 78 | const action = onFile(fileInfo, fileInfo.isScript ? 'compile' : 'ignore'); 79 | 80 | if (!fileInfo.isScript && action === 'compile') { 81 | throw new Error(`little-byte: cannot compile non-js file: ${fileInfo.relativePath}`); 82 | } 83 | 84 | await processFile(fileInfo, action || 'ignore', outputDir, silent); 85 | } catch (e) { 86 | console.log(fileInfo, e); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/app/foobar/foo.js: -------------------------------------------------------------------------------- 1 | console.log("I am foo.js"); -------------------------------------------------------------------------------- /test/app/index.js: -------------------------------------------------------------------------------- 1 | const sub = require('./sub/index'); 2 | 3 | sub.arrowFunction(1); 4 | const text = sub.readTextFile(); 5 | if (!text) { 6 | throw new Error('no text'); 7 | } 8 | 9 | const foo = new sub.myClass(999); 10 | foo.inc(); 11 | 12 | if (foo.getValue() !== 1000) { 13 | throw new Error('value is not 1000'); 14 | } 15 | 16 | (async function () { 17 | try { 18 | await sub.asyncArrowException(); 19 | } catch (e) { } 20 | })(); 21 | -------------------------------------------------------------------------------- /test/app/sub/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | function sayHello(more = []) { 5 | console.log(['Hello', 'Byte Code', ...more].join(', ')); 6 | } 7 | 8 | class myClass { 9 | constructor(v = 0) { 10 | this.value = v; 11 | } 12 | inc() { 13 | this.value += 1; 14 | } 15 | getValue() { 16 | return this.value; 17 | } 18 | }; 19 | 20 | module.exports.sayHello = sayHello; 21 | 22 | module.exports.stringExport = "foobar"; 23 | 24 | module.exports.asyncArrowException = async () => { 25 | undefined(); 26 | } 27 | 28 | module.exports.asyncArrowExceptionWithNull = async () => { 29 | const e = null; 30 | return e.Status; 31 | } 32 | module.exports.arrowFunction = (a) => a + 1; 33 | 34 | module.exports.myClass = myClass; 35 | 36 | module.exports.readTextFile = function () { 37 | return fs.readFileSync(path.join(__dirname, 'textfile.txt'), 'utf-8'); 38 | } 39 | 40 | module.exports.stackTrace = function () { 41 | return (new Error()).stack; 42 | } 43 | 44 | module.exports.getModuleInfo = function () { 45 | return { 46 | module, __dirname, __filename 47 | }; 48 | } -------------------------------------------------------------------------------- /test/app/sub/textfile.txt: -------------------------------------------------------------------------------- 1 | the quick brown fox jumps over a lazy dog -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const littleByte = require('../lib/index').default; 5 | 6 | async function fileExists(name) { 7 | try { 8 | await fs.promises.stat(path.join(__dirname, name)); 9 | return true; 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | describe('Compile ByteCode', function () { 16 | it('should compile without error', async function () { 17 | await littleByte.walker.start({ 18 | silent: true, 19 | inputDir: path.join(__dirname, 'app'), 20 | outputDir: path.join(__dirname, 'dist'), 21 | onFile(fileInfo, defaultAction) { 22 | if (fileInfo.relativePath.startsWith('foobar/')) { 23 | return 'ignore'; 24 | } 25 | if (fileInfo.isScript) { 26 | return 'compile'; 27 | } else { 28 | return 'copy'; 29 | } 30 | } 31 | }); 32 | }); 33 | 34 | it('should generate bytecode', async function () { 35 | assert(await fileExists('dist/index.bytecode')); 36 | assert(await fileExists('dist/sub/index.bytecode')); 37 | }); 38 | 39 | it('should generate bytesouce', async function () { 40 | assert(await fileExists('dist/index.bytesource')); 41 | assert(await fileExists('dist/sub/index.bytesource')); 42 | }); 43 | 44 | it('should copy files', async function () { 45 | assert(await fileExists('dist/sub/textfile.txt')); 46 | }); 47 | 48 | it('should ignore files', async function () { 49 | assert.strictEqual(await fileExists('dist/foobar/foo.js'), false); 50 | }); 51 | }); 52 | 53 | describe('Run ByteCode', function () { 54 | 55 | it('app should run without error', function () { 56 | require('./dist/index'); 57 | }); 58 | 59 | let subModule; 60 | function getSubModule() { 61 | if (!subModule) { 62 | const mod = require('./dist/sub/index'); 63 | const sayHello = mod.sayHello.toString(); 64 | const myClass = mod.myClass.toString(); 65 | subModule = { 66 | mod, 67 | sayHello, 68 | myClass 69 | }; 70 | } 71 | return subModule; 72 | } 73 | 74 | it('function.toString() should includes declaration', function () { 75 | const { mod, sayHello, myClass } = getSubModule(); 76 | 77 | assert(sayHello.includes('function sayHello')); 78 | assert(myClass.includes('class myClass')); 79 | assert(myClass.includes('constructor')); 80 | assert(myClass.includes('inc()')); 81 | }); 82 | 83 | it('function.toString() should not include function body', function () { 84 | const { mod, sayHello, myClass } = getSubModule(); 85 | 86 | assert(!myClass.includes('return this.value')); 87 | assert(!sayHello.includes('console.log')); 88 | }); 89 | 90 | it('read property of undefined in async arrow function should not crash', async function () { 91 | const { mod, sayHello, myClass } = getSubModule(); 92 | 93 | try { 94 | await mod.asyncArrowException(); 95 | } catch (e) { 96 | assert(e.toString().includes('TypeError')); 97 | } 98 | }) 99 | 100 | it('read property of null in async arrow function should not crash', async function () { 101 | const { mod, sayHello, myClass } = getSubModule(); 102 | 103 | try { 104 | await mod.asyncArrowExceptionWithNull(); 105 | } catch (e) { 106 | assert(e.toString().includes('TypeError')); 107 | } 108 | }) 109 | 110 | 111 | it('stack trace should contain correct file path', function () { 112 | const { mod, sayHello, myClass } = getSubModule(); 113 | assert(mod.stackTrace().includes('/sub/index.js:')); 114 | }) 115 | 116 | it('require should pass module info', function () { 117 | const { mod, sayHello, myClass } = getSubModule(); 118 | const { __dirname, __filename, module } = mod.getModuleInfo(); 119 | assert(typeof __dirname === 'string'); 120 | assert(__filename.endsWith('.bytecode')); 121 | assert(typeof module === 'object'); 122 | assert(typeof module.exports === 'object'); 123 | }) 124 | 125 | }) 126 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib", 4 | "module": "commonjs", 5 | "target": "es2019", 6 | "noImplicitAny": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "sourceMap": false 10 | }, 11 | "include": [ 12 | "src/**/*" 13 | ], 14 | "exclude": [ 15 | "node_modules", 16 | "**/*.spec.ts" 17 | ] 18 | } --------------------------------------------------------------------------------