├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── test.yml ├── .gitignore ├── API.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── bench ├── hello_async.bench.js └── hello_object_async.bench.js ├── binding.gyp ├── blacklist.txt ├── cloudformation └── ci.template.js ├── codecov.yml ├── common.gypi ├── docs ├── benchmarking.md ├── debugging-with-vs-code.md ├── extended-tour.md ├── image │ ├── howtodebug1.png │ ├── howtodebug2.png │ └── howtodebug3.png ├── npm-and-package-lock.md └── publishing-binaries.md ├── lib └── index.js ├── mason-versions.ini ├── package-lock.json ├── package.json ├── scripts ├── clang-format.sh ├── clang-tidy.sh ├── coverage.sh ├── create_scheme.sh ├── generate_compile_commands.py ├── library.xcscheme ├── liftoff.sh ├── node.xcscheme ├── publish.sh └── sanitize.sh ├── src ├── cpu_intensive_task.hpp ├── module.cpp ├── module_utils.hpp ├── object_async │ ├── hello_async.cpp │ └── hello_async.hpp ├── object_sync │ ├── hello.cpp │ └── hello.hpp ├── standalone │ ├── hello.cpp │ └── hello.hpp ├── standalone_async │ ├── hello_async.cpp │ └── hello_async.hpp └── standalone_promise │ ├── hello_promise.cpp │ └── hello_promise.hpp └── test ├── hello.test.js ├── hello_async.test.js ├── hello_object.test.js ├── hello_object_async.test.js └── hello_promise.test.js /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # Mapbox.Variant C/C+ style 3 | Language: Cpp 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 0 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: false 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 4 88 | UseTab: Never -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia-default-arguments*,-llvm-header-guard,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '\/src\/' 5 | AnalyzeTemporaryDtors: false 6 | CheckOptions: 7 | - key: google-readability-braces-around-statements.ShortStatementLines 8 | value: '1' 9 | - key: google-readability-function-size.StatementThreshold 10 | value: '800' 11 | - key: google-readability-namespace-comments.ShortNamespaceLines 12 | value: '10' 13 | - key: google-readability-namespace-comments.SpacesBeforeComments 14 | value: '2' 15 | - key: modernize-loop-convert.MaxCopySize 16 | value: '16' 17 | - key: modernize-loop-convert.MinConfidence 18 | value: reasonable 19 | - key: modernize-loop-convert.NamingStyle 20 | value: CamelCase 21 | - key: modernize-pass-by-value.IncludeStyle 22 | value: llvm 23 | - key: modernize-replace-auto-ptr.IncludeStyle 24 | value: llvm 25 | - key: modernize-use-nullptr.NullMacros 26 | value: 'NULL' 27 | ... 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: mapbox/node-cpp-skel 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-22.04 13 | permissions: 14 | id-token: write 15 | contents: read 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | buildtype: ['release', 'debug'] 21 | node-version: ['10', '12', '13'] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Install packages 26 | run: | 27 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 28 | sudo apt update 29 | sudo apt-get install -y libstdc++-5-dev 30 | 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | 35 | - name: Install and setup 36 | run: | 37 | node -v 38 | which node 39 | clang++ -v 40 | which clang++ 41 | make "${BUILDTYPE}" 42 | 43 | - name: Run tests 44 | run: | 45 | npm test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/binding 2 | node_modules 3 | build 4 | mason_packages 5 | .DS_Store 6 | *tgz -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | * [hello][1] 6 | * [Examples][2] 7 | * [helloAsync][3] 8 | * [Parameters][4] 9 | * [Examples][5] 10 | * [helloPromise][6] 11 | * [Parameters][7] 12 | * [Examples][8] 13 | * [HelloObject][9] 14 | * [Examples][10] 15 | * [hello][11] 16 | * [Examples][12] 17 | * [HelloObjectAsync][13] 18 | * [Examples][14] 19 | * [helloAsync][15] 20 | * [Parameters][16] 21 | * [Examples][17] 22 | 23 | ## hello 24 | 25 | This is a synchronous standalone function that logs a string. 26 | 27 | ### Examples 28 | 29 | ```javascript 30 | const { hello } = require('@mapbox/node-cpp-skel'); 31 | const check = hello(); 32 | console.log(check); // => "hello world" 33 | ``` 34 | 35 | Returns **[string][18]** 36 | 37 | ## helloAsync 38 | 39 | This is an asynchronous standalone function that logs a string. 40 | 41 | ### Parameters 42 | 43 | * `args` **[Object][19]** different ways to alter the string 44 | 45 | * `args.louder` **[boolean][20]** adds exclamation points to the string 46 | * `args.buffer` **[boolean][20]** returns value as a node buffer rather than a string 47 | * `callback` **[Function][21]** from whence the hello comes, returns a string 48 | 49 | ### Examples 50 | 51 | ```javascript 52 | const { helloAsync } = require('@mapbox/node-cpp-skel'); 53 | helloAsync({ louder: true }, function(err, result) { 54 | if (err) throw err; 55 | console.log(result); // => "...threads are busy async bees...hello 56 | world!!!!" 57 | }); 58 | ``` 59 | 60 | Returns **[string][18]** 61 | 62 | ## helloPromise 63 | 64 | This is a function that returns a promise. It multiplies a string N times. 65 | 66 | ### Parameters 67 | 68 | * `options` **[Object][19]?** different ways to alter the string 69 | 70 | * `options.phrase` **[string][18]** the string to multiply (optional, default `hello`) 71 | * `options.multiply` **[Number][22]** duplicate the string this number of times (optional, default `1`) 72 | 73 | ### Examples 74 | 75 | ```javascript 76 | const { helloPromise } = require('@mapbox/node-cpp-skel'); 77 | const result = await helloAsync({ phrase: 'Howdy', multiply: 3 }); 78 | console.log(result); // HowdyHowdyHowdy 79 | ``` 80 | 81 | Returns **[Promise][23]** 82 | 83 | ## HelloObject 84 | 85 | Synchronous class, called HelloObject 86 | 87 | ### Examples 88 | 89 | ```javascript 90 | const { HelloObject } = require('@mapbox/node-cpp-skel'); 91 | const Obj = new HelloObject('greg'); 92 | ``` 93 | 94 | ### hello 95 | 96 | Say hello 97 | 98 | #### Examples 99 | 100 | ```javascript 101 | const x = Obj.hello(); 102 | console.log(x); // => '...initialized an object...hello greg' 103 | ``` 104 | 105 | Returns **[String][18]** 106 | 107 | ## HelloObjectAsync 108 | 109 | Asynchronous class, called HelloObjectAsync 110 | 111 | ### Examples 112 | 113 | ```javascript 114 | const { HelloObjectAsync } = require('@mapbox/node-cpp-skel'); 115 | const Obj = new module.HelloObjectAsync('greg'); 116 | ``` 117 | 118 | ### helloAsync 119 | 120 | Say hello while doing expensive work in threads 121 | 122 | #### Parameters 123 | 124 | * `args` **[Object][19]** different ways to alter the string 125 | 126 | * `args.louder` **[boolean][20]** adds exclamation points to the string 127 | * `args.buffer` **[buffer][24]** returns object as a node buffer rather then string 128 | * `callback` **[Function][21]** from whence the hello comes, returns a string 129 | 130 | #### Examples 131 | 132 | ```javascript 133 | const { HelloObjectAsync } = require('@mapbox/node-cpp-skel'); 134 | const Obj = new HelloObjectAsync('greg'); 135 | Obj.helloAsync({ louder: true }, function(err, result) { 136 | if (err) throw err; 137 | console.log(result); // => '...threads are busy async bees...hello greg!!!' 138 | }); 139 | ``` 140 | 141 | Returns **[String][18]** 142 | 143 | [1]: #hello 144 | 145 | [2]: #examples 146 | 147 | [3]: #helloasync 148 | 149 | [4]: #parameters 150 | 151 | [5]: #examples-1 152 | 153 | [6]: #hellopromise 154 | 155 | [7]: #parameters-1 156 | 157 | [8]: #examples-2 158 | 159 | [9]: #helloobject 160 | 161 | [10]: #examples-3 162 | 163 | [11]: #hello-1 164 | 165 | [12]: #examples-4 166 | 167 | [13]: #helloobjectasync 168 | 169 | [14]: #examples-5 170 | 171 | [15]: #helloasync-1 172 | 173 | [16]: #parameters-2 174 | 175 | [17]: #examples-6 176 | 177 | [18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String 178 | 179 | [19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 180 | 181 | [20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean 182 | 183 | [21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function 184 | 185 | [22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number 186 | 187 | [23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise 188 | 189 | [24]: https://nodejs.org/api/buffer.html 190 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2/21/2022 2 | 3 | * Add `helloPromise` function example using [Napi::Promise](https://github.com/nodejs/node-addon-api/blob/c54aeef5fd37d3304e61af189672f9f61d403e6f/doc/promises.md) 4 | * Move all JSDoc to lib/index.js since versions >=5 do not support the `--polyglot` flag and give docs a little refresh 5 | 6 | # 1/9/2018 7 | 8 | * Add memory stats option to bench tests 9 | 10 | # 1/4/2018 11 | 12 | * Add doc note about remote vs local coverage using `LCOV` 13 | 14 | # 11/17/2017 15 | 16 | * Add liftoff script, setup docs, and more contributing details per https://github.com/mapbox/node-cpp-skel/pull/87 17 | 18 | # 10/31/2017 19 | 20 | * Add [sanitzer flag doc](https://github.com/mapbox/node-cpp-skel/pull/84) 21 | * Add [sanitizer script](hhttps://github.com/mapbox/node-cpp-skel/pull/85) and enable [leak sanitizer](https://github.com/mapbox/node-cpp-skel/commit/725601e4c7df6cb8477a128f018fb064a9f6f9aa) 22 | * 23 | 24 | # 10/20/2017 25 | 26 | * Add [code of conduct](https://github.com/mapbox/node-cpp-skel/pull/82) 27 | * Add [CC0 license](https://github.com/mapbox/node-cpp-skel/pull/82) 28 | * Point to [cpp glossary](https://github.com/mapbox/node-cpp-skel/pull/83) 29 | 30 | # 10/12/2017 31 | 32 | * Update compiler flags per best practices per https://github.com/mapbox/cpp/issues/37 33 | - https://github.com/mapbox/node-cpp-skel/pull/80 34 | - https://github.com/mapbox/node-cpp-skel/pull/78 35 | - https://github.com/mapbox/node-cpp-skel/pull/77 36 | 37 | # 09/10/2017 38 | 39 | * [Sanitize update](https://github.com/mapbox/node-cpp-skel/pull/74) 40 | 41 | # 08/24/2017 42 | 43 | * Clang tidy updates 44 | - https://github.com/mapbox/node-cpp-skel/pull/68 45 | - https://github.com/mapbox/node-cpp-skel/issues/65 46 | - https://github.com/mapbox/node-cpp-skel/pull/64 47 | - https://github.com/mapbox/node-cpp-skel/pull/66 48 | 49 | # 08/15/2017 50 | 51 | * Add [bench scripts](https://github.com/mapbox/node-cpp-skel/pull/61) for async examples 52 | 53 | # 08/09/2017 54 | 55 | * Add comments about "new" allocation 56 | 57 | # 08/08/2017 58 | 59 | * Add [clang-format](https://github.com/mapbox/node-cpp-skel/pull/56) 60 | 61 | # 08/04/2017 62 | 63 | * Use Nan's safer and high performance `Nan::Utf8String` when accepting string args per https://github.com/mapbox/node-cpp-skel/pull/55 64 | 65 | # 08/3/2017 66 | 67 | * Reorganize [documentation](https://github.com/mapbox/node-cpp-skel/pull/53) 68 | 69 | # 07/21/2017 70 | 71 | * Add [object_async example](https://github.com/mapbox/node-cpp-skel/pull/52) 72 | 73 | # 07/11/2017 74 | 75 | * Add [build docs](https://github.com/mapbox/node-cpp-skel/pull/51) 76 | 77 | * It begins -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | Everyone is invited to participate in Mapbox’s open source projects and public discussions: we want to create a welcoming and friendly environment. Harassment of participants or other unethical and unprofessional behavior will not be tolerated in our spaces. The [Contributor Covenant](http://contributor-covenant.org) applies to all projects under the Mapbox organization and we ask that you please read [the full text](http://contributor-covenant.org/version/1/2/0/). 4 | 5 | You can learn more about our open source philosophy on [mapbox.com](https://www.mapbox.com/about/open/). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for getting involved and contributing to the skel :tada: Below are a few things to setup when submitting a PR. 4 | 5 | ## Code comments 6 | 7 | If adding new code, be sure to include relevant code comments. Code comments are a great way for others to learn from your code. This is especially true within the skeleton, since it is made for learning. 8 | 9 | ## Update Documentation 10 | 11 | Be sure to update any documentation relevant to your change. This includes updating the [CHANGELOG.md](https://github.com/mapbox/node-cpp-skel/blob/master/CHANGELOG.md). 12 | 13 | ## [Code Formatting](/docs/extended-tour.md#clang-tools) 14 | 15 | We use [this script](/scripts/clang-format.sh#L20) to install a consistent version of [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) to format the code base. The format is automatically checked via a Travis CI build as well. Run the following script locally to ensure formatting is ready to merge: 16 | 17 | make format 18 | 19 | We also use [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) as a C++ linter. Run the following command to lint and ensure your code is ready to merge: 20 | 21 | make tidy 22 | 23 | These commands are set from within [the Makefile](./Makefile). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile serves a few purposes: 2 | # 3 | # 1. It provides an interface to iterate quickly while developing the C++ code in src/ 4 | # by typing `make` or `make debug`. To make iteration as fast as possible it calls out 5 | # directly to underlying build tools and skips running steps that appear to have already 6 | # been run (determined by the presence of a known file or directory). What `make` does is 7 | # the same as running `npm install --build-from-source` except that it is faster when 8 | # run a second time because it will skip re-running expensive steps. 9 | # Note: in rare cases (like if you hit `crtl-c` during an install) you might end up with 10 | # build deps only partially installed. In this case you should run `make distclean` to fully 11 | # restore your repo to is starting state and then running `make` again should start from 12 | # scratch, fixing any inconsistencies. 13 | # 14 | # 2. It provides a few commands that call out to external scripts like `make coverage` or 15 | # `make tidy`. These scripts can be called directly but this Makefile provides a more uniform 16 | # interface to call them. 17 | # 18 | # To learn more about the build system see https://github.com/mapbox/node-cpp-skel/blob/master/docs/extended-tour.md#builds 19 | 20 | # Whether to turn compiler warnings into errors 21 | export WERROR ?= true 22 | 23 | # the default target. This line means that 24 | # just typing `make` will call `make release` 25 | default: release 26 | 27 | node_modules/node-addon-api: 28 | npm install --ignore-scripts 29 | 30 | mason_packages/headers: node_modules/node-addon-api 31 | node_modules/.bin/mason-js install 32 | 33 | mason_packages/.link/include: mason_packages/headers 34 | node_modules/.bin/mason-js link 35 | 36 | build-deps: mason_packages/.link/include 37 | 38 | release: build-deps 39 | V=1 ./node_modules/.bin/node-pre-gyp configure build --error_on_warnings=$(WERROR) --loglevel=error 40 | @echo "run 'make clean' for full rebuild" 41 | 42 | debug: mason_packages/.link/include 43 | V=1 ./node_modules/.bin/node-pre-gyp configure build --error_on_warnings=$(WERROR) --loglevel=error --debug 44 | @echo "run 'make clean' for full rebuild" 45 | 46 | coverage: build-deps 47 | ./scripts/coverage.sh 48 | 49 | tidy: build-deps 50 | ./scripts/clang-tidy.sh 51 | 52 | format: build-deps 53 | ./scripts/clang-format.sh 54 | 55 | sanitize: build-deps 56 | ./scripts/sanitize.sh 57 | 58 | clean: 59 | rm -rf lib/binding 60 | rm -rf build 61 | # remove remains from running 'make coverage' 62 | rm -f *.profraw 63 | rm -f *.profdata 64 | @echo "run 'make distclean' to also clear node_modules, mason_packages, and .mason directories" 65 | 66 | distclean: clean 67 | rm -rf node_modules 68 | rm -rf mason_packages 69 | 70 | # variable used in the `xcode` target below 71 | MODULE_NAME := $(shell node -e "console.log(require('./package.json').binary.module_name)") 72 | 73 | xcode: node_modules 74 | ./node_modules/.bin/node-pre-gyp configure -- -f xcode 75 | @# If you need more targets, e.g. to run other npm scripts, duplicate the last line and change NPM_ARGUMENT 76 | SCHEME_NAME="$(MODULE_NAME)" SCHEME_TYPE=library BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node scripts/create_scheme.sh 77 | SCHEME_NAME="npm test" SCHEME_TYPE=node BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node NODE_ARGUMENT="`npm bin tape`/tape test/*.test.js" scripts/create_scheme.sh 78 | 79 | open build/binding.xcodeproj 80 | 81 | testpack: 82 | rm -f ./*tgz 83 | npm pack 84 | tar -ztvf *tgz 85 | 86 | docs: 87 | npm run docs 88 | 89 | test: 90 | npm test 91 | 92 | .PHONY: test docs 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![dancing skel](https://raw.githubusercontent.com/mapbox/cpp/master/assets/node-cpp-skel-readme_blue.png) 2 | [![Build Status](https://travis-ci.com/mapbox/node-cpp-skel.svg?branch=master)](https://travis-ci.com/mapbox/node-cpp-skel) 3 | [![codecov](https://codecov.io/gh/mapbox/node-cpp-skel/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/node-cpp-skel) 4 | 5 | A skeleton for building a C++ addon for Node.js. This is a small, helper repository that generates simple `HelloWorld` Javascript example constructors. The examples have a number of methods to demonstrate different ways to use the Node C+ API for building particular types of functionality (i.e. asynchronous functions). Use this repo as both a template for your own code as well as a learning tool if you're just starting to develop Node/C++ Addons. 6 | 7 | **Why port C++ to Node.js?**. That's a great question! C++ is a high performance language that allows you to execute operations without clogging up the event loop. Node.js is single-threaded, which blocks execution. Even in highly optimized javascript code it may be impossible to improve performance. Passing heavy operations into C++ and subsequently into C++ workers can greatly improve the overall runtime of the code. Porting C++ code to Node.js is also referred to as creating an ["Addon"](https://github.com/mapbox/cpp/blob/master/node-cpp.md). 8 | 9 | More examples of how to port C++ libraries to node can be found at [nodejs.org/api/addons.html](https://nodejs.org/api/addons.html). 10 | 11 | # What's in the box? :package: 12 | 13 | This repository itself can be cloned and edited to your needs. The skeleton prepares a C++ port to Node.js and provides the following for quick development: 14 | 15 | * **Tests**: created with [Tape](https://github.com/substack/tape) in the `test/` directory. Travis CI file is prepared to build and test your project on every push. 16 | * **Documentation**: use this README as a template and customize for your own project. Also, this skeleton uses [documentation.js](http://documentation.js.org/) to generate API documentation from JSDOC comments in the `.cpp` files. Docs are located in `API.md`. 17 | * **[Benchmarking](./docs/benchmarking.md)**: Easily test the performance of your code using the built-in benchmark tests provided in this skeleton. 18 | * **Build system**: [node-pre-gyp](https://github.com/mapbox/node-pre-gyp) generates binaries with the proper system architecture flags 19 | * **[Publishing](./docs/publishing-binaries.md)**: Structured as a node module with a `package.json` that can be deployed to NPM's registry. 20 | * **Learning resources**: Read the detailed inline comments within the example code to learn exactly what is happening behind the scenes. Also, check out the [extended tour](./docs/extended-tour.md) to learn more about Node/C++ Addon development, builds, Xcode, and more details about the configuration of this skeleton. 21 | 22 | # Installation 23 | 24 | Each `make` command is specified in [`Makefile`](./Makefile) 25 | 26 | ```bash 27 | git clone git@github.com:mapbox/node-cpp-skel.git 28 | cd node-cpp-skel 29 | 30 | # Build binaries. This looks to see if there were changes in the C++ code. This does not reinstall deps. 31 | make 32 | 33 | # Run tests 34 | make test 35 | 36 | # Cleans your current builds and removes potential cache 37 | make clean 38 | 39 | # Build binaries in Debug mode (https://github.com/mapbox/cpp/blob/master/glossary.md#debug-build) 40 | make debug 41 | 42 | # Cleans everything, including the things you download from the network in order to compile (ex: npm packages). 43 | # This is useful if you want to nuke everything and start from scratch. 44 | # For example, it's super useful for making sure everything works for Travis, production, someone else's machine, etc 45 | make distclean 46 | 47 | # This skel uses documentation.js to auto-generate API docs. 48 | # If you'd like to generate docs for your code, you'll need to install documentation.js, 49 | # and then add your subdirectory to the docs command in package.json 50 | npm install -g documentation@4.0.0 51 | npm run docs 52 | ``` 53 | 54 | NOTE: we are pinned to `documentation@4.0.0` because 5.x removed C++ support: https://github.com/documentationjs/documentation/blob/master/CHANGELOG.md#500-2017-07-27 55 | 56 | ### Customizing the compiler toolchain 57 | 58 | By default we use `clang++` via [mason](https://github.com/mapbox/mason). The reason we do this is: 59 | 60 | - We want to run the latest and greatest compiler version, to catch the most bugs, provide the best developer experience, and trigger the most helpful warnings 61 | - We use clang-format to format the code and each version of clang-format formats code slightly differently. To avoid friction around this (and ensure all devs format the code the same) we default to using the same version of clang++ via mason. 62 | - We want to support [LTO](https://github.com/mapbox/cpp/blob/master/glossary.md#link-time-optimization) in the builds, which is difficult to do on linux unless you control the toolchain tightly. 63 | 64 | The version of the clang++ binary (and related tools) is controlled by the [`mason-versions.ini`](./mason-versions.ini), and uses `mason-js` uses to install the toolchain. 65 | 66 | All that said, it is still absolutely possible and encouraged to compile your module with another compiler toolchain. In fact we hope that modules based on node-cpp-skel do this! 67 | 68 | To customize the toolchain you can override the defaults by setting these environment variables: CXX, CC, LINK, AR, NM. For example to use g++-6 you could do: 69 | 70 | 71 | ```bash 72 | export CXX="g++-6" 73 | export CC="gcc-6" 74 | export LINK="g++-6" 75 | export AR="ar" 76 | export NM="nm" 77 | make 78 | ``` 79 | 80 | These environment variables will override the compiler toolchain defaults in `make_global_settings` in the [`binding.gyp`](./binding.gyp). 81 | 82 | 83 | ### Warnings as errors 84 | 85 | By default the build errors on compiler warnings. To disable this do: 86 | 87 | ``` 88 | WERROR=false make 89 | ``` 90 | 91 | ### Sanitizers 92 | 93 | You can run the [sanitizers](https://github.com/mapbox/cpp/blob/master/glossary.md#sanitizers), to catch additional bugs, by doing: 94 | 95 | ```shell 96 | make sanitize 97 | ``` 98 | 99 | The sanitizers [are part of the compiler](https://github.com/mapbox/cpp/blob/master/glossary.md#sanitizers) and are also run in a specific job on Travis. 100 | 101 | # Add Custom Code 102 | 103 | Depending on your usecase, there are a variety of ways to start using this skeleton for your project. 104 | 105 | ### Setup new project 106 | Easily use this skeleton as a starting off point for a _new_ custom project: 107 | 108 | ``` 109 | # Clone node-cpp-skel locally 110 | 111 | git clone git@github.com:mapbox/node-cpp-skel.git 112 | cd node-cpp-skel/ 113 | 114 | # Create your new repo on GitHub and have the remote repo url handy for liftoff 115 | # Then run the liftoff script from within your local node-cpp-skel root directory. 116 | # 117 | # This will: 118 | # - prompt you for the new name of your project and the new remote repo url 119 | # - automatically create a new directory for your new project repo 120 | # - create a new branch called "node-cpp-skel-port" within your new repo directory 121 | # - add, commit, and push that branch to your new repo 122 | 123 | ./scripts/liftoff.sh 124 | 125 | ``` 126 | 127 | ### Add your code 128 | Once your project has ported node-cpp-skel, follow these steps to integrate your own code: 129 | 130 | - Create a dir in `./src` to hold your custom code. See the example code within `/src` for reference. 131 | - Add your new method or class to `./src/module.cpp`, and `#include` it at the top 132 | - Add your new file-to-be-compiled to the list of target sources in `./binding.gyp` 133 | - Run `make` and see what surprises await on your new journey :boat: 134 | 135 | ### Adding dependencies 136 | With updated versions of npm, a `package-lock.json` file is created, which is now included in node-cpp-skel. See [`npm-and-package-lock.md`](./docs/npm-and-package-lock.md) for more info on how to interact with this file and how to add new dependencies. 137 | 138 | ### Interactive Debugging 139 | 140 | * [Debugging with VS Code](./docs/debugging-with-vs-code.md) 141 | 142 | # Code coverage 143 | 144 | Code coverage is critical for knowing how well your tests actually test all your code. To see code coverage you can view current results online at [![codecov](https://codecov.io/gh/mapbox/node-cpp-skel/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/node-cpp-skel) or you can build in a customized way and display coverage locally like: 145 | 146 | ``` 147 | make coverage 148 | ``` 149 | 150 | **Note** 151 | 152 | Use [`// LCOV_EXCL_START` and `// LCOV_EXCL_STOP`](https://github.com/mapbox/vtvalidate/blob/master/src/vtvalidate.cpp#L70-L73) to ignore from [codecov](https://codecov.io/gh/mapbox/node-cpp-skel) _remotely_. However, this won't ignore when running coverage _locally_. 153 | 154 | For more details about what `make coverage` is doing under the hood see https://github.com/mapbox/cpp#code-coverage. 155 | 156 | # Contributing and License 157 | 158 | Contributors are welcome! :sparkles: This repo exists as a place to gather C++/Node Addon knowledge that will benefit the larger community. Please contribute your knowledge if you'd like. 159 | 160 | Node-cpp-skel is licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/). Attribution is not required, but definitely welcome! If your project uses this skeleton, please add the node-cpp-skel badge to your readme so that others can learn about the resource. 161 | 162 | [![badge](https://mapbox.s3.amazonaws.com/cpp-assets/node-cpp-skel-badge_blue.svg)](https://github.com/mapbox/node-cpp-skel) 163 | 164 | To include the badge, paste this into your README.md file: 165 | ``` 166 | [![badge](https://mapbox.s3.amazonaws.com/cpp-assets/node-cpp-skel-badge_blue.svg)](https://github.com/mapbox/node-cpp-skel) 167 | ``` 168 | 169 | See [CONTRIBUTING](CONTRIBUTING.md) and [LICENSE](LICENSE.md) for more info. 170 | -------------------------------------------------------------------------------- /bench/hello_async.bench.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | if (!argv.iterations || !argv.concurrency) { 5 | console.error('Please provide desired iterations, concurrency'); 6 | console.error('Example: \n\tnode bench/hello_async.bench.js --iterations 50 --concurrency 10'); 7 | console.error('Optional args: \n\t--mem (reports memory stats)'); 8 | process.exit(1); 9 | } 10 | 11 | // This env var sets the libuv threadpool size. 12 | // This value is locked in once a function interacts with the threadpool 13 | // Therefore we need to set this value either in the shell or at the very 14 | // top of a JS file (like we do here) 15 | process.env.UV_THREADPOOL_SIZE = argv.concurrency; 16 | 17 | var fs = require('fs'); 18 | var path = require('path'); 19 | var bytes = require('bytes'); 20 | var assert = require('assert') 21 | var d3_queue = require('d3-queue'); 22 | var module = require('../lib/index.js'); 23 | var queue = d3_queue.queue(); 24 | 25 | var track_mem = argv.mem ? true : false; 26 | var runs = 0; 27 | var memstats = { 28 | max_rss:0, 29 | max_heap:0, 30 | max_heap_total:0 31 | }; 32 | 33 | function run(cb) { 34 | module.helloAsync({ louder: false }, function(err, result) { 35 | if (err) { 36 | return cb(err); 37 | } 38 | ++runs; 39 | if (track_mem && runs % 1000) { 40 | var mem = process.memoryUsage(); 41 | if (mem.rss > memstats.max_rss) memstats.max_rss = mem.rss; 42 | if (mem.heapTotal > memstats.max_heap_total) memstats.max_heap_total = mem.heapTotal; 43 | if (mem.heapUsed > memstats.max_heap) memstats.max_heap = mem.heapUsed; 44 | } 45 | return cb(); 46 | }); 47 | } 48 | 49 | // Start monitoring time before async work begins within the defer iterator below. 50 | // AsyncWorkers will kick off actual work before the defer iterator is finished, 51 | // and we want to make sure we capture the time of the work of that initial cycle. 52 | var time = +(new Date()); 53 | 54 | for (var i = 0; i < argv.iterations; i++) { 55 | queue.defer(run); 56 | } 57 | 58 | queue.awaitAll(function(error) { 59 | if (error) throw error; 60 | if (runs != argv.iterations) { 61 | throw new Error('Error: did not run as expected'); 62 | } 63 | // check rate 64 | time = +(new Date()) - time; 65 | 66 | if (time == 0) { 67 | console.log('Warning: ms timer not high enough resolution to reliably track rate. Try more iterations'); 68 | } else { 69 | // number of milliseconds per iteration 70 | var rate = runs/(time/1000); 71 | console.log('Benchmark speed: ' + rate.toFixed(0) + ' runs/s (runs:' + runs + ' ms:' + time + ' )'); 72 | 73 | if (track_mem) { 74 | console.log('Benchmark peak mem (max_rss, max_heap, max_heap_total): ', bytes(memstats.max_rss), bytes(memstats.max_heap), bytes(memstats.max_heap_total)); 75 | } else { 76 | console.log('Note: pass --mem to track memory usage'); 77 | } 78 | } 79 | 80 | console.log('Benchmark iterations:',argv.iterations,'concurrency:',argv.concurrency) 81 | 82 | // There may be instances when you want to assert some performance metric 83 | //assert.equal(rate > 1000, true, 'speed not at least 1000/second ( rate was ' + rate + ' runs/s )'); 84 | 85 | }); -------------------------------------------------------------------------------- /bench/hello_object_async.bench.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | if (!argv.iterations || !argv.concurrency) { 5 | console.error('Please provide desired iterations, concurrency'); 6 | console.error('Example: \n\tnode bench/hello_object_async.bench.js --iterations 50 --concurrency 10'); 7 | console.error('Optional args: \n\t--mem (reports memory stats)'); 8 | process.exit(1); 9 | } 10 | 11 | // This env var sets the libuv threadpool size. 12 | // This value is locked in once a function interacts with the threadpool 13 | // Therefore we need to set this value either in the shell or at the very 14 | // top of a JS file (like we do here) 15 | process.env.UV_THREADPOOL_SIZE = argv.concurrency; 16 | 17 | var fs = require('fs'); 18 | var path = require('path'); 19 | var bytes = require('bytes'); 20 | var assert = require('assert') 21 | var d3_queue = require('d3-queue'); 22 | var queue = d3_queue.queue(); 23 | var module = require('../lib/index.js'); 24 | 25 | var H = new module.HelloObjectAsync('park bench'); 26 | var track_mem = argv.mem ? true : false; 27 | var runs = 0; 28 | var memstats = { 29 | max_rss:0, 30 | max_heap:0, 31 | max_heap_total:0 32 | }; 33 | 34 | function run(cb) { 35 | H.helloAsync({ louder: false }, function(err, result) { 36 | if (err) { 37 | return cb(err); 38 | } 39 | ++runs; 40 | if (track_mem && runs % 1000) { 41 | var mem = process.memoryUsage(); 42 | if (mem.rss > memstats.max_rss) memstats.max_rss = mem.rss; 43 | if (mem.heapTotal > memstats.max_heap_total) memstats.max_heap_total = mem.heapTotal; 44 | if (mem.heapUsed > memstats.max_heap) memstats.max_heap = mem.heapUsed; 45 | } 46 | return cb(); 47 | }); 48 | } 49 | 50 | // Start monitoring time before async work begins within the defer iterator below. 51 | // AsyncWorkers will kick off actual work before the defer iterator is finished, 52 | // and we want to make sure we capture the time of the work of that initial cycle. 53 | var time = +(new Date()); 54 | 55 | for (var i = 0; i < argv.iterations; i++) { 56 | queue.defer(run); 57 | } 58 | 59 | queue.awaitAll(function(error) { 60 | if (error) throw error; 61 | if (runs != argv.iterations) { 62 | throw new Error("Error: did not run as expected"); 63 | } 64 | // check rate 65 | time = +(new Date()) - time; 66 | 67 | if (time == 0) { 68 | console.log("Warning: ms timer not high enough resolution to reliably track rate. Try more iterations"); 69 | } else { 70 | // number of milliseconds per iteration 71 | var rate = runs/(time/1000); 72 | console.log('Benchmark speed: ' + rate.toFixed(0) + ' runs/s (runs:' + runs + ' ms:' + time + ' )'); 73 | 74 | if (track_mem) { 75 | console.log('Benchmark peak mem (max_rss, max_heap, max_heap_total): ', bytes(memstats.max_rss), bytes(memstats.max_heap), bytes(memstats.max_heap_total)); 76 | } else { 77 | console.log('Note: pass --mem to track memory usage'); 78 | } 79 | } 80 | 81 | console.log('Benchmark iterations:',argv.iterations,'concurrency:',argv.concurrency); 82 | 83 | // There may be instances when you want to assert some performance metric 84 | //assert.equal(rate > 1000, true, 'speed not at least 1000/second ( rate was ' + rate + ' runs/s )'); 85 | 86 | }); -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | # This file inherits default targets for Node addons, see https://github.com/nodejs/node-gyp/blob/master/addon.gypi 2 | { 3 | # https://github.com/springmeyer/gyp/blob/master/test/make_global_settings/wrapper/wrapper.gyp 4 | 'make_global_settings': [ 5 | ['CXX', '<(module_root_dir)/mason_packages/.link/bin/clang++'], 6 | ['CC', '<(module_root_dir)/mason_packages/.link/bin/clang'], 7 | ['LINK', '<(module_root_dir)/mason_packages/.link/bin/clang++'], 8 | ['AR', '<(module_root_dir)/mason_packages/.link/bin/llvm-ar'], 9 | ['NM', '<(module_root_dir)/mason_packages/.link/bin/llvm-nm'] 10 | ], 11 | 'includes': [ 'common.gypi' ], # brings in a default set of options that are inherited from gyp 12 | 'variables': { # custom variables we use specific to this file 13 | 'error_on_warnings%':'true', # can be overriden by a command line variable because of the % sign using "WERROR" (defined in Makefile) 14 | # Use this variable to silence warnings from mason dependencies and from NAN 15 | # It's a variable to make easy to pass to 16 | # cflags (linux) and xcode (mac) 17 | 'system_includes': [ 18 | "-isystem Open`. 8 | 9 | Then to start debugging there are two ways: 10 | 11 | #### Option 1 12 | 13 | Press `Cmd + Shift + P` to open the command bar, type open `open launch.json`, and then choose `C++`. 14 | 15 | Note: the first time you do this you will need to `install` the C++ Extension and reload the app. 16 | 17 | #### Option 2 18 | 19 | Click the debug button on the right sidebar, click the `⚙` button on the right top, and then choose `C++`. 20 | 21 | ![howtodebug](./image/howtodebug1.png) 22 | 23 | In the `launch.json` you can see some template, like this: 24 | 25 | ```json 26 | { 27 | "version": "0.2.0", 28 | "configurations": [ 29 | { 30 | "name": "(lldb) Launch", 31 | "type": "cppdbg", 32 | "request": "launch", 33 | "program": "enter program name, for example ${workspaceFolder}/a.out", 34 | "args": [], 35 | "stopAtEntry": false, 36 | "cwd": "${workspaceFolder}", 37 | "environment": [], 38 | "externalConsole": true, 39 | "MIMode": "lldb" 40 | } 41 | ] 42 | } 43 | ``` 44 | 45 | `launch.json` defines which program you want to run after you click the run debug button. Let's say we want run `node test/vtshaver.test.js`. In this case we need to change the configurations, put `program` to node's absolute path (you can use `which node` to find the path of current version of node), and change the `args` to `${workspaceFolder}/test/vtshaver.test.js`. 46 | 47 | Additional we want build C++ everytime before we start debug, so we add `preLaunchTask` into the config: 48 | 49 | ``` 50 | "preLaunchTask": "npm: build:dev", 51 | ``` 52 | 53 | Now the config file could look like this: 54 | 55 | 56 | ```json 57 | { 58 | "version": "0.2.0", 59 | "configurations": [ 60 | { 61 | "name": "(lldb) Launch", 62 | "type": "cppdbg", 63 | "request": "launch", 64 | "preLaunchTask": "npm: build:dev", 65 | "program": "/Users/mapbox-mofei/.nvm/versions/node/v8.11.3/bin/node", 66 | "args": ["${workspaceFolder}/test/vtshaver.test.js"], 67 | "stopAtEntry": false, 68 | "cwd": "${workspaceFolder}", 69 | "environment": [], 70 | "externalConsole": true, 71 | "MIMode": "lldb" 72 | } 73 | ] 74 | } 75 | ``` 76 | 77 | Now you can open any C++ files and click the left side of the line number to add a breakpoint, then go to the debug button on the sidebar, click the run button on the top. 78 | 79 | ![howtodebug](./image/howtodebug2.png) 80 | 81 | Now everything is done! Debug is Done! The program will stop at the breakpoint, you can use your mouse to explore the variable. If you want your program to continue past the breakpoint, you can navigate the program using the top control box. 82 | 83 | ![howtodebug](./image/howtodebug3.png) 84 | -------------------------------------------------------------------------------- /docs/extended-tour.md: -------------------------------------------------------------------------------- 1 | # Extended Tour 2 | Welcome to the extended tour of node-cpp-skel. This documentation is especially handy if you're just starting out with building [Node Addon-ons](https://github.com/mapbox/cpp/blob/master/node-cpp.md) or writing C++ :tada: 3 | 4 | ## Table of Contents: 5 | - [Walkthrough of example code](extended-tour.md#walkthrough-example-code) 6 | - [What does "build" mean?](extended-tour.md#what-does-build-mean) 7 | - [Software build components](extended-tour.md#software-build-components) 8 | - [Configuration files](extended-tour.md#configuration-files) 9 | - [Autogenerated files](extended-tour.md#autogenerated-files) 10 | - [Overall flow](extended-tour.md#overall-flow) 11 | - [Clang Tools](extended-tour.md#clang-tools) 12 | - [Xcode](extended-tour.md#xcode) 13 | 14 | # Walkthrough example code 15 | 16 | This skeleton includes a few examples of how you might design your application, including standalone functions and creating Objects/Classes. Both the synchronous and asynchronous versions of each are included to give an iterative example of what the progression from sync to async looks like. Let's run through reasons why you'd design your code in these ways: 17 | 1. [Standalone function](../API.md#hello-1) 18 | 2. [Standalone asynchronous function](../API.md#helloasync-1) 19 | 3. [Object/Class](../API.md#helloobject) 20 | 4. [Asynchronous Object/Class](../API.md#helloobjectasync) 21 | 22 | 23 | ### When would you use a standalone function? 24 | A standalone function is a function that exists at the top level of the module scope rather than as a member of an object that is instantiated. So if your module is `wonderful`, a standalone function would be called like `wonderful.callme()`. 25 | 26 | A standalone function makes sense when the only data needed by the function can be easily passed as arguments. When it is not easy or clean to pass data as arguments then you should consider encapsulation, for example, exposing a function as a member of an object. One of the benefits of creating a standalone function is that it can help [separate concerns](https://en.wikipedia.org/wiki/Separation_of_concerns), which is mainly a stylistic design decision. 27 | 28 | Example: 29 | - [vtinfo](https://github.com/mapbox/vtinfo/blob/master/src/vtinfo.cpp): A synchronous standalone function that is simply getting info about a single vector tile buffer. 30 | 31 | 32 | ### When would you use an object/class? 33 | Create an object/class when you need to do some kind of data preprocessing before going into the thread pool. It's best to write your code so that the preprocessing happens _once_ as a separate operation, then continues through to the thread pool after the preprocessing is finished and the object is ready. Examples: 34 | - [node-mapnik](https://github.com/mapnik/node-mapnik/blob/fe80ce5d79c0e90cfbb5a2b992bf0ae2b8f88198/src/mapnik_map.hpp#L20): we create a Map object once, then use the object multiple times for rendering each vector tile. 35 | - [node-addon-examples](https://github.com/nodejs/node-addon-examples/tree/master/6_object_wrap/node-addon-api) for another example of what an object-focused Addon looks like. 36 | 37 | Objects/Classes are useful when you start doing more complex operations and need to consider performance more heavily, when performance constraints start to matter. Use classes to compute the value of something once, rather than every time you call a function. 38 | 39 | One step further is using an asynchronous object or class, which enables you to pass data into the threadpool. The aim is to process data in the threadpool in the most efficient way possible. [Efficiency](https://github.com/mapbox/cpp/blob/master/glossary.md#efficiency) and high performance are the main goals of the `HelloObjectAsync` example in this skel. The `HelloObjectAsync` example demonstrates high load, when _many_ objects in _many_ threads are being allocated. This scenario is where reducing unnecessary allocations really pays off, and is also why this example uses "move semantics" for even better performance. 40 | - **Move semantics**: move semantics avoid data being copied (allocating memory), to limit unnecessary memory allocation, and are most useful when working with big data. 41 | 42 | Other thoughts related to move semantics: 43 | - Always best to use move semantics instead of passing by reference, espeically with objects like [`std::string`](http://shaharmike.com/cpp/std-string/), which can be expensive. 44 | - Relatedly, best to use [`std::unique_ptr`](http://en.cppreference.com/w/cpp/memory/unique_ptr) instead of using `std::shared_ptr` because `std::unique_ptr` is non-copyable. So you're forced to avoid copying, which is a good practice. 45 | 46 | 47 | # Builds 48 | 49 | ### What does "build" mean? 50 | 51 | When you "build" your module, you are compiling and linking your C++ [source code](https://github.com/mapbox/cpp/blob/master/glossary.md#source-code) to produce a binary file that allows Node.js to load and run your module. Once loaded your C++ module can be used as if it were a Javascript module. A large list of tools come together to make this possible. In the below section we'll describe: 52 | 53 | - [Software build components](extended-tour.md##software-build-components) 54 | - Listing of [configuration files](extended-tour.md#configuration-files) for the build 55 | - Listing of [autogenerated files](extended-tour.md#autogenerated-files) created when you run a build 56 | - Overall [build flow](extended-tour.md#build-flow) of what happens when you run a build 57 | 58 | ### Software build components 59 | 60 | The primary components involved in the build 61 | 62 | #### node-pre-gyp 63 | 64 | node-pre-gyp is a javascript command line tool used in this project as a [front end](https://github.com/mapbox/cpp/blob/master/glossary.md#front-end) to [node-gyp](#node-gyp) to either compile your code or install it from binaries. 65 | 66 | It is installed as a dependency in the [package.json](../package.json). 67 | 68 | Learn more about node-pre-gyp [here](https://github.com/mapbox/node-pre-gyp) 69 | 70 | #### node-gyp 71 | 72 | node-gyp is a javascript command line tool used in this project as a [front end](https://github.com/mapbox/cpp/blob/master/glossary.md#front-end) to [gyp](#gyp). 73 | 74 | node-gyp is bundled inside [npm](#npm) and does not need to be installed separately. Although, if installed in [package.json](../package.json), that version will be used by [node-pre-gyp](#node-pre-gyp). 75 | 76 | Learn more about node-gyp [here](https://github.com/nodejs/node-gyp) 77 | 78 | #### gyp 79 | 80 | gyp is a python command line tool used in this project as a [front end](https://github.com/mapbox/cpp/blob/master/glossary.md#front-end) to [make](#make). 81 | 82 | Learn more about gyp [here](https://github.com/mapbox/cpp/blob/master/glossary.md#gyp) 83 | 84 | #### make 85 | 86 | make is a command line tool, written in C, that is installed by default on most unix systems. It is used in this project in two ways: 87 | 88 | - We provide a `Makefile` that acts as a simple entry point for developers wanting to source compile node-pre-gyp 89 | - When [node-gyp](#node-gyp) is run, it generates a set of `Makefile`s automatically which are used to call out to the [compiler](#compiler) and [linker](#linker) to assemble your binary C++ module. 90 | 91 | Learn more about make [here](https://github.com/mapbox/cpp/blob/master/glossary.md#make) 92 | 93 | #### compiler 94 | 95 | The command line program able to compile C++ source code, in this case `clang++`. 96 | 97 | Learn more about what a compiler is [here](https://github.com/mapbox/cpp/blob/master/glossary.md#compiler) 98 | 99 | #### linker 100 | 101 | The command line program able to link C++ source code, in this case also `clang++`, which acts as a [front end](https://github.com/mapbox/cpp/blob/master/glossary.md#front-end) to the [system linker](https://github.com/mapbox/cpp/blob/master/glossary.md#linker) 102 | 103 | ### Configuration files 104 | 105 | Files you will find inside this repo and their purpose. For more info look inside each file for detailed comments. 106 | 107 | - [Makefile](../Makefile) - entry point to building from source. This is invoked when you type `make` in the root directory. By default the `default` target is run which maps to the `release` target. See the comments inside the Makefile for more detail. 108 | - [binding.gyp](../binding.gyp) - JSON configuration file for [node-gyp](#node-gyp). Must be named `binding.gyp` and present in the root directory so that `npm` detects it. Will be passed to [gyp](#gyp) by [node-gyp](#node-gyp). Because [gyp](#gyp) is python and has less strict JSON parsing rules, code comments with `#` are allowed (this would not be the case if parsed with node.js). 109 | - [common.gypi](../common.gypi) - "gypi" stands for gyp include file. This is referenced by the [binding.gyp](../binding.gyp) 110 | - [package.json](../package.json) - configuration file for npm. But it also contains a custom `binary` object that is the configuration for [node-pre-gyp](#node-pre-gyp). 111 | - [lib/index.js](../lib/index.js) - entry point for the javascript module. Referenced in the `main` property in the [package.json](../package.json). This is the file that is run when the module is loaded by `require` from another module. 112 | - [scripts/setup.sh](../scripts/setup.sh) - script used to 1) install [Mason](https://github.com/mapbox/cpp/blob/master/glossary.md#mason) and [clang++](https://github.com/mapbox/cpp/blob/master/glossary.md#clang-1) and 2) create a `local.env` that can be sourced in `bash` in order to set up [Mason](https://github.com/mapbox/cpp/blob/master/glossary.md#mason) and [clang++](https://github.com/mapbox/cpp/blob/master/glossary.md#clang-1) on your PATH. 113 | - [scripts/install_deps.sh](../scripts/install_deps.sh) - script that invokes [Mason](https://github.com/mapbox/cpp/blob/master/glossary.md#mason) to install mason packages 114 | - [scripts/publish.sh](../scripts/publish.sh) - script to publish the C++ binary module with [node-pre-gyp](#node-pre-gyp). Designed to be run on [travisci.org](https://travis-ci.org/) 115 | - [.travis.yml](../.travis.yml) - configuration for this module on [travisci.org](https://travis-ci.org/). Used to test the code and publish binaries for various node versions and compiler options. 116 | 117 | Note: the `binding.gyp` file also inherits from two files that you will not find inside the node-cpp-skel repo. These are gyp include files that come from node core and node-gyp and are invoked when [`node-gyp configure` is called](https://github.com/nodejs/node-gyp/blob/a8ba5288cb25d4edcafdc75d0cd59b474b7225e8/lib/configure.js#L279-L280): 118 | 119 | - https://github.com/nodejs/node/blob/v4.x/common.gypi 120 | - https://github.com/nodejs/node-gyp/blob/master/addon.gypi 121 | 122 | ### Autogenerated files 123 | 124 | Files you will notice are created when you build from source by running `make`: 125 | 126 | - `build/` - a directory created by [node-gyp](#node-gyp) to hold a variety of autogenerated Makefiles, gyp files, and binary outputs. 127 | - `build/Release/` - directory created to hold binary files for a `Release` build. A `Release` build is the default build when you run `make`. For more info on release builds see [this definition](https://github.com/mapbox/cpp/blob/master/glossary.md#release-build) 128 | - `build/Release/module.node` - The C++ binary module, for a `Release build, in the form of a [loadable module](https://github.com/mapbox/cpp/blob/master/glossary.md#loadable-module). This file was ultimately created by the [linker](#linker) and ended up at this path thanks to [node-gyp](#node-gyp). 129 | - `lib/binding/module.node` - the final resting place for the C++ binary module. Copied to this location from either `build/Release/module.node` or `build/Debug/module.node`. This location is configured via the `module_path` variable in the [package.json](../package.json) and is used by [node-pre-gyp](#node-pre-gyp) when assembling a package to publish remotely (to allow users to install via binaries). 130 | - `build/Release/obj.target/module/src/module.o`: the [object file](https://github.com/mapbox/cpp/blob/master/glossary.md#object-file) that corresponds to the [`src/module.cpp`](../src/module.cpp). 131 | - `build/Release/obj.target/module/standalone/hello.o`: the [object file](https://github.com/mapbox/cpp/blob/master/glossary.md#object-file) that corresponds to the [`src/standalone/hello.cpp`](../src/standalone/hello.cpp). 132 | - `build/Debug/` - directory created to hold binary files for a `Debug` build. A `Debug` build is trigged when you run `make debug`. For more info on debug builds see [this definition](https://github.com/mapbox/cpp/blob/master/glossary.md#debug-build) 133 | - `build/Debug/module.node` - The C++ binary module, for a `Debug build, in the form of a [loadable module](https://github.com/mapbox/cpp/blob/master/glossary.md#loadable-module). This file was ultimately created by the [linker](#linker) and ended up at this path thanks to [node-gyp](#node-gyp). 134 | 135 | 136 | 137 | ### Overall flow 138 | 139 | The overall flow in terms of software components is: 140 | 141 | ``` 142 | make -> node-pre-gyp -> node-gyp -> gyp -> make -> compiler/linker 143 | ``` 144 | 145 | The overall flow, including operations, is: 146 | 147 | ``` 148 | Developer (you) runs 'make' 149 | -> make reads Makefile 150 | -> Makefile has custom line that calls out to 'node-pre-gyp configure build' 151 | -> node-pre-gyp passes variables in the package.json along to 'node-gyp rebuild' 152 | -> node-gyp finds the `binding.gyp` and passes it to gyp along with other .gyp includes (addon.gypi and a common.gypi from node core) 153 | -> gyp loads the `binding.gyp` and the `common.gypi` and generates Makefiles inside `build/` directory 154 | -> node-gyp runs make in the `build/` directory 155 | -> make runs all the targets in the makefiles generated by gyp in the `build/` directory 156 | -> these makefiles run the `install_deps.sh` and invoke the compiler and linker for all sources to compile 157 | -> `install_deps.sh` puts mason packages in `/mason_packages` directory 158 | -> compiler outputs object files in `build/` 159 | -> linker outputs the loadable module in `build/` 160 | -> make copies the 'module.node` from `build/Release` to `lib/binding` 161 | ``` 162 | 163 | Then the module is ready to use. What happens when it is used is: 164 | 165 | ``` 166 | User of your module runs 'npm install' 167 | -> npm fetches your module 168 | -> npm notices an 'install' target that calls out to node-pre-gyp 169 | -> node-pre-gyp downloads the C++ binary from remote url (as specified in the node-pre-gyp config in the package.json) 170 | -> node-pre-gyp places the C++ binary at the ['module_path']('../lib/binding/module.node') 171 | -> the index.js reads '../lib/binding/module.node' 172 | ``` 173 | 174 | This binary file `../lib/binding/module.node` is what `require()` points to within Node.js. 175 | 176 | # Clang Tools 177 | 178 | This skeleton uses two clang/llvm tools for automated formatting and static fixes. 179 | 180 | Each of these tools run within their own [Travis jobs](https://github.com/mapbox/node-cpp-skel/blob/master/.travis.yml). You can disable these Travis jobs if you'd like, by commenting them out in `.travis.yml`. This may be necessary if you're porting this skel into an already-existing project that triggers tons of clang-tidy errors, and you'd like to work through them gradually while still actively iterating on other parts of your code. 181 | 182 | ### [clang-format](https://clang.llvm.org/docs/ClangFormat.html) 183 | Automates coding style enforcement, for example whitespace, tabbing, wrapping. 184 | 185 | To run 186 | ``` 187 | make format 188 | ``` 189 | 190 | - Set format preferences in [`.clang-format`](https://github.com/mapbox/node-cpp-skel/blob/master/.clang-format) 191 | - Installed and run via [`/scripts/clang-format.sh`](https://github.com/mapbox/node-cpp-skel/blob/master/scripts/clang-format.sh) 192 | 193 | 194 | ### [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) 195 | Lint framework that can do very powerful checks, for example bugs that can be deduced via [static analysis](https://github.com/mapbox/cpp/blob/master/glossary.md#static-analysis), unneeded copies, inefficient for-loops, and improved readability. In many cases, it can fix and modernize your code. 196 | 197 | To run 198 | ``` 199 | make tidy 200 | ``` 201 | 202 | - The clang-tidy tool has a large set of checks and when one fails, the error message contains the shorthand for the check name. See llvm's docs for details about what failed check is referring to. For example, see ["performance-unnecessary-copy-initialization"](https://clang.llvm.org/extra/clang-tidy/checks/performance-unnecessary-copy-initialization.html). 203 | - Set tidy preferences in [`.clang-tidy`](https://github.com/mapbox/node-cpp-skel/blob/master/.clang-tidy). When you run clang-tidy, you'll see output of a number of specific checks that tidy runs. You can add/remove any of these checks via the `.clang-tidy` file, configure these checks, and specify the files you'd like to run these checks against. The skel runs clang-tidy against everything within the `/src` directory. 204 | - This skel runs clang-tidy with the `-fix` option, which will automatically apply fixes to your code. Though some warnings will need to be fixed manually. 205 | - Installed and run via [`/scripts/clang-tidy.sh`](https://github.com/mapbox/node-cpp-skel/blob/master/scripts/clang-tidy.sh) 206 | - `NOLINT`: In the case that clang-tidy throws for code that you either want to keep as-is or can't control, you can [use `NOLINT`](https://github.com/mapbox/node-cpp-skel/blob/87c8d51f2d7d05ac44f3ea7a607f60e73a2af9c8/src/module.cpp#L43-L46) to silent clang-tidy. 207 | - Since clang-tidy must compile the code to do its checks, skel [generates](https://github.com/mapbox/node-cpp-skel/blob/master/scripts/generate_compile_commands.py) a JSON file that tells clang-tidy which files to run against and what compile command to run. This newly generated file, `/build/compile_command.json`, is created when running `make tidy`. See [clang's JSON compilation format docs](https://clang.llvm.org/docs/JSONCompilationDatabase.html) for more info. 208 | 209 | 210 | # Xcode 211 | 212 | ![npm-test scheme in Xcode](https://cloud.githubusercontent.com/assets/52399/16448893/4967a454-3df4-11e6-8bb5-701fe46174ef.png) 213 | 214 | If you're developing on macOS and have Xcode installed, you can also type `make xcode` to generate and open an Xcode project. In the dropdown, choose `npm test` to run the npm tests. You can also add more targets by adding the appropriate lines in `Makefile`, and rerunning `make xcode`. If you are modifying `binding.gyp`, e.g. by adding more source files, make sure to rerun `make xcode` so that Xcode knows about the new source files. 215 | -------------------------------------------------------------------------------- /docs/image/howtodebug1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/node-cpp-skel/395eb7256b042605214a9ef66bc7477a9244c0da/docs/image/howtodebug1.png -------------------------------------------------------------------------------- /docs/image/howtodebug2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/node-cpp-skel/395eb7256b042605214a9ef66bc7477a9244c0da/docs/image/howtodebug2.png -------------------------------------------------------------------------------- /docs/image/howtodebug3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/node-cpp-skel/395eb7256b042605214a9ef66bc7477a9244c0da/docs/image/howtodebug3.png -------------------------------------------------------------------------------- /docs/npm-and-package-lock.md: -------------------------------------------------------------------------------- 1 | ## Using npm, package.json, and package-lock.json 2 | 3 | With updated versions of npm, there is now such thing as a `package-lock.json` file, which is now included in node-cpp-skel. Read this doc for more info, also discussion at https://github.com/mapbox/node-cpp-skel/issues/124. 4 | 5 | `package.json` defines the dependencies required by any given library. It often defines a range of acceptable version of those dependencies, for example: 6 | 7 | ``` 8 | "dependencies": { 9 | "eslint": "^4.3.0" 10 | } 11 | ``` 12 | 13 | ... means that any version of eslint >4.3.0 and <5 is acceptable. 14 | 15 | `package-lock.json` defines _explicit versions of dependencies that should be used_. This is useful, because by using `package-lock.json` you can be confident that the version of your dependencies deployed in production matches exactly with the versions you install in your local testing environment. 16 | 17 | Running `npm install` in a folder that contains a `package.json` file will create a `package-lock.json` file if one does not exist. **You should commit this file**. 18 | 19 | If a `package-lock.json` file exists in the repository, but some of the versions of dependencies are out-of-sync with what's specified in `package.json` then running `npm install` will update the `package-lock.json` file. This may happen if you manually edited a version of a dependency in `package.json`, which you should not do (see below). 20 | 21 | If you are creating a PR, you should notice if that PR contains changes to `package-lock.json`, and you should be able to explain in the PR why those changes are being made. 22 | 23 | To take full advantage of the explicit dependency versions specified in `package-lock.json`, don't use `npm install`. Instead, run 24 | 25 | ``` 26 | npm ci 27 | ``` 28 | 29 | Note: this `ci` requires at least npm >= v5.7.1 (https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable). 30 | 31 | This will remove your existing `node_modules` folder, and then install the exact versions defined by `package-lock.json`. It will **never** make changes to `package.json` or `package-lock.json`, unlike `npm install`. 32 | 33 | ### Avoid editing package.json directly 34 | 35 | If you need to change the range of acceptable versions for a dependency: 36 | 37 | ``` 38 | npm install --save eslint@^4.4.8 39 | ``` 40 | 41 | If you want to upgrade a dependency to the most recent version specified by the existing range in `package.json`; 42 | 43 | ``` 44 | npm update eslint 45 | ``` 46 | 47 | If you want to remove a dependency: 48 | 49 | ``` 50 | npm remove eslint 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/publishing-binaries.md: -------------------------------------------------------------------------------- 1 | # Workflow 2 | 3 | So your code is compiling, tested, benched, and ready to be shared. How to get it into the wild? This document will go through what's next. 4 | 5 | Generally speaking, the workflow for a node-addon isn't much different than publishing a regular Javascript node module. The main difference is an added step and a bit more configuration in order to handle the binary. If the concept of a binary file is new, give [this doc a gander](https://github.com/mapbox/node-cpp-skel/blob/dbc48924b3e30bba903e6b9220b0cdf2854f717f/docs/extended-tour.md#builds). 6 | 7 | The typical workflow for a regular node module may look something like this: 8 | 9 | 1. merge to master 10 | 2. git tag 11 | 3. npm publish (Now it's ready to be npm installed) 12 | 13 | The workflow for a node add-on looks very similar: 14 | 15 | 1. merge to master 16 | 2. git tag 17 | 3. publish binaries 18 | 4. npm publish 19 | 20 | Let's talk generally about the relationship between the third and fourth steps. Since your code is in C++, any projects that `npm install` your module as a dependency will need the C++ code precompiled so that Node can use your module in Javascript world. Before publishing your module to npm, you will publish your binaries by putting them on s3 (**Note**: you will likely publish multiple binaries, one for each Node version and various operating systems). This s3 location is reflected in your module's [package.json file](https://github.com/mapbox/node-cpp-skel/blob/dbc48924b3e30bba903e6b9220b0cdf2854f717f/package.json#L35). Your package.json file is also redefining [the install command](https://github.com/mapbox/node-cpp-skel/blob/dbc48924b3e30bba903e6b9220b0cdf2854f717f/package.json#L14), by running node-pre-gyp instead. 21 | 22 | [Node-pre-gyp](https://github.com/mapbox/node-pre-gyp) is responsible for installing the binary by pulling the relevant binary from s3 and placing it in the specified and expected location defined by your module's [main index.js file](https://github.com/mapbox/node-cpp-skel/blob/dbc48924b3e30bba903e6b9220b0cdf2854f717f/lib/index.js#L3). So when a project runs `require()` on your module, they are directly accessing the binary. In a bit more detail, node-pre-gyp will detect [what version of Node is being used and which operating system](https://github.com/mapbox/node-cpp-skel/blob/dbc48924b3e30bba903e6b9220b0cdf2854f717f/package.json#L37), then go to s3 to retrieve the binary that matches. 23 | 24 | Continue reading below to learn how to publish your binaries to s3 so they're ready to be installed. 25 | 26 | 27 | # Publishing Binaries 28 | 29 | It's a good idea to publish pre-built binaries of your module if you want others to be able to easily install it on their system without needing to install a compiler like g++ or clang++. Node-pre-gyp does a lot of the heavy lifting for us (like detecting which system you are building on and deploying to s3) but you'll need a few things configured to get started. 30 | 31 | #### 1) In the `package.json`, update the `"binary"` field to the appropriate s3 bucket `host`. 32 | 33 | For Mapbox staff we recommend using a host setting of `"host": "https://mapbox-node-binary.s3.amazonaws.com",` which will publish to `s3://mapbox-node-binary/`. 34 | 35 | Note: for namespaced modules the path will end up being `s3://mapbox-node-binary/@org/`. 36 | 37 | #### 2) Copy the ci.template.js 38 | 39 | Copy the `ci.template.js` from this repo into your repo and place it at `./cloudformation/ci.template.js` 40 | 41 | #### 3) Install deps for validating and managing cloudformation templates 42 | 43 | ```bash 44 | npm install -g @mapbox/cfn-config # deploying stacks 45 | npm install -g @mapbox/cloudfriend # validating and building templates 46 | ``` 47 | 48 | #### 4) Create a user with permissions to upload to `s3:////` 49 | 50 | First configure your AWS creds. You will need to set at least the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` keys. And also `AWS_SESSION_TOKEN` if you are using 2-factor auth. 51 | 52 | Then run: 53 | 54 | ```bash 55 | validate-template cloudformation/ci.template.js 56 | build-template cloudformation/ci.template.js > cloudformation/ci.template 57 | ``` 58 | 59 | Next we will actually create the user. But first let's discuss what happens here. In addition to creating the user we also write details about the user to a separate bucket (for east auditing purposes). In the below command we: 60 | 61 | - Create a `ci-binary-publish` user 62 | - Using the `cloudformation/ci.template` 63 | - And ask to save our stack configuration to the `cfn-config` bucket. If you are outside of Mapbox, see [you will need to pass a different bucket and also a `--template-bucket` option](https://github.com/mapbox/cfn-config#prerequisites) 64 | 65 | Now, run the command to create the user: 66 | 67 | ``` 68 | cfn-config create ci-binary-publish cloudformation/ci.template -c cfn-configs 69 | ``` 70 | 71 | It will prompt you, choose: 72 | 73 | - New configuration 74 | - Ready to create the stack? Y 75 | 76 | It will fail if the stack already exists. In this case you can recreate a new user by deleting the stack by running `./node_modules/.bin/cfn-config delete ci-binary-publish cloudformation/ci.template` and then creating a new one. 77 | 78 | #### 5) Get the user keys 79 | 80 | After the create step succeeds you will have a new user. You now need to get get the users `AccessKeyId` and `SecretAccessKey`. 81 | 82 | You can do this in two ways: 1) finding the keys through the AWS console, or 2) using cfn-config to show the stack information 83 | 84 | **Tokens via cfn-config** 85 | 86 | Run the command `cfn-config info ci-binary-publish` and you'll see a JSON output with `AccessKeyId` and `SecretAccessKey`. 87 | 88 | **Tokens via the AWS console** 89 | 90 | - Go to https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks?filter=active&tab=outputs 91 | - Search for `ci-binary-publish`. You should see a stack called `-ci-binary-publish` 92 | - Click the checkbox beside your `-ci-binary-publish` stack 93 | - Click the `Output` tab to access the `AccessKeyId` and `SecretAccessKey` for this new user. 94 | 95 | 96 | #### 6) Add the keys to the travis 97 | 98 | **Adding to travis UI settings** 99 | 100 | - Go to https://travis-ci.org///settings 101 | - Scroll to the bottom and find the `Environment Variables` section 102 | - Add a variable called `AWS_ACCESS_KEY_ID` and put the value of the `AccessKeyId` in it 103 | - CRITICAL: Choose `OFF` for `Display value in build log` to ensure the variables are not shown in the logs 104 | - Click `Add` 105 | - Add a variable called `AWS_SECRET_ACCESS_KEY` and put the value of the `SecretAccessKey` in it 106 | - CRITICAL: Choose `OFF` for `Display value in build log` to ensure the variables are not shown in the logs 107 | - Click `Add` 108 | 109 | #### 7) All done! 110 | 111 | Now that you have generated keys for a user that can publish to s3 and provided these keys to travis in a secure way, you should be able to publish binaries. But this should be done in an automated way. See the next section below for how to do that with travis.ci. 112 | 113 | **Publishing on Travis CI** 114 | 115 | This project includes a `script/publish.sh` command that builds binaries and publishes them to s3. This script checks your commit message for either `[publish binary]` or `[republish binary]` in order to begin publishing. This allows you to publish binaries according to the version specified in your `package.json` like this: 116 | 117 | ``` 118 | git commit -m 'releasing 0.1.0 [publish binary]' 119 | ``` 120 | 121 | Republishing a binary overrides the current version and must be specified with `[republish binary]`. 122 | 123 | **Adding new operating systems and node versions** 124 | 125 | The `.travis.yml` file uses the `matrix` to set up each individual job, which specifies the operating system, node version, and other environment variables for running the scripts. To add more operating systems and node versions to the binaries you publish, add another job to the matrix like this: 126 | 127 | ```yaml 128 | - os: {operating system} 129 | env: NODE="{your node version}" TARGET="Release" 130 | install: *setup 131 | script: *test 132 | after_script: *publish 133 | ``` 134 | 135 | ### Dev releases 136 | 137 | You may want to test your module works correctly, in downstream dependencies, before formally publishing. To do this we recommend you: 138 | 139 | 1. Create a branch of your node c++ module 140 | 141 | 2. Modify the `version` string in your `package.json` like: 142 | 143 | ```diff 144 | diff --git a/package.json b/package.json 145 | index e00b7b5..22f7cd9 100644 146 | --- a/package.json 147 | +++ b/package.json 148 | @@ -1,6 +1,6 @@ 149 | { 150 | "name": "@mapbox/node-cpp-skel", 151 | - "version": "0.1.0", 152 | + "version": "0.1.0-alpha", 153 | "description": "Skeleton for bindings to C++ libraries for Node.js using NAN", 154 | "url": "http://github.com/mapbox/node-cpp-skel", 155 | "main": "./lib/index.js", 156 | ``` 157 | 158 | 3. Publishing C++ binaries by pushing a commit with `[publish binary]` per https://github.com/mapbox/node-cpp-skel/blob/master/docs/publishing-binaries.md#7-all-done 159 | 160 | 161 | 4. **Option A)** Require your module in downstream applications like: 162 | 163 | ```js 164 | "your-module": "https://github.com///tarball/", 165 | ``` 166 | 167 | If you're publishing from a private repo, generate a dev release and then reference the url in the appropriate `package.json` file. For example, `zip` the repo, put to S3, and then reference the S3 url in `package.json`. 168 | 169 | **Option B)** Issue a npm dev release after the binary is published. 170 | 171 | Run `npm publish --tag dev` and then require your module in downstream applications like: 172 | 173 | ```js 174 | "your-module": "0.1.0-alpha", 175 | ``` 176 | 177 | For npm dev releases, it’s good to use the `--tag ` to avoid publishing to the latest tag. If you run `npm publish` then `0.1.0-alpha` is what anyone running `npm install --save` will receive. 178 | 179 | 180 | #### Before `npm publish` 181 | 182 | Before publishing to npm, you can ensure the final packaged tarball will include what you expect. For instance, you want to avoid a large accidental file being packaged by npm and make sure the package contains all needed dependencies. 183 | 184 | Take a peek at what npm will publish by running: 185 | 186 | ``` 187 | make 188 | make testpack 189 | ``` 190 | 191 | This will create a tarball locally and print every file included. A couple basic checks: 192 | - make sure `.mason` is not included 193 | - make sure `node-pre-gyp` directory is included, because it is responsible for knowing where to grab the binary from s3. 194 | 195 | **Hot tips** :hot_pepper: 196 | - Node and npm versions can have differing `npm pack` and `npm publish` behaviour, so be mindful of what your environment is using 197 | - You can use the resulting tarball locally and install it within another local repo to make sure it works: 198 | ``` 199 | npm install 200 | ``` 201 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { 4 | hello, 5 | helloAsync, 6 | helloPromise, 7 | HelloObject, 8 | HelloObjectAsync 9 | } = require('./binding/module.node'); 10 | 11 | module.exports = { 12 | /** 13 | * This is a synchronous standalone function that logs a string. 14 | * @name hello 15 | * @returns {string} 16 | * @example 17 | * const { hello } = require('@mapbox/node-cpp-skel'); 18 | * const check = hello(); 19 | * console.log(check); // => "hello world" 20 | */ 21 | hello, 22 | 23 | /** 24 | * This is an asynchronous standalone function that logs a string. 25 | * @name helloAsync 26 | * @param {Object} args - different ways to alter the string 27 | * @param {boolean} args.louder - adds exclamation points to the string 28 | * @param {boolean} args.buffer - returns value as a node buffer rather than a string 29 | * @param {Function} callback - from whence the hello comes, returns a string 30 | * @returns {string} 31 | * @example 32 | * const { helloAsync } = require('@mapbox/node-cpp-skel'); 33 | * helloAsync({ louder: true }, function(err, result) { 34 | * if (err) throw err; 35 | * console.log(result); // => "...threads are busy async bees...hello 36 | * world!!!!" 37 | * }); 38 | */ 39 | helloAsync, 40 | 41 | /** 42 | * This is a function that returns a promise. It multiplies a string N times. 43 | * @name helloPromise 44 | * @param {Object} [options] - different ways to alter the string 45 | * @param {string} [options.phrase=hello] - the string to multiply 46 | * @param {Number} [options.multiply=1] - duplicate the string this number of times 47 | * @returns {Promise} 48 | * @example 49 | * const { helloPromise } = require('@mapbox/node-cpp-skel'); 50 | * const result = await helloAsync({ phrase: 'Howdy', multiply: 3 }); 51 | * console.log(result); // HowdyHowdyHowdy 52 | */ 53 | helloPromise, 54 | 55 | /** 56 | * Synchronous class, called HelloObject 57 | * @class HelloObject 58 | * @example 59 | * const { HelloObject } = require('@mapbox/node-cpp-skel'); 60 | * const Obj = new HelloObject('greg'); 61 | */ 62 | 63 | /** 64 | * Say hello 65 | * 66 | * @name hello 67 | * @memberof HelloObject 68 | * @returns {String} 69 | * @example 70 | * const x = Obj.hello(); 71 | * console.log(x); // => '...initialized an object...hello greg' 72 | */ 73 | HelloObject, 74 | 75 | /** 76 | * Asynchronous class, called HelloObjectAsync 77 | * @class HelloObjectAsync 78 | * @example 79 | * const { HelloObjectAsync } = require('@mapbox/node-cpp-skel'); 80 | * const Obj = new module.HelloObjectAsync('greg'); 81 | */ 82 | 83 | /** 84 | * Say hello while doing expensive work in threads 85 | * 86 | * @name helloAsync 87 | * @memberof HelloObjectAsync 88 | * @param {Object} args - different ways to alter the string 89 | * @param {boolean} args.louder - adds exclamation points to the string 90 | * @param {buffer} args.buffer - returns object as a node buffer rather then string 91 | * @param {Function} callback - from whence the hello comes, returns a string 92 | * @returns {String} 93 | * @example 94 | * const { HelloObjectAsync } = require('@mapbox/node-cpp-skel'); 95 | * const Obj = new HelloObjectAsync('greg'); 96 | * Obj.helloAsync({ louder: true }, function(err, result) { 97 | * if (err) throw err; 98 | * console.log(result); // => '...threads are busy async bees...hello greg!!!' 99 | * }); 100 | */ 101 | HelloObjectAsync 102 | }; -------------------------------------------------------------------------------- /mason-versions.ini: -------------------------------------------------------------------------------- 1 | [headers] 2 | protozero=1.6.8 3 | [compiled] 4 | clang++=10.0.0 5 | clang-tidy=10.0.0 6 | clang-format=10.0.0 7 | llvm-cov=10.0.0 8 | binutils=2.35 9 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/node-cpp-skel", 3 | "version": "0.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/mason-js": { 8 | "version": "0.1.5", 9 | "resolved": "https://registry.npmjs.org/@mapbox/mason-js/-/mason-js-0.1.5.tgz", 10 | "integrity": "sha512-/XHxI8UvCARISrpc1GCIOn6JI17KC2//svFCztAzb+Vjzfq3tTDOzhrh605yusmOyL1A80plUTwUta3GSpaiMw==", 11 | "requires": { 12 | "d3-queue": "^3.0.7", 13 | "fs-extra": "^4.0.2", 14 | "minimist": "^1.2.0", 15 | "mkdirp": "^0.5.1", 16 | "needle": "^2.2.0", 17 | "npmlog": "^4.1.2", 18 | "tar": "^4.0.2" 19 | } 20 | }, 21 | "@mapbox/node-pre-gyp": { 22 | "version": "1.0.8", 23 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", 24 | "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", 25 | "requires": { 26 | "detect-libc": "^1.0.3", 27 | "https-proxy-agent": "^5.0.0", 28 | "make-dir": "^3.1.0", 29 | "node-fetch": "^2.6.5", 30 | "nopt": "^5.0.0", 31 | "npmlog": "^5.0.1", 32 | "rimraf": "^3.0.2", 33 | "semver": "^7.3.5", 34 | "tar": "^6.1.11" 35 | }, 36 | "dependencies": { 37 | "ansi-regex": { 38 | "version": "5.0.1", 39 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 40 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 41 | }, 42 | "are-we-there-yet": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 45 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 46 | "requires": { 47 | "delegates": "^1.0.0", 48 | "readable-stream": "^3.6.0" 49 | } 50 | }, 51 | "chownr": { 52 | "version": "2.0.0", 53 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 54 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 55 | }, 56 | "fs-minipass": { 57 | "version": "2.1.0", 58 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 59 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 60 | "requires": { 61 | "minipass": "^3.0.0" 62 | } 63 | }, 64 | "gauge": { 65 | "version": "3.0.2", 66 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 67 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 68 | "requires": { 69 | "aproba": "^1.0.3 || ^2.0.0", 70 | "color-support": "^1.1.2", 71 | "console-control-strings": "^1.0.0", 72 | "has-unicode": "^2.0.1", 73 | "object-assign": "^4.1.1", 74 | "signal-exit": "^3.0.0", 75 | "string-width": "^4.2.3", 76 | "strip-ansi": "^6.0.1", 77 | "wide-align": "^1.1.2" 78 | } 79 | }, 80 | "is-fullwidth-code-point": { 81 | "version": "3.0.0", 82 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 83 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 84 | }, 85 | "minipass": { 86 | "version": "3.1.6", 87 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", 88 | "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", 89 | "requires": { 90 | "yallist": "^4.0.0" 91 | } 92 | }, 93 | "minizlib": { 94 | "version": "2.1.2", 95 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 96 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 97 | "requires": { 98 | "minipass": "^3.0.0", 99 | "yallist": "^4.0.0" 100 | } 101 | }, 102 | "mkdirp": { 103 | "version": "1.0.4", 104 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 105 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 106 | }, 107 | "npmlog": { 108 | "version": "5.0.1", 109 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 110 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 111 | "requires": { 112 | "are-we-there-yet": "^2.0.0", 113 | "console-control-strings": "^1.1.0", 114 | "gauge": "^3.0.0", 115 | "set-blocking": "^2.0.0" 116 | } 117 | }, 118 | "readable-stream": { 119 | "version": "3.6.0", 120 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 121 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 122 | "requires": { 123 | "inherits": "^2.0.3", 124 | "string_decoder": "^1.1.1", 125 | "util-deprecate": "^1.0.1" 126 | } 127 | }, 128 | "string-width": { 129 | "version": "4.2.3", 130 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 131 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 132 | "requires": { 133 | "emoji-regex": "^8.0.0", 134 | "is-fullwidth-code-point": "^3.0.0", 135 | "strip-ansi": "^6.0.1" 136 | } 137 | }, 138 | "strip-ansi": { 139 | "version": "6.0.1", 140 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 141 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 142 | "requires": { 143 | "ansi-regex": "^5.0.1" 144 | } 145 | }, 146 | "tar": { 147 | "version": "6.1.11", 148 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 149 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 150 | "requires": { 151 | "chownr": "^2.0.0", 152 | "fs-minipass": "^2.0.0", 153 | "minipass": "^3.0.0", 154 | "minizlib": "^2.1.1", 155 | "mkdirp": "^1.0.3", 156 | "yallist": "^4.0.0" 157 | } 158 | }, 159 | "yallist": { 160 | "version": "4.0.0", 161 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 162 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 163 | } 164 | } 165 | }, 166 | "abbrev": { 167 | "version": "1.1.1", 168 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 169 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 170 | }, 171 | "agent-base": { 172 | "version": "6.0.2", 173 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 174 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 175 | "requires": { 176 | "debug": "4" 177 | }, 178 | "dependencies": { 179 | "debug": { 180 | "version": "4.3.3", 181 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 182 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 183 | "requires": { 184 | "ms": "2.1.2" 185 | } 186 | }, 187 | "ms": { 188 | "version": "2.1.2", 189 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 190 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 191 | } 192 | } 193 | }, 194 | "ansi-regex": { 195 | "version": "2.1.1", 196 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 197 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 198 | }, 199 | "aproba": { 200 | "version": "1.2.0", 201 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 202 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 203 | }, 204 | "are-we-there-yet": { 205 | "version": "1.1.4", 206 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", 207 | "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", 208 | "requires": { 209 | "delegates": "^1.0.0", 210 | "readable-stream": "^2.0.6" 211 | } 212 | }, 213 | "array-filter": { 214 | "version": "1.0.0", 215 | "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", 216 | "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", 217 | "dev": true 218 | }, 219 | "available-typed-arrays": { 220 | "version": "1.0.2", 221 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", 222 | "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", 223 | "dev": true, 224 | "requires": { 225 | "array-filter": "^1.0.0" 226 | } 227 | }, 228 | "aws-sdk": { 229 | "version": "2.840.0", 230 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.840.0.tgz", 231 | "integrity": "sha512-ngesHJqb0PXYjJNnCsAX4yLkR6JFQJB+3eDGwh3mYRjcq9voix5RfbCFQT1lwWu7bcMBPCrRIA2lJkkTMYXq+A==", 232 | "dev": true, 233 | "requires": { 234 | "buffer": "4.9.2", 235 | "events": "1.1.1", 236 | "ieee754": "1.1.13", 237 | "jmespath": "0.15.0", 238 | "querystring": "0.2.0", 239 | "sax": "1.2.1", 240 | "url": "0.10.3", 241 | "uuid": "3.3.2", 242 | "xml2js": "0.4.19" 243 | }, 244 | "dependencies": { 245 | "sax": { 246 | "version": "1.2.1", 247 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 248 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 249 | "dev": true 250 | } 251 | } 252 | }, 253 | "balanced-match": { 254 | "version": "1.0.0", 255 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 256 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 257 | }, 258 | "base64-js": { 259 | "version": "1.5.1", 260 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 261 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 262 | "dev": true 263 | }, 264 | "brace-expansion": { 265 | "version": "1.1.11", 266 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 267 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 268 | "requires": { 269 | "balanced-match": "^1.0.0", 270 | "concat-map": "0.0.1" 271 | } 272 | }, 273 | "buffer": { 274 | "version": "4.9.2", 275 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 276 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 277 | "dev": true, 278 | "requires": { 279 | "base64-js": "^1.0.2", 280 | "ieee754": "^1.1.4", 281 | "isarray": "^1.0.0" 282 | } 283 | }, 284 | "bytes": { 285 | "version": "3.1.0", 286 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 287 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 288 | "dev": true 289 | }, 290 | "call-bind": { 291 | "version": "1.0.2", 292 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 293 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 294 | "dev": true, 295 | "requires": { 296 | "function-bind": "^1.1.1", 297 | "get-intrinsic": "^1.0.2" 298 | } 299 | }, 300 | "chownr": { 301 | "version": "1.0.1", 302 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", 303 | "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" 304 | }, 305 | "code-point-at": { 306 | "version": "1.1.0", 307 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 308 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 309 | }, 310 | "color-support": { 311 | "version": "1.1.3", 312 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 313 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" 314 | }, 315 | "concat-map": { 316 | "version": "0.0.1", 317 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 318 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 319 | }, 320 | "console-control-strings": { 321 | "version": "1.1.0", 322 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 323 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 324 | }, 325 | "core-util-is": { 326 | "version": "1.0.2", 327 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 328 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 329 | }, 330 | "d3-queue": { 331 | "version": "3.0.7", 332 | "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", 333 | "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" 334 | }, 335 | "debug": { 336 | "version": "2.6.9", 337 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 338 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 339 | "requires": { 340 | "ms": "2.0.0" 341 | } 342 | }, 343 | "deep-equal": { 344 | "version": "2.0.5", 345 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", 346 | "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", 347 | "dev": true, 348 | "requires": { 349 | "call-bind": "^1.0.0", 350 | "es-get-iterator": "^1.1.1", 351 | "get-intrinsic": "^1.0.1", 352 | "is-arguments": "^1.0.4", 353 | "is-date-object": "^1.0.2", 354 | "is-regex": "^1.1.1", 355 | "isarray": "^2.0.5", 356 | "object-is": "^1.1.4", 357 | "object-keys": "^1.1.1", 358 | "object.assign": "^4.1.2", 359 | "regexp.prototype.flags": "^1.3.0", 360 | "side-channel": "^1.0.3", 361 | "which-boxed-primitive": "^1.0.1", 362 | "which-collection": "^1.0.1", 363 | "which-typed-array": "^1.1.2" 364 | }, 365 | "dependencies": { 366 | "isarray": { 367 | "version": "2.0.5", 368 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 369 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 370 | "dev": true 371 | } 372 | } 373 | }, 374 | "define-properties": { 375 | "version": "1.1.3", 376 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 377 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 378 | "dev": true, 379 | "requires": { 380 | "object-keys": "^1.0.12" 381 | } 382 | }, 383 | "defined": { 384 | "version": "1.0.0", 385 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 386 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 387 | "dev": true 388 | }, 389 | "delegates": { 390 | "version": "1.0.0", 391 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 392 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 393 | }, 394 | "detect-libc": { 395 | "version": "1.0.3", 396 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 397 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 398 | }, 399 | "dotignore": { 400 | "version": "0.1.2", 401 | "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", 402 | "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", 403 | "dev": true, 404 | "requires": { 405 | "minimatch": "^3.0.4" 406 | } 407 | }, 408 | "emoji-regex": { 409 | "version": "8.0.0", 410 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 411 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 412 | }, 413 | "es-abstract": { 414 | "version": "1.18.0-next.2", 415 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", 416 | "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", 417 | "dev": true, 418 | "requires": { 419 | "call-bind": "^1.0.2", 420 | "es-to-primitive": "^1.2.1", 421 | "function-bind": "^1.1.1", 422 | "get-intrinsic": "^1.0.2", 423 | "has": "^1.0.3", 424 | "has-symbols": "^1.0.1", 425 | "is-callable": "^1.2.2", 426 | "is-negative-zero": "^2.0.1", 427 | "is-regex": "^1.1.1", 428 | "object-inspect": "^1.9.0", 429 | "object-keys": "^1.1.1", 430 | "object.assign": "^4.1.2", 431 | "string.prototype.trimend": "^1.0.3", 432 | "string.prototype.trimstart": "^1.0.3" 433 | } 434 | }, 435 | "es-get-iterator": { 436 | "version": "1.1.2", 437 | "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", 438 | "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", 439 | "dev": true, 440 | "requires": { 441 | "call-bind": "^1.0.2", 442 | "get-intrinsic": "^1.1.0", 443 | "has-symbols": "^1.0.1", 444 | "is-arguments": "^1.1.0", 445 | "is-map": "^2.0.2", 446 | "is-set": "^2.0.2", 447 | "is-string": "^1.0.5", 448 | "isarray": "^2.0.5" 449 | }, 450 | "dependencies": { 451 | "isarray": { 452 | "version": "2.0.5", 453 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 454 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 455 | "dev": true 456 | } 457 | } 458 | }, 459 | "es-to-primitive": { 460 | "version": "1.2.1", 461 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 462 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 463 | "dev": true, 464 | "requires": { 465 | "is-callable": "^1.1.4", 466 | "is-date-object": "^1.0.1", 467 | "is-symbol": "^1.0.2" 468 | } 469 | }, 470 | "events": { 471 | "version": "1.1.1", 472 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 473 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 474 | "dev": true 475 | }, 476 | "for-each": { 477 | "version": "0.3.3", 478 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 479 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 480 | "dev": true, 481 | "requires": { 482 | "is-callable": "^1.1.3" 483 | } 484 | }, 485 | "foreach": { 486 | "version": "2.0.5", 487 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 488 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 489 | "dev": true 490 | }, 491 | "fs-extra": { 492 | "version": "4.0.3", 493 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", 494 | "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", 495 | "requires": { 496 | "graceful-fs": "^4.1.2", 497 | "jsonfile": "^4.0.0", 498 | "universalify": "^0.1.0" 499 | } 500 | }, 501 | "fs-minipass": { 502 | "version": "1.2.5", 503 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", 504 | "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", 505 | "requires": { 506 | "minipass": "^2.2.1" 507 | } 508 | }, 509 | "fs.realpath": { 510 | "version": "1.0.0", 511 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 512 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 513 | }, 514 | "function-bind": { 515 | "version": "1.1.1", 516 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 517 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 518 | "dev": true 519 | }, 520 | "gauge": { 521 | "version": "2.7.4", 522 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 523 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 524 | "requires": { 525 | "aproba": "^1.0.3", 526 | "console-control-strings": "^1.0.0", 527 | "has-unicode": "^2.0.0", 528 | "object-assign": "^4.1.0", 529 | "signal-exit": "^3.0.0", 530 | "string-width": "^1.0.1", 531 | "strip-ansi": "^3.0.1", 532 | "wide-align": "^1.1.0" 533 | } 534 | }, 535 | "get-intrinsic": { 536 | "version": "1.1.1", 537 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 538 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 539 | "dev": true, 540 | "requires": { 541 | "function-bind": "^1.1.1", 542 | "has": "^1.0.3", 543 | "has-symbols": "^1.0.1" 544 | } 545 | }, 546 | "glob": { 547 | "version": "7.1.6", 548 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 549 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 550 | "requires": { 551 | "fs.realpath": "^1.0.0", 552 | "inflight": "^1.0.4", 553 | "inherits": "2", 554 | "minimatch": "^3.0.4", 555 | "once": "^1.3.0", 556 | "path-is-absolute": "^1.0.0" 557 | } 558 | }, 559 | "graceful-fs": { 560 | "version": "4.2.3", 561 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 562 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" 563 | }, 564 | "has": { 565 | "version": "1.0.3", 566 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 567 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 568 | "dev": true, 569 | "requires": { 570 | "function-bind": "^1.1.1" 571 | } 572 | }, 573 | "has-symbols": { 574 | "version": "1.0.1", 575 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 576 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 577 | "dev": true 578 | }, 579 | "has-unicode": { 580 | "version": "2.0.1", 581 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 582 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 583 | }, 584 | "https-proxy-agent": { 585 | "version": "5.0.0", 586 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 587 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 588 | "requires": { 589 | "agent-base": "6", 590 | "debug": "4" 591 | }, 592 | "dependencies": { 593 | "debug": { 594 | "version": "4.3.3", 595 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 596 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 597 | "requires": { 598 | "ms": "2.1.2" 599 | } 600 | }, 601 | "ms": { 602 | "version": "2.1.2", 603 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 604 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 605 | } 606 | } 607 | }, 608 | "iconv-lite": { 609 | "version": "0.4.23", 610 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 611 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 612 | "requires": { 613 | "safer-buffer": ">= 2.1.2 < 3" 614 | } 615 | }, 616 | "ieee754": { 617 | "version": "1.1.13", 618 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 619 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 620 | "dev": true 621 | }, 622 | "inflight": { 623 | "version": "1.0.6", 624 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 625 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 626 | "requires": { 627 | "once": "^1.3.0", 628 | "wrappy": "1" 629 | } 630 | }, 631 | "inherits": { 632 | "version": "2.0.3", 633 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 634 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 635 | }, 636 | "is-arguments": { 637 | "version": "1.1.0", 638 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", 639 | "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", 640 | "dev": true, 641 | "requires": { 642 | "call-bind": "^1.0.0" 643 | } 644 | }, 645 | "is-bigint": { 646 | "version": "1.0.1", 647 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", 648 | "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", 649 | "dev": true 650 | }, 651 | "is-boolean-object": { 652 | "version": "1.1.0", 653 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", 654 | "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", 655 | "dev": true, 656 | "requires": { 657 | "call-bind": "^1.0.0" 658 | } 659 | }, 660 | "is-callable": { 661 | "version": "1.2.3", 662 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", 663 | "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", 664 | "dev": true 665 | }, 666 | "is-core-module": { 667 | "version": "2.2.0", 668 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", 669 | "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", 670 | "dev": true, 671 | "requires": { 672 | "has": "^1.0.3" 673 | } 674 | }, 675 | "is-date-object": { 676 | "version": "1.0.2", 677 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 678 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 679 | "dev": true 680 | }, 681 | "is-fullwidth-code-point": { 682 | "version": "1.0.0", 683 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 684 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 685 | "requires": { 686 | "number-is-nan": "^1.0.0" 687 | } 688 | }, 689 | "is-map": { 690 | "version": "2.0.2", 691 | "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", 692 | "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", 693 | "dev": true 694 | }, 695 | "is-negative-zero": { 696 | "version": "2.0.1", 697 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", 698 | "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", 699 | "dev": true 700 | }, 701 | "is-number-object": { 702 | "version": "1.0.4", 703 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", 704 | "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", 705 | "dev": true 706 | }, 707 | "is-regex": { 708 | "version": "1.1.2", 709 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", 710 | "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", 711 | "dev": true, 712 | "requires": { 713 | "call-bind": "^1.0.2", 714 | "has-symbols": "^1.0.1" 715 | } 716 | }, 717 | "is-set": { 718 | "version": "2.0.2", 719 | "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", 720 | "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", 721 | "dev": true 722 | }, 723 | "is-string": { 724 | "version": "1.0.5", 725 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", 726 | "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", 727 | "dev": true 728 | }, 729 | "is-symbol": { 730 | "version": "1.0.3", 731 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 732 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 733 | "dev": true, 734 | "requires": { 735 | "has-symbols": "^1.0.1" 736 | } 737 | }, 738 | "is-typed-array": { 739 | "version": "1.1.4", 740 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.4.tgz", 741 | "integrity": "sha512-ILaRgn4zaSrVNXNGtON6iFNotXW3hAPF3+0fB1usg2jFlWqo5fEDdmJkz0zBfoi7Dgskr8Khi2xZ8cXqZEfXNA==", 742 | "dev": true, 743 | "requires": { 744 | "available-typed-arrays": "^1.0.2", 745 | "call-bind": "^1.0.0", 746 | "es-abstract": "^1.18.0-next.1", 747 | "foreach": "^2.0.5", 748 | "has-symbols": "^1.0.1" 749 | } 750 | }, 751 | "is-weakmap": { 752 | "version": "2.0.1", 753 | "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", 754 | "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", 755 | "dev": true 756 | }, 757 | "is-weakset": { 758 | "version": "2.0.1", 759 | "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", 760 | "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", 761 | "dev": true 762 | }, 763 | "isarray": { 764 | "version": "1.0.0", 765 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 766 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 767 | }, 768 | "jmespath": { 769 | "version": "0.15.0", 770 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 771 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 772 | "dev": true 773 | }, 774 | "jsonfile": { 775 | "version": "4.0.0", 776 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 777 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 778 | "requires": { 779 | "graceful-fs": "^4.1.6" 780 | } 781 | }, 782 | "lru-cache": { 783 | "version": "6.0.0", 784 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 785 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 786 | "requires": { 787 | "yallist": "^4.0.0" 788 | }, 789 | "dependencies": { 790 | "yallist": { 791 | "version": "4.0.0", 792 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 793 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 794 | } 795 | } 796 | }, 797 | "make-dir": { 798 | "version": "3.1.0", 799 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 800 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 801 | "requires": { 802 | "semver": "^6.0.0" 803 | }, 804 | "dependencies": { 805 | "semver": { 806 | "version": "6.3.0", 807 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 808 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 809 | } 810 | } 811 | }, 812 | "minimatch": { 813 | "version": "3.0.4", 814 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 815 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 816 | "requires": { 817 | "brace-expansion": "^1.1.7" 818 | } 819 | }, 820 | "minimist": { 821 | "version": "1.2.5", 822 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 823 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 824 | }, 825 | "minipass": { 826 | "version": "2.3.3", 827 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", 828 | "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", 829 | "requires": { 830 | "safe-buffer": "^5.1.2", 831 | "yallist": "^3.0.0" 832 | }, 833 | "dependencies": { 834 | "safe-buffer": { 835 | "version": "5.1.2", 836 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 837 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 838 | } 839 | } 840 | }, 841 | "minizlib": { 842 | "version": "1.1.0", 843 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", 844 | "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", 845 | "requires": { 846 | "minipass": "^2.2.1" 847 | } 848 | }, 849 | "mkdirp": { 850 | "version": "0.5.1", 851 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 852 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 853 | "requires": { 854 | "minimist": "0.0.8" 855 | }, 856 | "dependencies": { 857 | "minimist": { 858 | "version": "0.0.8", 859 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 860 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 861 | } 862 | } 863 | }, 864 | "ms": { 865 | "version": "2.0.0", 866 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 867 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 868 | }, 869 | "needle": { 870 | "version": "2.2.1", 871 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", 872 | "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", 873 | "requires": { 874 | "debug": "^2.1.2", 875 | "iconv-lite": "^0.4.4", 876 | "sax": "^1.2.4" 877 | } 878 | }, 879 | "node-addon-api": { 880 | "version": "4.3.0", 881 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", 882 | "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" 883 | }, 884 | "node-fetch": { 885 | "version": "2.6.7", 886 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 887 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 888 | "requires": { 889 | "whatwg-url": "^5.0.0" 890 | } 891 | }, 892 | "nopt": { 893 | "version": "5.0.0", 894 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 895 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 896 | "requires": { 897 | "abbrev": "1" 898 | } 899 | }, 900 | "npmlog": { 901 | "version": "4.1.2", 902 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 903 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 904 | "requires": { 905 | "are-we-there-yet": "~1.1.2", 906 | "console-control-strings": "~1.1.0", 907 | "gauge": "~2.7.3", 908 | "set-blocking": "~2.0.0" 909 | } 910 | }, 911 | "number-is-nan": { 912 | "version": "1.0.1", 913 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 914 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 915 | }, 916 | "object-assign": { 917 | "version": "4.1.1", 918 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 919 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 920 | }, 921 | "object-inspect": { 922 | "version": "1.9.0", 923 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", 924 | "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", 925 | "dev": true 926 | }, 927 | "object-is": { 928 | "version": "1.1.4", 929 | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", 930 | "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", 931 | "dev": true, 932 | "requires": { 933 | "call-bind": "^1.0.0", 934 | "define-properties": "^1.1.3" 935 | } 936 | }, 937 | "object-keys": { 938 | "version": "1.1.1", 939 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 940 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 941 | "dev": true 942 | }, 943 | "object.assign": { 944 | "version": "4.1.2", 945 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", 946 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", 947 | "dev": true, 948 | "requires": { 949 | "call-bind": "^1.0.0", 950 | "define-properties": "^1.1.3", 951 | "has-symbols": "^1.0.1", 952 | "object-keys": "^1.1.1" 953 | } 954 | }, 955 | "once": { 956 | "version": "1.4.0", 957 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 958 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 959 | "requires": { 960 | "wrappy": "1" 961 | } 962 | }, 963 | "path-is-absolute": { 964 | "version": "1.0.1", 965 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 966 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 967 | }, 968 | "path-parse": { 969 | "version": "1.0.6", 970 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 971 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 972 | "dev": true 973 | }, 974 | "process-nextick-args": { 975 | "version": "2.0.0", 976 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 977 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 978 | }, 979 | "punycode": { 980 | "version": "1.3.2", 981 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 982 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 983 | "dev": true 984 | }, 985 | "querystring": { 986 | "version": "0.2.0", 987 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 988 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 989 | "dev": true 990 | }, 991 | "readable-stream": { 992 | "version": "2.3.6", 993 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 994 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 995 | "requires": { 996 | "core-util-is": "~1.0.0", 997 | "inherits": "~2.0.3", 998 | "isarray": "~1.0.0", 999 | "process-nextick-args": "~2.0.0", 1000 | "safe-buffer": "~5.1.1", 1001 | "string_decoder": "~1.1.1", 1002 | "util-deprecate": "~1.0.1" 1003 | }, 1004 | "dependencies": { 1005 | "string_decoder": { 1006 | "version": "1.1.1", 1007 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1008 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1009 | "requires": { 1010 | "safe-buffer": "~5.1.0" 1011 | } 1012 | } 1013 | } 1014 | }, 1015 | "regexp.prototype.flags": { 1016 | "version": "1.3.1", 1017 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", 1018 | "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", 1019 | "dev": true, 1020 | "requires": { 1021 | "call-bind": "^1.0.2", 1022 | "define-properties": "^1.1.3" 1023 | } 1024 | }, 1025 | "resolve": { 1026 | "version": "1.19.0", 1027 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", 1028 | "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", 1029 | "dev": true, 1030 | "requires": { 1031 | "is-core-module": "^2.1.0", 1032 | "path-parse": "^1.0.6" 1033 | } 1034 | }, 1035 | "resumer": { 1036 | "version": "0.0.0", 1037 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 1038 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 1039 | "dev": true, 1040 | "requires": { 1041 | "through": "~2.3.4" 1042 | } 1043 | }, 1044 | "rimraf": { 1045 | "version": "3.0.2", 1046 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1047 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1048 | "requires": { 1049 | "glob": "^7.1.3" 1050 | } 1051 | }, 1052 | "safe-buffer": { 1053 | "version": "5.1.2", 1054 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1055 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1056 | }, 1057 | "safer-buffer": { 1058 | "version": "2.1.2", 1059 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1060 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1061 | }, 1062 | "sax": { 1063 | "version": "1.2.4", 1064 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1065 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1066 | }, 1067 | "semver": { 1068 | "version": "7.3.5", 1069 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 1070 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1071 | "requires": { 1072 | "lru-cache": "^6.0.0" 1073 | } 1074 | }, 1075 | "set-blocking": { 1076 | "version": "2.0.0", 1077 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1078 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1079 | }, 1080 | "side-channel": { 1081 | "version": "1.0.4", 1082 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1083 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1084 | "dev": true, 1085 | "requires": { 1086 | "call-bind": "^1.0.0", 1087 | "get-intrinsic": "^1.0.2", 1088 | "object-inspect": "^1.9.0" 1089 | } 1090 | }, 1091 | "signal-exit": { 1092 | "version": "3.0.2", 1093 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1094 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 1095 | }, 1096 | "string-width": { 1097 | "version": "1.0.2", 1098 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1099 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1100 | "requires": { 1101 | "code-point-at": "^1.0.0", 1102 | "is-fullwidth-code-point": "^1.0.0", 1103 | "strip-ansi": "^3.0.0" 1104 | } 1105 | }, 1106 | "string.prototype.trim": { 1107 | "version": "1.2.3", 1108 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.3.tgz", 1109 | "integrity": "sha512-16IL9pIBA5asNOSukPfxX2W68BaBvxyiRK16H3RA/lWW9BDosh+w7f+LhomPHpXJ82QEe7w7/rY/S1CV97raLg==", 1110 | "dev": true, 1111 | "requires": { 1112 | "call-bind": "^1.0.0", 1113 | "define-properties": "^1.1.3", 1114 | "es-abstract": "^1.18.0-next.1" 1115 | } 1116 | }, 1117 | "string.prototype.trimend": { 1118 | "version": "1.0.3", 1119 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", 1120 | "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", 1121 | "dev": true, 1122 | "requires": { 1123 | "call-bind": "^1.0.0", 1124 | "define-properties": "^1.1.3" 1125 | } 1126 | }, 1127 | "string.prototype.trimstart": { 1128 | "version": "1.0.3", 1129 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", 1130 | "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", 1131 | "dev": true, 1132 | "requires": { 1133 | "call-bind": "^1.0.0", 1134 | "define-properties": "^1.1.3" 1135 | } 1136 | }, 1137 | "string_decoder": { 1138 | "version": "1.3.0", 1139 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1140 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1141 | "requires": { 1142 | "safe-buffer": "~5.2.0" 1143 | }, 1144 | "dependencies": { 1145 | "safe-buffer": { 1146 | "version": "5.2.1", 1147 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1148 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1149 | } 1150 | } 1151 | }, 1152 | "strip-ansi": { 1153 | "version": "3.0.1", 1154 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1155 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1156 | "requires": { 1157 | "ansi-regex": "^2.0.0" 1158 | } 1159 | }, 1160 | "tape": { 1161 | "version": "5.1.1", 1162 | "resolved": "https://registry.npmjs.org/tape/-/tape-5.1.1.tgz", 1163 | "integrity": "sha512-ujhT+ZJPqSGY9Le02mIGBnyWo7Ks05FEGS9PnlqECr3sM3KyV4CSCXAvSBJKMN+t+aZYLKEFUEo0l4wFJMhppQ==", 1164 | "dev": true, 1165 | "requires": { 1166 | "call-bind": "^1.0.0", 1167 | "deep-equal": "^2.0.5", 1168 | "defined": "^1.0.0", 1169 | "dotignore": "^0.1.2", 1170 | "for-each": "^0.3.3", 1171 | "glob": "^7.1.6", 1172 | "has": "^1.0.3", 1173 | "inherits": "^2.0.4", 1174 | "is-regex": "^1.1.1", 1175 | "minimist": "^1.2.5", 1176 | "object-inspect": "^1.9.0", 1177 | "object-is": "^1.1.4", 1178 | "object.assign": "^4.1.2", 1179 | "resolve": "^1.19.0", 1180 | "resumer": "^0.0.0", 1181 | "string.prototype.trim": "^1.2.3", 1182 | "through": "^2.3.8" 1183 | }, 1184 | "dependencies": { 1185 | "inherits": { 1186 | "version": "2.0.4", 1187 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1188 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1189 | "dev": true 1190 | } 1191 | } 1192 | }, 1193 | "tar": { 1194 | "version": "4.4.4", 1195 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", 1196 | "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", 1197 | "requires": { 1198 | "chownr": "^1.0.1", 1199 | "fs-minipass": "^1.2.5", 1200 | "minipass": "^2.3.3", 1201 | "minizlib": "^1.1.0", 1202 | "mkdirp": "^0.5.0", 1203 | "safe-buffer": "^5.1.2", 1204 | "yallist": "^3.0.2" 1205 | }, 1206 | "dependencies": { 1207 | "safe-buffer": { 1208 | "version": "5.1.2", 1209 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1210 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1211 | } 1212 | } 1213 | }, 1214 | "through": { 1215 | "version": "2.3.8", 1216 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1217 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1218 | "dev": true 1219 | }, 1220 | "tr46": { 1221 | "version": "0.0.3", 1222 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1223 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 1224 | }, 1225 | "universalify": { 1226 | "version": "0.1.2", 1227 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 1228 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 1229 | }, 1230 | "url": { 1231 | "version": "0.10.3", 1232 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 1233 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 1234 | "dev": true, 1235 | "requires": { 1236 | "punycode": "1.3.2", 1237 | "querystring": "0.2.0" 1238 | } 1239 | }, 1240 | "util-deprecate": { 1241 | "version": "1.0.2", 1242 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1243 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1244 | }, 1245 | "uuid": { 1246 | "version": "3.3.2", 1247 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 1248 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 1249 | "dev": true 1250 | }, 1251 | "webidl-conversions": { 1252 | "version": "3.0.1", 1253 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1254 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1255 | }, 1256 | "whatwg-url": { 1257 | "version": "5.0.0", 1258 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1259 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1260 | "requires": { 1261 | "tr46": "~0.0.3", 1262 | "webidl-conversions": "^3.0.0" 1263 | } 1264 | }, 1265 | "which-boxed-primitive": { 1266 | "version": "1.0.2", 1267 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 1268 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 1269 | "dev": true, 1270 | "requires": { 1271 | "is-bigint": "^1.0.1", 1272 | "is-boolean-object": "^1.1.0", 1273 | "is-number-object": "^1.0.4", 1274 | "is-string": "^1.0.5", 1275 | "is-symbol": "^1.0.3" 1276 | } 1277 | }, 1278 | "which-collection": { 1279 | "version": "1.0.1", 1280 | "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", 1281 | "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", 1282 | "dev": true, 1283 | "requires": { 1284 | "is-map": "^2.0.1", 1285 | "is-set": "^2.0.1", 1286 | "is-weakmap": "^2.0.1", 1287 | "is-weakset": "^2.0.1" 1288 | } 1289 | }, 1290 | "which-typed-array": { 1291 | "version": "1.1.4", 1292 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", 1293 | "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", 1294 | "dev": true, 1295 | "requires": { 1296 | "available-typed-arrays": "^1.0.2", 1297 | "call-bind": "^1.0.0", 1298 | "es-abstract": "^1.18.0-next.1", 1299 | "foreach": "^2.0.5", 1300 | "function-bind": "^1.1.1", 1301 | "has-symbols": "^1.0.1", 1302 | "is-typed-array": "^1.1.3" 1303 | } 1304 | }, 1305 | "wide-align": { 1306 | "version": "1.1.2", 1307 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", 1308 | "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", 1309 | "requires": { 1310 | "string-width": "^1.0.2" 1311 | } 1312 | }, 1313 | "wrappy": { 1314 | "version": "1.0.2", 1315 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1316 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1317 | }, 1318 | "xml2js": { 1319 | "version": "0.4.19", 1320 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 1321 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 1322 | "dev": true, 1323 | "requires": { 1324 | "sax": ">=0.6.0", 1325 | "xmlbuilder": "~9.0.1" 1326 | } 1327 | }, 1328 | "xmlbuilder": { 1329 | "version": "9.0.7", 1330 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 1331 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 1332 | "dev": true 1333 | }, 1334 | "yallist": { 1335 | "version": "3.0.2", 1336 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", 1337 | "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" 1338 | } 1339 | } 1340 | } 1341 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/node-cpp-skel", 3 | "version": "0.2.0", 4 | "description": "Skeleton for bindings to C++ libraries for Node.js using N-API (node-addon-api)", 5 | "url": "http://github.com/mapbox/node-cpp-skel", 6 | "main": "./lib/index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:mapbox/node-cpp-skel.git" 10 | }, 11 | "scripts": { 12 | "test": "tape test/*.test.js", 13 | "install": "node-pre-gyp install --fallback-to-build", 14 | "docs": "documentation build ./lib/index.js -f md > API.md" 15 | }, 16 | "author": "Mapbox", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@mapbox/mason-js": "^0.1.5", 20 | "@mapbox/node-pre-gyp": "^1.0.8", 21 | "node-addon-api": "^4.3.0" 22 | }, 23 | "devDependencies": { 24 | "aws-sdk": "^2.840.0", 25 | "bytes": "^3.1.0", 26 | "d3-queue": "^3.0.7", 27 | "minimist": "^1.2.5", 28 | "tape": "^5.1.1" 29 | }, 30 | "binary": { 31 | "module_name": "module", 32 | "module_path": "./lib/binding/", 33 | "host": "https://mapbox-node-binary.s3.amazonaws.com", 34 | "remote_path": "./{name}/v{version}/{configuration}/{toolset}/", 35 | "package_name": "{platform}-{arch}.tar.gz" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | : ' 7 | 8 | Runs clang-format on the code in src/ 9 | 10 | Return `1` if there are files to be formatted, and automatically formats them. 11 | 12 | Returns `0` if everything looks properly formatted. 13 | 14 | ' 15 | 16 | PATH_TO_FORMAT_SCRIPT="$(pwd)/mason_packages/.link/bin/clang-format" 17 | 18 | # Run clang-format on all cpp and hpp files in the /src directory 19 | find src/ -type f -name '*.hpp' -o -name '*.cpp' \ 20 | | xargs -I{} ${PATH_TO_FORMAT_SCRIPT} -i -style=file {} 21 | 22 | # Print list of modified files 23 | dirty=$(git ls-files --modified src/) 24 | 25 | if [[ $dirty ]]; then 26 | echo "The following files have been modified:" 27 | echo $dirty 28 | git diff 29 | exit 1 30 | else 31 | exit 0 32 | fi -------------------------------------------------------------------------------- /scripts/clang-tidy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | # https://clang.llvm.org/extra/clang-tidy/ 7 | 8 | : ' 9 | Runs clang-tidy on the code in src/ 10 | Return `1` if there are files automatically fixed by clang-tidy. 11 | Returns `0` if no fixes by clang-tidy. 12 | TODO: should also return non-zero if clang-tidy emits warnings 13 | or errors about things it cannot automatically fix. However I cannot 14 | figure out how to get this working yet as it seems that clang-tidy 15 | always returns 0 even on errors. 16 | ' 17 | 18 | PATH_TO_CLANG_TIDY_SCRIPT="$(pwd)/mason_packages/.link/share/run-clang-tidy.py" 19 | # make sure that run-clang-tidy.py can find the right clang-tidy 20 | export PATH=$(pwd)/mason_packages/.link/bin:${PATH} 21 | 22 | # build the compile_commands.json file if it does not exist 23 | if [[ ! -f build/compile_commands.json ]]; then 24 | # We need to clean otherwise when we make the project 25 | # will will not see all the compile commands 26 | make clean 27 | # Create the build directory to put the compile_commands in 28 | # We do this first to ensure it is there to start writing to 29 | # immediately (make make not create it right away) 30 | mkdir -p build 31 | # Run make, pipe the output to the generate_compile_commands.py 32 | # and drop them in a place that clang-tidy will automatically find them 33 | RESULT=0 34 | make | tee /tmp/make-node-cpp-skel-build-output.txt || RESULT=$? 35 | if [[ ${RESULT} != 0 ]]; then 36 | echo "Build failed, could not generate compile commands for clang-tidy, aborting!" 37 | exit ${RESULT} 38 | else 39 | cat /tmp/make-node-cpp-skel-build-output.txt | scripts/generate_compile_commands.py > build/compile_commands.json 40 | fi 41 | 42 | fi 43 | 44 | # change into the build directory so that clang-tidy can find the files 45 | # at the right paths (since this is where the actual build happens) 46 | cd build 47 | ${PATH_TO_CLANG_TIDY_SCRIPT} -fix 48 | cd ../ 49 | 50 | # Print list of modified files 51 | dirty=$(git ls-files --modified src/) 52 | 53 | if [[ $dirty ]]; then 54 | echo "The following files have been modified:" 55 | echo $dirty 56 | git diff 57 | exit 1 58 | else 59 | exit 0 60 | fi 61 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | # http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation 7 | # https://www.bignerdranch.com/blog/weve-got-you-covered/ 8 | 9 | make clean 10 | export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" 11 | export LDFLAGS="-fprofile-instr-generate" 12 | make debug 13 | rm -f *profraw 14 | rm -f *gcov 15 | rm -f *profdata 16 | LLVM_PROFILE_FILE="code-%p.profraw" npm test 17 | CXX_MODULE=$(./node_modules/.bin/node-pre-gyp reveal module --silent) 18 | export PATH=$(pwd)/mason_packages/.link/bin/:${PATH} 19 | llvm-profdata merge -output=code.profdata code-*.profraw 20 | llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color 21 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color 22 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html 23 | echo "open /tmp/coverage.html for HTML version of this report" 24 | -------------------------------------------------------------------------------- /scripts/create_scheme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | CONTAINER=build/binding.xcodeproj 7 | OUTPUT="${CONTAINER}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme" 8 | 9 | # Required ENV vars: 10 | # - SCHEME_TYPE: type of the scheme 11 | # - SCHEME_NAME: name of the scheme 12 | 13 | # Optional ENV vars: 14 | # - NODE_ARGUMENT (defaults to "") 15 | # - BUILDABLE_NAME (defaults ot SCHEME_NAME) 16 | # - BLUEPRINT_NAME (defaults ot SCHEME_NAME) 17 | 18 | 19 | # Try to reuse the existing Blueprint ID if the scheme already exists. 20 | if [ -f "${OUTPUT}" ]; then 21 | BLUEPRINT_ID=$(sed -n "s/[ \t]*BlueprintIdentifier *= *\"\([A-Z0-9]\{24\}\)\"/\\1/p" "${OUTPUT}" | head -1) 22 | fi 23 | 24 | NODE_ARGUMENT=${NODE_ARGUMENT:-} 25 | BLUEPRINT_ID=${BLUEPRINT_ID:-$(hexdump -n 12 -v -e '/1 "%02X"' /dev/urandom)} 26 | BUILDABLE_NAME=${BUILDABLE_NAME:-${SCHEME_NAME}} 27 | BLUEPRINT_NAME=${BLUEPRINT_NAME:-${SCHEME_NAME}} 28 | 29 | mkdir -p "${CONTAINER}/xcshareddata/xcschemes" 30 | 31 | sed "\ 32 | s#{{BLUEPRINT_ID}}#${BLUEPRINT_ID}#;\ 33 | s#{{BLUEPRINT_NAME}}#${BLUEPRINT_NAME}#;\ 34 | s#{{BUILDABLE_NAME}}#${BUILDABLE_NAME}#;\ 35 | s#{{CONTAINER}}#${CONTAINER}#;\ 36 | s#{{WORKING_DIRECTORY}}#$(pwd)#;\ 37 | s#{{NODE_PATH}}#$(dirname `which node`)#;\ 38 | s#{{NODE_ARGUMENT}}#${NODE_ARGUMENT}#" \ 39 | scripts/${SCHEME_TYPE}.xcscheme > "${OUTPUT}" 40 | -------------------------------------------------------------------------------- /scripts/generate_compile_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import json 5 | import os 6 | import re 7 | 8 | # Script to generate compile_commands.json based on Makefile output 9 | # Works by accepting Makefile output from stdin, parsing it, and 10 | # turning into json records. These are then printed to stdout. 11 | # More details on the compile_commands format at: 12 | # https://clang.llvm.org/docs/JSONCompilationDatabase.html 13 | # 14 | # Note: make must be run in verbose mode, e.g. V=1 make or VERBOSE=1 make 15 | # 16 | # Usage with node-cpp-skel: 17 | # 18 | # make | ./scripts/generate_compile_commands.py > build/compile_commands.json 19 | 20 | # These work for node-cpp-skel to detect the files being compiled 21 | # They may need to be modified if you adapt this to another tool 22 | matcher = re.compile('^(.*) (.+cpp)\n') 23 | build_dir = os.path.join(os.getcwd(),"build") 24 | TOKEN_DENOTING_COMPILED_FILE='NODE_GYP_MODULE_NAME' 25 | 26 | def generate(): 27 | compile_commands = [] 28 | for line in sys.stdin.readlines(): 29 | if TOKEN_DENOTING_COMPILED_FILE in line: 30 | match = matcher.match(line) 31 | if match: 32 | compile_commands.append({ 33 | "directory": build_dir, 34 | "command": line.strip(), 35 | "file": os.path.normpath(os.path.join(build_dir,match.group(2))) 36 | }) 37 | print(json.dumps(compile_commands,indent=4)) 38 | 39 | if __name__ == '__main__': 40 | generate() 41 | -------------------------------------------------------------------------------- /scripts/library.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /scripts/liftoff.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # First create new repo on GitHub and copy the SSH repo url 6 | # Then run "./scripts/liftoff.sh" from within your local node-cpp-skel root directory 7 | # and it will create your new local project repo side by side with node-cpp-skel directory 8 | 9 | echo "What is the name of your new project? " 10 | read name 11 | echo "What is the remote repo url for your new project? " 12 | read url 13 | 14 | mkdir ../$name 15 | cp -R ../node-cpp-skel/. ../$name/ 16 | cd ../$name/ 17 | rm -rf .git 18 | git init 19 | 20 | git checkout -b node-cpp-skel-port 21 | git add . 22 | git commit -m "Port from node-cpp-skel" 23 | git remote add origin $url 24 | git push -u origin node-cpp-skel-port 25 | 26 | # Perhaps useful for fresh start, also check out https://github.com/mapbox/node-cpp-skel#add-custom-code 27 | # cp /dev/null CHANGELOG.md 28 | # cp /dev/null README.md -------------------------------------------------------------------------------- /scripts/node.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 46 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 81 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | export COMMIT_MESSAGE=$(git log --format=%B --no-merges -n 1 | tr -d '\n') 7 | 8 | # `is_pr_merge` is designed to detect if a gitsha represents a normal 9 | # push commit (to any branch) or whether it represents travis attempting 10 | # to merge between the origin and the upstream branch. 11 | # For more details see: https://docs.travis-ci.com/user/pull-requests 12 | function is_pr_merge() { 13 | # Get the commit message via git log 14 | # This should always be the exactly the text the developer provided 15 | local COMMIT_LOG=${COMMIT_MESSAGE} 16 | 17 | # Get the commit message via git show 18 | # If the gitsha represents a merge then this will 19 | # look something like "Merge e3b1981 into 615d2a3" 20 | # Otherwise it will be the same as the "git log" output 21 | export COMMIT_SHOW=$(git show -s --format=%B | tr -d '\n') 22 | 23 | if [[ "${COMMIT_LOG}" != "${COMMIT_SHOW}" ]]; then 24 | echo true 25 | fi 26 | } 27 | 28 | # Detect if this commit represents a tag. This is useful 29 | # to detect if we are on a travis job that is running due to 30 | # "git tags --push". In this case we don't want to publish even 31 | # if [publish binary] is present since that should refer only to the 32 | # previously job that ran for that commit and not the tag made 33 | function is_tag_commit() { 34 | export COMMIT_MATCHES_KNOWN_TAG=$(git describe --exact-match $(git rev-parse HEAD) 2> /dev/null) 35 | if [[ ${COMMIT_MATCHES_KNOWN_TAG} ]]; then 36 | echo true 37 | fi 38 | } 39 | 40 | # `publish` is used to publish binaries to s3 via commit messages if: 41 | # - the commit message includes [publish binary] 42 | # - the commit message includes [republish binary] 43 | # - the commit is not a pr_merge (checked with `is_pr_merge` function) 44 | function publish() { 45 | echo "dumping binary meta..." 46 | ./node_modules/.bin/node-pre-gyp reveal --loglevel=error $@ 47 | 48 | echo "determining publishing status..." 49 | 50 | if [[ $(is_pr_merge) ]]; then 51 | echo "Skipping publishing because this is a PR merge commit" 52 | elif [[ $(is_tag_commit) ]]; then 53 | echo "Skipping publishing because this is a tag" 54 | else 55 | echo "Commit message was: '${COMMIT_MESSAGE}'" 56 | 57 | if [[ ${COMMIT_MESSAGE} =~ "[publish binary]" ]]; then 58 | echo "Publishing" 59 | ./node_modules/.bin/node-pre-gyp package publish $@ 60 | elif [[ ${COMMIT_MESSAGE} =~ "[republish binary]" ]]; then 61 | echo "Re-Publishing" 62 | ./node_modules/.bin/node-pre-gyp package unpublish publish $@ 63 | else 64 | echo "Skipping publishing since we did not detect either [publish binary] or [republish binary] in commit message" 65 | fi 66 | fi 67 | } 68 | 69 | function usage() { 70 | >&2 echo "Usage" 71 | >&2 echo "" 72 | >&2 echo "$ ./scripts/publish.sh " 73 | >&2 echo "" 74 | >&2 echo "All args are forwarded to node-pre-gyp like --debug" 75 | >&2 echo "" 76 | exit 1 77 | } 78 | 79 | # https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash 80 | for i in "$@" 81 | do 82 | case $i in 83 | -h | --help) 84 | usage 85 | shift 86 | ;; 87 | *) 88 | ;; 89 | esac 90 | done 91 | 92 | publish $@ 93 | -------------------------------------------------------------------------------- /scripts/sanitize.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | : ' 7 | 8 | Rebuilds the code with the sanitizers and runs the tests 9 | 10 | ' 11 | 12 | # See https://github.com/mapbox/node-cpp-skel/blob/master/docs/extended-tour.md#configuration-files 13 | 14 | make clean 15 | 16 | # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso 17 | SHARED_LIB_EXT=.so 18 | if [[ $(uname -s) == 'Darwin' ]]; then 19 | SHARED_LIB_EXT=.dylib 20 | fi 21 | 22 | export MASON_LLVM_RT_PRELOAD=$(pwd)/$(ls mason_packages/.link/lib/clang/*/lib/*/libclang_rt.asan*${SHARED_LIB_EXT}) 23 | SUPPRESSION_FILE="/tmp/leak_suppressions.txt" 24 | echo "leak:__strdup" > ${SUPPRESSION_FILE} 25 | echo "leak:v8::internal" >> ${SUPPRESSION_FILE} 26 | echo "leak:node::CreateEnvironment" >> ${SUPPRESSION_FILE} 27 | echo "leak:node::Start" >> ${SUPPRESSION_FILE} 28 | echo "leak:node::Init" >> ${SUPPRESSION_FILE} 29 | # Suppress leak related to https://github.com/libuv/libuv/pull/2480 30 | echo "leak:uv__set_process_title_platform_init" >> ${SUPPRESSION_FILE} 31 | export ASAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer 32 | export MSAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer 33 | export UBSAN_OPTIONS=print_stacktrace=1 34 | export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE} 35 | export ASAN_OPTIONS=detect_leaks=1:symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1 36 | export MASON_SANITIZE="-fsanitize=address,undefined,integer,leak -fno-sanitize=vptr,function" 37 | export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common" 38 | export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}" 39 | # Note: to build without stopping on errors remove the -fno-sanitize-recover=all flag 40 | # You might want to do this if there are multiple errors and you want to see them all before fixing 41 | export CXXFLAGS="${MASON_SANITIZE_CXXFLAGS} ${CXXFLAGS:-} -fno-sanitize-recover=all" 42 | export LDFLAGS="${MASON_SANITIZE_LDFLAGS} ${LDFLAGS:-}" 43 | make debug 44 | export ASAN_OPTIONS=fast_unwind_on_malloc=0:${ASAN_OPTIONS} 45 | if [[ $(uname -s) == 'Darwin' ]]; then 46 | # NOTE: we must call node directly here rather than `npm test` 47 | # because OS X blocks `DYLD_INSERT_LIBRARIES` being inherited by sub shells 48 | # If this is not done right we'll see 49 | # ==18464==ERROR: Interceptors are not working. This may be because AddressSanitizer is loaded too late (e.g. via dlopen). 50 | # 51 | # See https://github.com/mapbox/node-cpp-skel/issues/122 52 | DYLD_INSERT_LIBRARIES=${MASON_LLVM_RT_PRELOAD} \ 53 | node node_modules/.bin/$(node -e "console.log(require('./package.json').scripts.test)") test/*test.js 54 | else 55 | LD_PRELOAD=${MASON_LLVM_RT_PRELOAD} \ 56 | npm test 57 | fi 58 | -------------------------------------------------------------------------------- /src/cpu_intensive_task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace detail { 9 | 10 | // simulate CPU intensive task 11 | inline std::unique_ptr> do_expensive_work(std::string const& name, bool louder) 12 | { 13 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 14 | std::string str = "...threads are busy async bees...hello " + name; 15 | std::unique_ptr> result = std::make_unique>(str.begin(), str.end()); 16 | if (louder) 17 | { 18 | result->push_back('!'); 19 | result->push_back('!'); 20 | result->push_back('!'); 21 | result->push_back('!'); 22 | } 23 | return result; 24 | } 25 | 26 | } // namespace detail 27 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "object_async/hello_async.hpp" 2 | #include "object_sync/hello.hpp" 3 | #include "standalone/hello.hpp" 4 | #include "standalone_async/hello_async.hpp" 5 | #include "standalone_promise/hello_promise.hpp" 6 | #include 7 | // #include "your_code.hpp" 8 | 9 | Napi::Object init(Napi::Env env, Napi::Object exports) 10 | { 11 | // expose hello method 12 | exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, standalone::hello)); 13 | 14 | // expose helloAsync method 15 | exports.Set(Napi::String::New(env, "helloAsync"), Napi::Function::New(env, standalone_async::helloAsync)); 16 | 17 | // expose helloPromise method 18 | exports.Set(Napi::String::New(env, "helloPromise"), Napi::Function::New(env, standalone_promise::helloPromise)); 19 | 20 | // expose HelloObject class 21 | object_sync::HelloObject::Init(env, exports); 22 | 23 | // expose HelloObjectAsync class 24 | object_async::HelloObjectAsync::Init(env, exports); 25 | 26 | return exports; 27 | /** 28 | * You may have noticed there are multiple "hello" functions as part of this 29 | * module. 30 | * They are exposed a bit differently. 31 | * 1) standalone::hello // exposed above 32 | * 2) HelloObject.hello // exposed in object_sync/hello.cpp as part of 33 | * HelloObject 34 | */ 35 | 36 | // add more methods/classes below that youd like to use in Javascript world 37 | // then create a directory in /src with a .cpp and a .hpp file. 38 | // Include your .hpp file at the top of this file. 39 | } 40 | 41 | // Initialize the module (we only do this once) 42 | // Mark this NOLINT to avoid the clang-tidy checks 43 | // NODE_GYP_MODULE_NAME is the name of our module as defined in 'target_name' 44 | // variable in the 'binding.gyp', which is passed along as a compiler define 45 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, init) // NOLINT 46 | -------------------------------------------------------------------------------- /src/module_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace utils { 7 | 8 | /* 9 | * This is an internal function used to return callback error messages instead of 10 | * throwing errors. 11 | * Usage: 12 | * 13 | * return CallbackError(env, "error message", callback); 14 | * 15 | */ 16 | 17 | inline Napi::Value CallbackError(Napi::Env env, std::string const& message, Napi::Function const& func) 18 | { 19 | Napi::Object obj = Napi::Object::New(env); 20 | obj.Set("message", message); 21 | return func.Call({obj}); 22 | } 23 | 24 | } // namespace utils 25 | 26 | namespace gsl { 27 | template 28 | using owner = T; 29 | } // namespace gsl 30 | 31 | // ^^^ type alias required for clang-tidy (cppcoreguidelines-owning-memory) 32 | -------------------------------------------------------------------------------- /src/object_async/hello_async.cpp: -------------------------------------------------------------------------------- 1 | #include "hello_async.hpp" 2 | #include "../cpu_intensive_task.hpp" 3 | #include "../module_utils.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // If this was not defined within a namespace, it would be in the global scope. 12 | 13 | namespace object_async { 14 | 15 | /* 16 | struct AsyncHelloWorker : Napi::AsyncWorker 17 | { 18 | using Base = Napi::AsyncWorker; 19 | // ctor 20 | AsyncHelloWorker(bool louder, 21 | bool buffer, 22 | std::string name, 23 | Napi::Function const& cb) 24 | : Base(cb), 25 | louder_(louder), 26 | buffer_(buffer), 27 | name_(std::move(name)) {} 28 | 29 | // The Execute() function is getting called when the worker starts to run. 30 | // - You only have access to member variables stored in this worker. 31 | // - You do not have access to Javascript v8 objects here. 32 | void Execute() override 33 | { 34 | try 35 | { 36 | result_ = detail::do_expensive_work(name_, louder_); 37 | } 38 | catch (std::exception const& e) 39 | { 40 | SetError(e.what()); 41 | } 42 | } 43 | 44 | // The OnOK() is getting called when Execute() successfully 45 | // completed. 46 | // - In case Execute() invoked SetErrorMessage("") this function is not 47 | // getting called. 48 | // - You have access to Javascript v8 objects again 49 | // - You have to translate from C++ member variables to Javascript v8 objects 50 | // - Finally, you call the user's callback with your results 51 | void OnOK() override 52 | { 53 | if (!Callback().IsEmpty()) 54 | { 55 | if (buffer_) 56 | { 57 | char * data = result_->data(); 58 | std::size_t size = result_->size(); 59 | auto buffer = Napi::Buffer::New(Env(), 60 | data, 61 | size, 62 | [](Napi::Env, char*, gsl::owner*> v) { 63 | delete v; 64 | }, 65 | result_.release()); 66 | Callback().Call({Env().Null(), buffer}); 67 | } 68 | else 69 | { 70 | Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())}); 71 | } 72 | } 73 | } 74 | 75 | std::unique_ptr> result_ = nullptr; 76 | bool const louder_; 77 | bool const buffer_; 78 | std::string const name_; 79 | }; 80 | */ 81 | 82 | // This V2 worker is overriding `GetResult` to return the arguments 83 | // passed to the Callback invoked by the default OnOK() implementation. 84 | // Above is alternative implementation with OnOK() method calling 85 | // Callback with appropriate args. Both implementations use default OnError(). 86 | struct AsyncHelloWorker_v2 : Napi::AsyncWorker 87 | { 88 | using Base = Napi::AsyncWorker; 89 | // ctor 90 | AsyncHelloWorker_v2(bool louder, 91 | bool buffer, 92 | std::string name, 93 | Napi::Function const& cb) 94 | : Base(cb), 95 | louder_(louder), 96 | buffer_(buffer), 97 | name_(std::move(name)) {} 98 | 99 | // The Execute() function is getting called when the worker starts to run. 100 | // - You only have access to member variables stored in this worker. 101 | // - You do not have access to Javascript v8 objects here. 102 | void Execute() override 103 | { 104 | try 105 | { 106 | result_ = detail::do_expensive_work(name_, louder_); 107 | } 108 | catch (std::exception const& e) 109 | { 110 | SetError(e.what()); 111 | } 112 | } 113 | 114 | std::vector GetResult(Napi::Env env) override 115 | { 116 | if (result_) 117 | { 118 | if (buffer_) 119 | { 120 | char* data = result_->data(); 121 | std::size_t size = result_->size(); 122 | auto buffer = Napi::Buffer::New( 123 | env, 124 | data, 125 | size, 126 | [](Napi::Env /*unused*/, char* /*unused*/, gsl::owner*> v) { 127 | delete v; 128 | }, 129 | result_.release()); 130 | return {env.Null(), buffer}; 131 | } 132 | return {env.Null(), Napi::String::New(env, result_->data(), result_->size())}; 133 | } 134 | return Base::GetResult(env); // returns an empty vector (default) 135 | } 136 | 137 | std::unique_ptr> result_ = nullptr; 138 | bool const louder_; 139 | bool const buffer_; 140 | std::string const name_; 141 | }; 142 | 143 | Napi::FunctionReference HelloObjectAsync::constructor; // NOLINT 144 | 145 | HelloObjectAsync::HelloObjectAsync(Napi::CallbackInfo const& info) 146 | : Napi::ObjectWrap(info) 147 | { 148 | Napi::Env env = info.Env(); 149 | std::size_t length = info.Length(); 150 | if (length != 1 || !info[0].IsString()) 151 | { 152 | Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); 153 | return; 154 | } 155 | name_ = info[0].As(); 156 | // ^^ uses std::string() operator to convert to UTF-8 encoded string 157 | // alternatively Utf8Value() method can be used e.g 158 | // name_ = info[0].As().Utf8Value(); 159 | if (name_.empty()) 160 | { 161 | Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException(); 162 | } 163 | } 164 | 165 | Napi::Value HelloObjectAsync::helloAsync(Napi::CallbackInfo const& info) 166 | { 167 | bool louder = false; 168 | bool buffer = false; 169 | 170 | Napi::Env env = info.Env(); 171 | if (!(info.Length() == 2 && info[1].IsFunction())) 172 | { 173 | Napi::TypeError::New(env, "second arg 'callback' must be a function").ThrowAsJavaScriptException(); 174 | return env.Null(); 175 | } 176 | 177 | Napi::Function callback = info[1].As(); 178 | 179 | // Check first argument, should be an 'options' object 180 | if (!info[0].IsObject()) 181 | { 182 | return utils::CallbackError(env, "first arg 'options' must be an object", callback); 183 | } 184 | Napi::Object options = info[0].As(); 185 | 186 | // Check options object for the "louder" property, which should be a boolean 187 | // value 188 | if (options.Has(Napi::String::New(env, "louder"))) 189 | { 190 | Napi::Value louder_val = options.Get(Napi::String::New(env, "louder")); 191 | if (!louder_val.IsBoolean()) 192 | { 193 | return utils::CallbackError(env, "option 'louder' must be a boolean", callback); 194 | } 195 | louder = louder_val.As().Value(); 196 | } 197 | // Check options object for the "buffer" property, which should be a boolean 198 | // value 199 | if (options.Has(Napi::String::New(env, "buffer"))) 200 | { 201 | Napi::Value buffer_val = options.Get(Napi::String::New(env, "buffer")); 202 | if (!buffer_val.IsBoolean()) 203 | { 204 | return utils::CallbackError(env, "option 'buffer' must be a boolean", callback); 205 | } 206 | buffer = buffer_val.As().Value(); 207 | } 208 | 209 | auto* worker = new AsyncHelloWorker_v2{louder, buffer, name_, callback}; // NOLINT 210 | worker->Queue(); 211 | return info.Env().Undefined(); // NOLINT 212 | } 213 | 214 | Napi::Object HelloObjectAsync::Init(Napi::Env env, Napi::Object exports) 215 | { 216 | Napi::Function func = DefineClass(env, "HelloObjectAsync", {InstanceMethod("helloAsync", &HelloObjectAsync::helloAsync)}); 217 | // Create a peristent reference to the class constructor. This will allow 218 | // a function called on a class prototype and a function 219 | // called on instance of a class to be distinguished from each other. 220 | constructor = Napi::Persistent(func); 221 | // Call the SuppressDestruct() method on the static data prevent the calling 222 | // to this destructor to reset the reference when the environment is no longer 223 | // available. 224 | constructor.SuppressDestruct(); 225 | exports.Set("HelloObjectAsync", func); 226 | return exports; 227 | } 228 | 229 | } // namespace object_async 230 | -------------------------------------------------------------------------------- /src/object_async/hello_async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace object_async { 5 | 6 | /** 7 | * HelloObject class 8 | * This is in a header file so we can access it across other .cpp files if 9 | * necessary 10 | * Also, this class adheres to the rule of Zero because we define no custom 11 | * destructor or copy constructor 12 | */ 13 | class HelloObjectAsync : public Napi::ObjectWrap 14 | { 15 | public: 16 | // initializer 17 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 18 | explicit HelloObjectAsync(Napi::CallbackInfo const& info); 19 | Napi::Value helloAsync(Napi::CallbackInfo const& info); 20 | 21 | private: 22 | // member variable 23 | // specific to each instance of the class 24 | static Napi::FunctionReference constructor; 25 | std::string name_ = ""; 26 | }; 27 | } // namespace object_async 28 | -------------------------------------------------------------------------------- /src/object_sync/hello.cpp: -------------------------------------------------------------------------------- 1 | #include "hello.hpp" 2 | #include 3 | 4 | // If this was not defined within a namespace, it would be in the global scope. 5 | // Namespaces are used because C++ has no notion of scoped modules, so all of 6 | // the code you write in any file could conflict with other code. 7 | // Namespaces are generally a great idea in C++ because it helps scale and 8 | // clearly organize your application. 9 | namespace object_sync { 10 | 11 | Napi::FunctionReference HelloObject::constructor; // NOLINT 12 | 13 | // Triggered from Javascript world when calling "new HelloObject(name)" 14 | HelloObject::HelloObject(Napi::CallbackInfo const& info) 15 | : Napi::ObjectWrap(info) 16 | { 17 | Napi::Env env = info.Env(); 18 | std::size_t length = info.Length(); 19 | if (length != 1 || !info[0].IsString()) 20 | { 21 | Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); 22 | return; 23 | } 24 | name_ = info[0].As(); 25 | // ^^ uses std::string() operator to convert to UTF-8 encoded string 26 | // alternatively Utf8Value() method can be used e.g 27 | // name_ = info[0].As().Utf8Value(); 28 | if (name_.empty()) 29 | { 30 | Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException(); 31 | } 32 | } 33 | 34 | Napi::Value HelloObject::hello(Napi::CallbackInfo const& info) 35 | { 36 | Napi::Env env = info.Env(); 37 | return Napi::String::New(env, name_); 38 | } 39 | 40 | Napi::Object HelloObject::Init(Napi::Env env, Napi::Object exports) 41 | { 42 | 43 | Napi::Function func = DefineClass(env, "HelloObject", {InstanceMethod("helloMethod", &HelloObject::hello)}); 44 | // Create a peristent reference to the class constructor. This will allow 45 | // a function called on a class prototype and a function 46 | // called on instance of a class to be distinguished from each other. 47 | constructor = Napi::Persistent(func); 48 | // Call the SuppressDestruct() method on the static data prevent the calling 49 | // to this destructor to reset the reference when the environment is no longer 50 | // available. 51 | constructor.SuppressDestruct(); 52 | exports.Set("HelloObject", func); 53 | return exports; 54 | } 55 | 56 | } // namespace object_sync 57 | -------------------------------------------------------------------------------- /src/object_sync/hello.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace object_sync { 6 | 7 | /** 8 | * HelloObject class 9 | * This is in a header file so we can access it across other .cpp files if necessary 10 | * Also, this class adheres to the rule of Zero because we define no custom destructor or copy constructor 11 | */ 12 | class HelloObject : public Napi::ObjectWrap 13 | { 14 | public: 15 | // initializers 16 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 17 | explicit HelloObject(Napi::CallbackInfo const& info); 18 | Napi::Value hello(Napi::CallbackInfo const& info); 19 | 20 | private: 21 | static Napi::FunctionReference constructor; 22 | std::string name_ = ""; 23 | }; 24 | } // namespace object_sync 25 | -------------------------------------------------------------------------------- /src/standalone/hello.cpp: -------------------------------------------------------------------------------- 1 | #include "hello.hpp" 2 | 3 | namespace standalone { 4 | 5 | Napi::Value hello(Napi::CallbackInfo const& info) 6 | { 7 | Napi::Env env = info.Env(); 8 | return Napi::String::New(env, "hello world"); 9 | } 10 | 11 | } // namespace standalone 12 | -------------------------------------------------------------------------------- /src/standalone/hello.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace standalone { 5 | 6 | // hello, custom sync method 7 | Napi::Value hello(Napi::CallbackInfo const& info); 8 | 9 | } // namespace standalone 10 | -------------------------------------------------------------------------------- /src/standalone_async/hello_async.cpp: -------------------------------------------------------------------------------- 1 | #include "hello_async.hpp" 2 | #include "../cpu_intensive_task.hpp" 3 | #include "../module_utils.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace standalone_async { 12 | 13 | // This is the worker running asynchronously and calling a user-provided 14 | // callback when done. 15 | // Consider storing all C++ objects you need by value or by shared_ptr to keep 16 | // them alive until done. 17 | // Napi::AsyncWorker docs: 18 | // https://github.com/nodejs/node-addon-api/blob/master/doc/async_worker.md 19 | struct AsyncHelloWorker : Napi::AsyncWorker 20 | { 21 | using Base = Napi::AsyncWorker; 22 | // ctor 23 | AsyncHelloWorker(bool louder, bool buffer, Napi::Function const& cb) 24 | : Base(cb), 25 | louder_(louder), 26 | buffer_(buffer) {} 27 | 28 | // The Execute() function is getting called when the worker starts to run. 29 | // - You only have access to member variables stored in this worker. 30 | // - You do not have access to Javascript v8 objects here. 31 | void Execute() override 32 | { 33 | // The try/catch is critical here: if code was added that could throw an 34 | // unhandled error INSIDE the threadpool, it would be disasterous 35 | try 36 | { 37 | result_ = detail::do_expensive_work("world", louder_); 38 | } 39 | catch (std::exception const& e) 40 | { 41 | SetError(e.what()); 42 | } 43 | } 44 | 45 | // The OnOK() is getting called when Execute() successfully 46 | // completed. 47 | // - In case Execute() invoked SetErrorMessage("") this function is not 48 | // getting called. 49 | // - You have access to Javascript v8 objects again 50 | // - You have to translate from C++ member variables to Javascript v8 objects 51 | // - Finally, you call the user's callback with your results 52 | void OnOK() final 53 | { 54 | if (!Callback().IsEmpty() && result_) 55 | { 56 | if (buffer_) 57 | { 58 | char* data = result_->data(); 59 | std::size_t size = result_->size(); 60 | auto buffer = Napi::Buffer::New( 61 | Env(), 62 | data, 63 | size, 64 | [](Napi::Env /*unused*/, char* /*unused*/, gsl::owner*> v) { 65 | delete v; 66 | }, 67 | result_.release()); 68 | Callback().Call({Env().Null(), buffer}); 69 | } 70 | else 71 | { 72 | Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())}); 73 | } 74 | } 75 | } 76 | 77 | std::unique_ptr> result_ = nullptr; 78 | const bool louder_; 79 | const bool buffer_; 80 | }; 81 | 82 | // helloAsync is a "standalone function" because it's not a class. 83 | // If this function was not defined within a namespace ("standalone_async" 84 | // specified above), it would be in the global scope. 85 | Napi::Value helloAsync(Napi::CallbackInfo const& info) 86 | { 87 | bool louder = false; 88 | bool buffer = false; 89 | Napi::Env env = info.Env(); 90 | // Check second argument, should be a 'callback' function. 91 | if (!info[1].IsFunction()) 92 | { 93 | Napi::TypeError::New(env, "second arg 'callback' must be a function").ThrowAsJavaScriptException(); 94 | return env.Null(); 95 | } 96 | 97 | Napi::Function callback = info[1].As(); 98 | 99 | // Check first argument, should be an 'options' object 100 | if (!info[0].IsObject()) 101 | { 102 | return utils::CallbackError(env, "first arg 'options' must be an object", callback); 103 | } 104 | Napi::Object options = info[0].As(); 105 | 106 | // Check options object for the "louder" property, which should be a boolean 107 | // value 108 | 109 | if (options.Has(Napi::String::New(env, "louder"))) 110 | { 111 | Napi::Value louder_val = options.Get(Napi::String::New(env, "louder")); 112 | if (!louder_val.IsBoolean()) 113 | { 114 | return utils::CallbackError(env, "option 'louder' must be a boolean", callback); 115 | } 116 | louder = louder_val.As().Value(); 117 | } 118 | // Check options object for the "buffer" property, which should be a boolean value 119 | if (options.Has(Napi::String::New(env, "buffer"))) 120 | { 121 | Napi::Value buffer_val = options.Get(Napi::String::New(env, "buffer")); 122 | if (!buffer_val.IsBoolean()) 123 | { 124 | return utils::CallbackError(env, "option 'buffer' must be a boolean", callback); 125 | } 126 | buffer = buffer_val.As().Value(); 127 | } 128 | 129 | // Creates a worker instance and queues it to run asynchronously, invoking the 130 | // callback when done. 131 | // - Napi::AsyncWorker takes a pointer to a Napi::FunctionReference and deletes the 132 | // pointer automatically. 133 | // - Napi::AsyncQueueWorker takes a pointer to a Napi::AsyncWorker and deletes 134 | // the pointer automatically. 135 | auto* worker = new AsyncHelloWorker{louder, buffer, callback}; // NOLINT 136 | worker->Queue(); 137 | return env.Undefined(); // NOLINT 138 | } 139 | 140 | } // namespace standalone_async 141 | -------------------------------------------------------------------------------- /src/standalone_async/hello_async.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace standalone_async { 5 | 6 | // hello, custom sync method 7 | // method's logic lives in hello.cpp 8 | Napi::Value helloAsync(Napi::CallbackInfo const& info); 9 | 10 | } // namespace standalone_async 11 | -------------------------------------------------------------------------------- /src/standalone_promise/hello_promise.cpp: -------------------------------------------------------------------------------- 1 | #include "hello_promise.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace standalone_promise { 7 | 8 | // async worker that handles the Deferred methods 9 | struct PromiseWorker : Napi::AsyncWorker 10 | { 11 | // constructor / ctor 12 | PromiseWorker(Napi::Env const& env, std::string phrase, int multiply) 13 | : Napi::AsyncWorker(env), 14 | phrase_(std::move(phrase)), 15 | multiply_(multiply), 16 | deferred_(Napi::Promise::Deferred::New(env)) {} 17 | 18 | // The Execute() function is getting called when the worker starts to run. 19 | // - You only have access to member variables stored in this worker. 20 | // - You do not have access to Javascript v8 objects here. 21 | void Execute() override 22 | { 23 | for (int i = 0; i < multiply_; ++i) 24 | { 25 | output_ += phrase_; 26 | } 27 | } 28 | 29 | // The OnOK() is getting called when Execute() successfully 30 | // completed. 31 | // - In case Execute() invoked SetErrorMessage("") this function is not 32 | // getting called. 33 | // - You have access to Javascript v8 objects again 34 | // - You have to translate from C++ member variables to Javascript v8 objects 35 | // - Finally, you call the user's callback with your results 36 | void OnOK() final 37 | { 38 | deferred_.Resolve(Napi::String::New(Env(), output_)); 39 | } 40 | 41 | // If anything in the PromiseWorker.Execute method throws an error 42 | // it will be caught here and rejected. 43 | void OnError(Napi::Error const& error) override 44 | { 45 | deferred_.Reject(error.Value()); 46 | } 47 | 48 | Napi::Promise GetPromise() const 49 | { 50 | return deferred_.Promise(); 51 | } 52 | 53 | const std::string phrase_; 54 | const int multiply_; 55 | Napi::Promise::Deferred deferred_; 56 | std::string output_ = ""; 57 | }; 58 | 59 | // entry point 60 | Napi::Value helloPromise(Napi::CallbackInfo const& info) 61 | { 62 | Napi::Env env = info.Env(); 63 | 64 | // default params 65 | std::string phrase = "hello"; 66 | int multiply = 1; 67 | 68 | // validate inputs 69 | // - if params is defined, validate contents 70 | // - - params is an object 71 | // - - params.multiply is int and greater than zero 72 | // - - params.phrase is string 73 | // - otherwise skip and use defaults 74 | if (!info[0].IsUndefined()) 75 | { 76 | if (!info[0].IsObject()) 77 | { 78 | throw Napi::Error::New(env, "options must be an object"); 79 | } 80 | Napi::Object options = info[0].As(); 81 | 82 | // phrase must be a string 83 | if (options.Has(Napi::String::New(env, "phrase"))) 84 | { 85 | Napi::Value phrase_val = options.Get(Napi::String::New(env, "phrase")); 86 | if (!phrase_val.IsString()) 87 | { 88 | throw Napi::Error::New(env, "options.phrase must be a string"); 89 | } 90 | phrase = phrase_val.As(); 91 | } 92 | 93 | // multiply must be int > 1 94 | if (options.Has(Napi::String::New(env, "multiply"))) 95 | { 96 | Napi::Value multiply_val = options.Get(Napi::String::New(env, "multiply")); 97 | if (!multiply_val.IsNumber()) 98 | { 99 | throw Napi::Error::New(env, "options.multiply must be a number"); 100 | } 101 | multiply = multiply_val.As().Int32Value(); 102 | if (multiply < 1) 103 | { 104 | throw Napi::Error::New(env, "options.multiply must be 1 or greater"); 105 | } 106 | } 107 | } 108 | 109 | // initialize Napi::AsyncWorker 110 | // This comes with a GetPromise that returns the necessary 111 | // Napi::Promise::Deferred class that we send the user 112 | auto* worker = new PromiseWorker{env, phrase, multiply}; 113 | auto promise = worker->GetPromise(); 114 | 115 | // begin asynchronous work by queueing it. 116 | // https://github.com/nodejs/node-addon-api/blob/main/doc/async_worker.md#queue 117 | worker->Queue(); 118 | 119 | // return the deferred promise to the user. Let the 120 | // async worker resolve/reject accordingly 121 | return promise; 122 | } 123 | 124 | } // namespace standalone_promise -------------------------------------------------------------------------------- /src/standalone_promise/hello_promise.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace standalone_promise { 5 | 6 | // hello, custom sync method 7 | // method's logic lives in hello_promise.cpp 8 | Napi::Value helloPromise(Napi::CallbackInfo const& info); 9 | 10 | } // namespace standalone_promise 11 | -------------------------------------------------------------------------------- /test/hello.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var module = require('../lib/index.js'); 3 | 4 | test('prints world', function(t) { 5 | var check = module.hello(); 6 | t.equal(check, 'hello world', 'returned world'); 7 | t.end(); 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /test/hello_async.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var module = require('../lib/index.js'); 3 | 4 | test('success: prints loud busy world', function(t) { 5 | module.helloAsync({ louder: true }, function(err, result) { 6 | if (err) throw err; 7 | t.equal(result, '...threads are busy async bees...hello world!!!!'); 8 | t.end(); 9 | }); 10 | }); 11 | 12 | test('success: prints regular busy world', function(t) { 13 | module.helloAsync({ louder: false }, function(err, result) { 14 | if (err) throw err; 15 | t.equal(result, '...threads are busy async bees...hello world'); 16 | t.end(); 17 | }); 18 | }); 19 | 20 | test('success: buffer regular busy world', function(t) { 21 | module.helloAsync({ buffer: true }, function(err, result) { 22 | if (err) throw err; 23 | t.equal(result.length, 44); 24 | t.equal(typeof(result), 'object'); 25 | t.equal(result.toString(), '...threads are busy async bees...hello world'); 26 | t.end(); 27 | }); 28 | }); 29 | 30 | test('error: handles invalid louder value', function(t) { 31 | module.helloAsync({ louder: 'oops' }, function(err, result) { 32 | t.ok(err, 'expected error'); 33 | t.ok(err.message.indexOf('option \'louder\' must be a boolean') > -1, 'expected error message'); 34 | t.end(); 35 | }); 36 | }); 37 | 38 | test('error: handles invalid buffer value', function(t) { 39 | module.helloAsync({ buffer: 'oops' }, function(err, result) { 40 | t.ok(err, 'expected error'); 41 | t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message'); 42 | t.end(); 43 | }); 44 | }); 45 | 46 | test('error: handles invalid options value', function(t) { 47 | module.helloAsync('oops', function(err, result) { 48 | t.ok(err, 'expected error'); 49 | t.ok(err.message.indexOf('first arg \'options\' must be an object') > -1, 'expected error message'); 50 | t.end(); 51 | }); 52 | }); 53 | 54 | test('error: handles missing callback', function(t) { 55 | try { 56 | module.helloAsync({ louder: 'oops' }, {}); 57 | } catch (err) { 58 | t.ok(err, 'expected error'); 59 | t.ok(err.message.indexOf('second arg \'callback\' must be a function') > -1, 'expected error message'); 60 | t.end(); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /test/hello_object.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var module = require('../lib/index.js'); 3 | 4 | test('success: prints expected string', function(t) { 5 | var H = new module.HelloObject('carol'); 6 | var check = H.helloMethod(); 7 | t.equal(check, 'carol', 'returned expected string'); 8 | t.end(); 9 | }); 10 | 11 | test('error: throws when passing empty string', function(t) { 12 | try { 13 | var H = new module.HelloObject(''); 14 | } catch(err) { 15 | t.ok(err, 'expected error'); 16 | t.equal(err.message, 'arg must be a non-empty string', 'expected error message'); 17 | t.end(); 18 | } 19 | }); 20 | 21 | test('error: throws when missing "new"', function(t) { 22 | try { 23 | var H = module.HelloObject(); 24 | } catch(err) { 25 | t.ok(err); 26 | t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message'); 27 | t.end(); 28 | } 29 | }); 30 | 31 | test('error: handles non-string arg within constructor', function(t) { 32 | try { 33 | var H = new module.HelloObject(24); 34 | } catch(err) { 35 | t.ok(err, 'expected error'); 36 | t.ok(err.message, 'A string was expected', 'expected error message'); 37 | t.end(); 38 | } 39 | }); 40 | 41 | test('error: handles missing arg', function(t) { 42 | try { 43 | var H = new module.HelloObject(); 44 | } catch (err) { 45 | t.ok(err, 'expected error'); 46 | t.ok(err.message, 'must provide string arg', 'expected error message'); 47 | t.end(); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /test/hello_object_async.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var module = require('../lib/index.js'); 3 | 4 | test('success: prints expected string via custom constructor', function(t) { 5 | var H = new module.HelloObjectAsync('carol'); 6 | H.helloAsync({ louder: false }, function(err, result) { 7 | if (err) throw err; 8 | t.equal(result, '...threads are busy async bees...hello carol'); 9 | t.end(); 10 | }); 11 | }); 12 | 13 | test('success: prints loud busy world', function(t) { 14 | var H = new module.HelloObjectAsync('world'); 15 | H.helloAsync({ louder: true }, function(err, result) { 16 | if (err) throw err; 17 | t.equal(result, '...threads are busy async bees...hello world!!!!'); 18 | t.end(); 19 | }); 20 | }); 21 | 22 | test('success: return buffer busy world', function(t) { 23 | var H = new module.HelloObjectAsync('world'); 24 | H.helloAsync({ buffer: true }, function(err, result) { 25 | if (err) throw err; 26 | t.equal(result.length, 44); 27 | t.equal(typeof(result), 'object'); 28 | t.equal(result.toString(), '...threads are busy async bees...hello world'); 29 | t.end(); 30 | }); 31 | }); 32 | 33 | test('error: throws when passing empty string', function(t) { 34 | try { 35 | var H = new module.HelloObjectAsync(''); 36 | } catch(err) { 37 | t.ok(err, 'expected error'); 38 | t.equal(err.message, 'arg must be a non-empty string', 'expected error message') 39 | t.end(); 40 | } 41 | }); 42 | 43 | test('error: throws when missing "new"', function(t) { 44 | try { 45 | var H = module.HelloObjectAsync('world'); 46 | } catch(err) { 47 | t.ok(err, 'expected error'); 48 | t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message') 49 | t.end(); 50 | } 51 | }); 52 | 53 | test('error: handles non-string arg within constructor', function(t) { 54 | try { 55 | var H = new module.HelloObjectAsync(24); 56 | } catch(err) { 57 | console.log(err.message); 58 | t.equal(err.message, 'String expected', 'expected error message'); 59 | t.end(); 60 | } 61 | }); 62 | 63 | test('error: handles invalid louder value', function(t) { 64 | var H = new module.HelloObjectAsync('world'); 65 | H.helloAsync({ louder: 'oops' }, function(err, result) { 66 | t.ok(err, 'expected error'); 67 | t.ok(err.message.indexOf('option \'louder\' must be a boolean') > -1, 'expected error message'); 68 | t.end(); 69 | }); 70 | }); 71 | 72 | test('error: handles invalid buffer value', function(t) { 73 | var H = new module.HelloObjectAsync('world'); 74 | H.helloAsync({ buffer: 'oops' }, function(err, result) { 75 | t.ok(err, 'expected error'); 76 | t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message'); 77 | t.end(); 78 | }); 79 | }); 80 | 81 | test('error: handles invalid options value', function(t) { 82 | var H = new module.HelloObjectAsync('world'); 83 | H.helloAsync('oops', function(err, result) { 84 | t.ok(err, 'expected error'); 85 | t.ok(err.message.indexOf('first arg \'options\' must be an object') > -1, 'expected error message'); 86 | t.end(); 87 | }); 88 | }); 89 | 90 | test('error: handles missing callback', function(t) { 91 | var H = new module.HelloObjectAsync('world'); 92 | try { 93 | H.helloAsync({ louder: false}, {}); 94 | } catch (err) { 95 | t.ok(err, 'expected error'); 96 | t.ok(err.message.indexOf('second arg \'callback\' must be a function') > -1, 'expected error message'); 97 | t.end(); 98 | } 99 | }); 100 | 101 | test('error: handles missing arg', function(t) { 102 | try { 103 | var H = new module.HelloObjectAsync(); 104 | } catch (err) { 105 | t.ok(err, 'expected error'); 106 | t.equal(err.message, 'String expected', 'expected error message'); 107 | t.end(); 108 | } 109 | }); 110 | -------------------------------------------------------------------------------- /test/hello_promise.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const { helloPromise } = require('../lib/index.js'); 5 | 6 | test('success: no options object', async (assert) => { 7 | const result = await helloPromise(); 8 | assert.equal(result, 'hello'); 9 | assert.end(); 10 | }); 11 | 12 | test('success: empty options object', async (assert) => { 13 | const result = await helloPromise({}); 14 | assert.equal(result, 'hello'); 15 | assert.end(); 16 | }); 17 | 18 | test('success: options.phrase', async (assert) => { 19 | const result = await helloPromise({ phrase: 'Waka' }); 20 | assert.equal(result, 'Waka'); 21 | assert.end(); 22 | }); 23 | 24 | test('success: options.multiply', async (assert) => { 25 | const result = await helloPromise({ multiply: 4 }); 26 | assert.equal(result, 'hellohellohellohello'); 27 | assert.end(); 28 | }); 29 | 30 | test('success: options.phrase and options.multiply', async (assert) => { 31 | const result = await helloPromise({ phrase: 'Waka', multiply: 5 }); 32 | assert.equal(result, 'WakaWakaWakaWakaWaka'); 33 | assert.end(); 34 | }); 35 | 36 | test('error: invalid options type', async (assert) => { 37 | try { 38 | await helloPromise('not an object'); 39 | assert.fail(); 40 | } catch (err) { 41 | assert.ok(err); 42 | assert.equal(err.message, 'options must be an object'); 43 | } 44 | assert.end(); 45 | }); 46 | 47 | test('error: invalid options.phrase', async (assert) => { 48 | try { 49 | await helloPromise({ phrase: 10 }); 50 | assert.fail(); 51 | } catch (err) { 52 | assert.ok(err); 53 | assert.equal(err.message, 'options.phrase must be a string'); 54 | } 55 | assert.end(); 56 | }); 57 | 58 | test('error: invalid options.multiply', async (assert) => { 59 | try { 60 | await helloPromise({ multiply: 'not a number' }); 61 | assert.fail(); 62 | } catch (err) { 63 | assert.ok(err); 64 | assert.equal(err.message, 'options.multiply must be a number'); 65 | } 66 | assert.end(); 67 | }); --------------------------------------------------------------------------------