text:
# Workflow

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.

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).

The typical workflow for a regular node module may look something like this:

1. merge to master
2. git tag
3. npm publish (Now it's ready to be npm installed)

The workflow for a node add-on looks very similar:

1. merge to master
2. git tag
3. publish binaries
4. npm publish

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.

[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.

Continue reading below to learn how to publish your binaries to s3 so they're ready to be installed.

# Publishing Binaries

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.

#### 1) In the `package.json`, update the `"binary"` field to the appropriate s3 bucket `host`.

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/<your module name>`.

Note: for namespaced modules the path will end up being `s3://mapbox-node-binary/@org/<your module name>`.

#### 2) Copy the ci.template.js

Copy the `ci.template.js` from this repo into your repo and place it at  `./cloudformation/ci.template.js`

#### 3) Install deps for validating and managing cloudformation templates

```bash
npm install -g @mapbox/cfn-config # deploying stacks
npm install -g @mapbox/cloudfriend # validating and building templates
```

#### 4) Create a user with permissions to upload to `s3://<bucket name>/<your module name>/`

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.

Then run:

```bash
validate-template cloudformation/ci.template.js
build-template cloudformation/ci.template.js > cloudformation/ci.template
```

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:

  - Create a `ci-binary-publish` user
  - Using the `cloudformation/ci.template`
  - 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)

Now, run the command to create the user:

```
cfn-config create ci-binary-publish cloudformation/ci.template -c cfn-configs
```

It will prompt you, choose:

  - New configuration
  - Ready to create the stack? Y

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.

#### 5) Get the user keys

After the create step succeeds you will have a new user. You now need to get get the users `AccessKeyId` and `SecretAccessKey`.

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

**Tokens via cfn-config**

Run the command `cfn-config info ci-binary-publish` and you'll see a JSON output with `AccessKeyId` and `SecretAccessKey`.

**Tokens via the AWS console**

 - Go to https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks?filter=active&tab=outputs
 - Search for `ci-binary-publish`. You should see a stack called `<your module name>-ci-binary-publish`
 - Click the checkbox beside your `<your module name>-ci-binary-publish` stack
 - Click the `Output` tab to access the `AccessKeyId` and `SecretAccessKey` for this new user.

#### 6) Add the keys to the travis

 **Adding to travis UI settings**

 - Go to https://travis-ci.org/<your user or org>/<your module>/settings
 - Scroll to the bottom and find the `Environment Variables` section
 - Add a variable called `AWS_ACCESS_KEY_ID` and put the value of the `AccessKeyId` in it
 - CRITICAL: Choose `OFF` for `Display value in build log` to ensure the variables are not shown in the logs
 - Click `Add`
 - Add a variable called `AWS_SECRET_ACCESS_KEY` and put the value of the `SecretAccessKey` in it
 - CRITICAL: Choose `OFF` for `Display value in build log` to ensure the variables are not shown in the logs
 - Click `Add`

#### 7) All done!

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.

**Publishing on Travis CI**

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:

```
git commit -m 'releasing 0.1.0 [publish binary]'
```

Republishing a binary overrides the current version and must be specified with `[republish binary]`.

**Adding new operating systems and node versions**

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:

```yaml
- os: {operating system}
  env: NODE="{your node version}" TARGET="Release"
  install: *setup
  script: *test
  after_script: *publish
```

### Dev releases

You may want to test your module works correctly, in downstream dependencies, before formally publishing. To do this we recommend you:

1. Create a branch of your node c++ module

2. Modify the `version` string in your `package.json` like:

```diff
diff --git a/package.json b/package.json
index e00b7b5..22f7cd9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@mapbox/node-cpp-skel",
-  "version": "0.1.0",
+  "version": "0.1.0-alpha",
   "description": "Skeleton for bindings to C++ libraries for Node.js using NAN",
   "url": "http://github.com/mapbox/node-cpp-skel",
   "main": "./lib/index.js",
```

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

4. **Option A)** Require your module in downstream applications like:

```js
"your-module": "https://github.com/<your-org>/<your-module>/tarball/<your-branch>",
```

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`.

**Option B)** Issue a npm dev release after the binary is published.

Run `npm publish --tag dev` and then require your module in downstream applications like:

```js
"your-module": "0.1.0-alpha",
```

For npm dev releases, it’s good to use the `--tag <something>` to avoid publishing to the latest tag. If you run `npm publish` then `0.1.0-alpha` is what anyone running `npm install <your module> --save` will receive.

#### Before `npm publish`

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.

Take a peek at what npm will publish by running:

```
make
make testpack
```

This will create a tarball locally and print every file included. A couple basic checks:
  - make sure `.mason` is not included
  - make sure `node-pre-gyp` directory is included, because it is responsible for knowing where to grab the binary from s3.

**Hot tips** :hot_pepper:
- Node and npm versions can have differing `npm pack` and `npm publish` behaviour, so be mindful of what your environment is using
- You can use the resulting tarball locally and install it within another local repo to make sure it works:
```
npm install <path to tarball>
```


text:
# Benchmarking

This project includes [bench tests](https://github.com/mapbox/node-cpp-skel/tree/master/bench) you can use to experiment with and measure performance. These bench tests hit the functions that [simulate expensive work being done in the threadpool](https://github.com/mapbox/node-cpp-skel/blob/master/src/object_async/hello_async.cpp#L121-L122). This is intended to model realworld use cases where you, as the developer, have expensive computation you'd like to dispatch to worker threads. Adapt these tests to your custom code and monitor the performance of your code.

We've included two bench tests for the async examples, demonstrating the affects of concurrency and threads within a process. For example, you can run:

```
node bench/hello_async.bench.js --iterations 50 --concurrency 10 --mem
```

This will run a bunch of calls to the module's `helloAsync()` function. You can control two things:

- iterations: number of times to call `helloAsync()`
- concurrency: max number of threads the test can utilize, by setting `UV_THREADPOOL_SIZE`. When running the bench script, you can see this number of threads reflected in your [Activity Monitor](https://github.com/springmeyer/profiling-guide#activity-monitorapp-on-os-x)/[htop window](https://hisham.hm/htop/).
- `--mem`: Optional arg to show memory stats per [`process.memoryUsage()`](https://nodejs.org/api/process.html#process_process_memoryusage)

### Ideal Benchmarks

**Ideally, you want your workers to run your code ~99% of the time and never idle.** This reflects a healthy node c++ addon and what you would expect to see when you've picked a good problem to solve with node.

The bench tests and async functions that come with `node-cpp-skel` out of the box demonstrate this behaviour:
- An async function that is CPU intensive and takes a while to finish (expensive creation and querying of a `std::map` and string comparisons).
- Worker threads are busy doing a lot of work, and the main loop is relatively idle. Depending on how many threads (concurrency) you enable, you may see your CPU% sky-rocket and your cores max out. Yeaahhh!!!
- If you bump up `--iterations` to 500 and [profile in Activity Monitor.app](https://github.com/springmeyer/profiling-guide#activity-monitorapp-on-os-x), you'll see the main loop is idle as expected since the threads are doing all the work. You'll also see the threads busy doing work in AsyncHelloWorker roughly 99% of the time :tada:

![](https://user-images.githubusercontent.com/1209162/29333300-e7c483e2-81c8-11e7-8253-1beb12173841.png)

repo_name: mapbox/node-cpp-skel

mime_type: text/plain


text:
## Using npm, package.json, and package-lock.json

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.

`package.json` defines the dependencies required by any given library. It often defines a range of acceptable version of those dependencies, for example:

```
"dependencies": {
    "eslint": "^4.3.0"
}
```

... means that any version of eslint >4.3.0 and <5 is acceptable.

`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.

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**.

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).

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.

To take full advantage of the explicit dependency versions specified in `package-lock.json`, don't use `npm install`. Instead, run

```
npm ci
```

Note: this `ci` requires at least npm >= v5.7.1 (https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable).

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`.

### Avoid editing package.json directly

If you need to change the range of acceptable versions for a dependency:

```
npm install --save eslint@^4.4.8
```

If you want to upgrade a dependency to the most recent version specified by the existing range in `package.json`;

```
npm update eslint
```

If you want to remove a dependency:

```
npm remove eslint
```


text:
# How to debug C++ with VS Code

VS Code is a great source code editor for developing code in many languages. This document is to tell you how to debug a node C++ addon with VS Code.

### 1. Setting up VS Code debug

First open the node C++ addon folder in VS Code by choosing `File > Open`.

Then to start debugging there are two ways:

#### Option 1

Press `Cmd + Shift + P` to open the command bar, type open `open launch.json`, and then choose `C++`.

Note: the first time you do this you will need to `install` the C++ Extension and reload the app.

#### Option 2

Click the debug button on the right sidebar, click the `⚙` button on the right top, and then choose `C++`.

![howtodebug](./image/howtodebug1.png)

In the `launch.json` you can see some template, like this:

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(lldb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "enter program name, for example ${workspaceFolder}/a.out",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "MIMode": "lldb"
        }
    ]
}
```

`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`.

Additional we want build C++ everytime before we start debug, so we add `preLaunchTask` into the config:

```
"preLaunchTask": "npm: build:dev",
```

Now the config file could look like this:

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(lldb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "preLaunchTask": "npm: build:dev",
            "program": "/Users/mapbox-mofei/.nvm/versions/node/v8.11.3/bin/node",
            "args": ["${workspaceFolder}/test/vtshaver.test.js"],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "MIMode": "lldb"
        }
    ]
}
```

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.

![howtodebug](./image/howtodebug2.png)

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.

![howtodebug](./image/howtodebug3.png)


text:
#pragma once

#include <chrono>
#include <string>
#include <thread>
#include <vector>

namespace detail {

// simulate CPU intensive task
inline std::unique_ptr<std::vector<char>> do_expensive_work(std::string const& name, bool louder)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::string str = "...threads are busy async bees...hello " + name;
    std::unique_ptr<std::vector<char>> result = std::make_unique<std::vector<char>>(str.begin(), str.end());
    if (louder)
    {
        result->push_back('!');
        result->push_back('!');
        result->push_back('!');
        result->push_back('!');
    }
    return result;
}

} // namespace detail


text:
#pragma once
#include <memory>
#include <napi.h>
#include <string>

namespace utils {

/*
* This is an internal function used to return callback error messages instead of
* throwing errors.
* Usage:
*
* return CallbackError(env, "error message", callback);
*
*/

inline Napi::Value CallbackError(Napi::Env env, std::string const& message, Napi::Function const& func)
{
    Napi::Object obj = Napi::Object::New(env);
    obj.Set("message", message);
    return func.Call({obj});
}

} // namespace utils

namespace gsl {
template <typename T>
using owner = T;
} // namespace gsl

// ^^^ type alias required for clang-tidy (cppcoreguidelines-owning-memory)


text:
#include "object_async/hello_async.hpp"
#include "object_sync/hello.hpp"
#include "standalone/hello.hpp"
#include "standalone_async/hello_async.hpp"
#include "standalone_promise/hello_promise.hpp"
#include <napi.h>
// #include "your_code.hpp"

Napi::Object init(Napi::Env env, Napi::Object exports)
{
    // expose hello method
    exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, standalone::hello));

    // expose helloAsync method
    exports.Set(Napi::String::New(env, "helloAsync"), Napi::Function::New(env, standalone_async::helloAsync));

    // expose helloPromise method
    exports.Set(Napi::String::New(env, "helloPromise"), Napi::Function::New(env, standalone_promise::helloPromise));

    // expose HelloObject class
    object_sync::HelloObject::Init(env, exports);

    // expose HelloObjectAsync class
    object_async::HelloObjectAsync::Init(env, exports);

    return exports;
    /**
   * You may have noticed there are multiple "hello" functions as part of this
   * module.
   * They are exposed a bit differently.
   * 1) standalone::hello    // exposed above
   * 2) HelloObject.hello    // exposed in object_sync/hello.cpp as part of
   * HelloObject
   */

    // add more methods/classes below that youd like to use in Javascript world
    // then create a directory in /src with a .cpp and a .hpp file.
    // Include your .hpp file at the top of this file.
}

// Initialize the module (we only do this once)
// Mark this NOLINT to avoid the clang-tidy checks
// NODE_GYP_MODULE_NAME is the name of our module as defined in 'target_name'
// variable in the 'binding.gyp', which is passed along as a compiler define
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init) // NOLINT


text:
#include "hello_async.hpp"
#include "../cpu_intensive_task.hpp"
#include "../module_utils.hpp"

#include <exception>
#include <iostream>
#include <map>
#include <memory>
#include <stdexcept>

namespace standalone_async {

// This is the worker running asynchronously and calling a user-provided
// callback when done.
// Consider storing all C++ objects you need by value or by shared_ptr to keep
// them alive until done.
// Napi::AsyncWorker docs:
// https://github.com/nodejs/node-addon-api/blob/master/doc/async_worker.md
struct AsyncHelloWorker : Napi::AsyncWorker
{
    using Base = Napi::AsyncWorker;
    // ctor
    AsyncHelloWorker(bool louder, bool buffer, Napi::Function const& cb)
        : Base(cb),
          louder_(louder),
          buffer_(buffer) {}

    // The Execute() function is getting called when the worker starts to run.
    // - You only have access to member variables stored in this worker.
    // - You do not have access to Javascript v8 objects here.
    void Execute() override
    {
        // The try/catch is critical here: if code was added that could throw an
        // unhandled error INSIDE the threadpool, it would be disasterous
        try
        {
            result_ = detail::do_expensive_work("world", louder_);
        }
        catch (std::exception const& e)
        {
            SetError(e.what());
        }
    }

    // The OnOK() is getting called when Execute() successfully
    // completed.
    // - In case Execute() invoked SetErrorMessage("") this function is not
    // getting called.
    // - You have access to Javascript v8 objects again
    // - You have to translate from C++ member variables to Javascript v8 objects
    // - Finally, you call the user's callback with your results
    void OnOK() final
    {
        if (!Callback().IsEmpty() && result_)
        {
            if (buffer_)
            {
                char* data = result_->data();
                std::size_t size = result_->size();
                auto buffer = Napi::Buffer<char>::New(
                    Env(),
                    data,
                    size,
                    [](Napi::Env /*unused*/, char* /*unused*/, gsl::owner<std::vector<char>*> v) {
                        delete v;
                    },
                    result_.release());
                Callback().Call({Env().Null(), buffer});
            }
            else
            {
                Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())});
            }
        }
    }

    std::unique_ptr<std::vector<char>> result_ = nullptr;
    const bool louder_;
    const bool buffer_;
};

// helloAsync is a "standalone function" because it's not a class.
// If this function was not defined within a namespace ("standalone_async"
// specified above), it would be in the global scope.
Napi::Value helloAsync(Napi::CallbackInfo const& info)
{
    bool louder = false;
    bool buffer = false;
    Napi::Env env = info.Env();
    // Check second argument, should be a 'callback' function.
    if (!info[1].IsFunction())
    {
        Napi::TypeError::New(env, "second arg 'callback' must be a function").ThrowAsJavaScriptException();
        return env.Null();
    }

    Napi::Function callback = info[1].As<Napi::Function>();

    // Check first argument, should be an 'options' object
    if (!info[0].IsObject())
    {
        return utils::CallbackError(env, "first arg 'options' must be an object", callback);
    }
    Napi::Object options = info[0].As<Napi::Object>();

    // Check options object for the "louder" property, which should be a boolean
    // value

    if (options.Has(Napi::String::New(env, "louder")))
    {
        Napi::Value louder_val = options.Get(Napi::String::New(env, "louder"));
        if (!louder_val.IsBoolean())
        {
            return utils::CallbackError(env, "option 'louder' must be a boolean", callback);
        }
        louder = louder_val.As<Napi::Boolean>().Value();
    }
    // Check options object for the "buffer" property, which should be a boolean value
    if (options.Has(Napi::String::New(env, "buffer")))
    {
        Napi::Value buffer_val = options.Get(Napi::String::New(env, "buffer"));
        if (!buffer_val.IsBoolean())
        {
            return utils::CallbackError(env, "option 'buffer' must be a boolean", callback);
        }
        buffer = buffer_val.As<Napi::Boolean>().Value();
    }

    // Creates a worker instance and queues it to run asynchronously, invoking the
    // callback when done.
    // - Napi::AsyncWorker takes a pointer to a Napi::FunctionReference and deletes the
    // pointer automatically.
    // - Napi::AsyncQueueWorker takes a pointer to a Napi::AsyncWorker and deletes
    // the pointer automatically.
    auto* worker = new AsyncHelloWorker{louder, buffer, callback}; // NOLINT
    worker->Queue();
    return env.Undefined(); // NOLINT
}

} // namespace standalone_async


text:
#pragma once
#include <napi.h>

namespace standalone_async {

// hello, custom sync method
// method's logic lives in hello.cpp
Napi::Value helloAsync(Napi::CallbackInfo const& info);

} // namespace standalone_async


text:
#pragma once
#include <napi.h>

namespace standalone {

// hello, custom sync method
Napi::Value hello(Napi::CallbackInfo const& info);

} // namespace standalone


text:
#include "hello.hpp"

namespace standalone {

Napi::Value hello(Napi::CallbackInfo const& info)
{
    Napi::Env env = info.Env();
    return Napi::String::New(env, "hello world");
}

} // namespace standalone


text:
#pragma once

#include <napi.h>

namespace object_sync {

/**
 * HelloObject class
 * This is in a header file so we can access it across other .cpp files if necessary
 * Also, this class adheres to the rule of Zero because we define no custom destructor or copy constructor
 */
class HelloObject : public Napi::ObjectWrap<HelloObject>
{
  public:
    // initializers
    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    explicit HelloObject(Napi::CallbackInfo const& info);
    Napi::Value hello(Napi::CallbackInfo const& info);

  private:
    static Napi::FunctionReference constructor;
    std::string name_ = "";
};
} // namespace object_sync


text:
#include "hello.hpp"
#include <memory>

// If this was not defined within a namespace, it would be in the global scope.
// Namespaces are used because C++ has no notion of scoped modules, so all of
// the code you write in any file could conflict with other code.
// Namespaces are generally a great idea in C++ because it helps scale and
// clearly organize your application.
namespace object_sync {

Napi::FunctionReference HelloObject::constructor; // NOLINT

// Triggered from Javascript world when calling "new HelloObject(name)"
HelloObject::HelloObject(Napi::CallbackInfo const& info)
    : Napi::ObjectWrap<HelloObject>(info)
{
    Napi::Env env = info.Env();
    std::size_t length = info.Length();
    if (length != 1 || !info[0].IsString())
    {
        Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
        return;
    }
    name_ = info[0].As<Napi::String>();
    // ^^ uses std::string() operator to convert to UTF-8 encoded string
    // alternatively Utf8Value() method can be used e.g
    // name_ = info[0].As<Napi::String>().Utf8Value();
    if (name_.empty())
    {
        Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException();
    }
}

Napi::Value HelloObject::hello(Napi::CallbackInfo const& info)
{
    Napi::Env env = info.Env();
    return Napi::String::New(env, name_);
}

Napi::Object HelloObject::Init(Napi::Env env, Napi::Object exports)
{

    Napi::Function func = DefineClass(env, "HelloObject", {InstanceMethod("helloMethod", &HelloObject::hello)});
    // Create a peristent reference to the class constructor. This will allow
    // a function called on a class prototype and a function
    // called on instance of a class to be distinguished from each other.
    constructor = Napi::Persistent(func);
    // Call the SuppressDestruct() method on the static data prevent the calling
    // to this destructor to reset the reference when the environment is no longer
    // available.
    constructor.SuppressDestruct();
    exports.Set("HelloObject", func);
    return exports;
}

} // namespace object_sync


text:
#include "hello_async.hpp"
#include "../cpu_intensive_task.hpp"
#include "../module_utils.hpp"

#include <exception>
#include <map>
#include <memory>
#include <stdexcept>
#include <utility>

// If this was not defined within a namespace, it would be in the global scope.

namespace object_async {

/*
struct AsyncHelloWorker : Napi::AsyncWorker
{
    using Base = Napi::AsyncWorker;
    // ctor
    AsyncHelloWorker(bool louder,
                     bool buffer,
                     std::string name,
                     Napi::Function const& cb)
        : Base(cb),
          louder_(louder),
          buffer_(buffer),
          name_(std::move(name)) {}

    // The Execute() function is getting called when the worker starts to run.
    // - You only have access to member variables stored in this worker.
    // - You do not have access to Javascript v8 objects here.
    void Execute() override
    {
        try
        {
            result_ = detail::do_expensive_work(name_, louder_);
        }
        catch (std::exception const& e)
        {
            SetError(e.what());
        }
    }

    // The OnOK() is getting called when Execute() successfully
    // completed.
    // - In case Execute() invoked SetErrorMessage("") this function is not
    // getting called.
    // - You have access to Javascript v8 objects again
    // - You have to translate from C++ member variables to Javascript v8 objects
    // - Finally, you call the user's callback with your results
    void OnOK() override
    {
        if (!Callback().IsEmpty())
        {
            if (buffer_)
            {
                char * data = result_->data();
                std::size_t size = result_->size();
                auto buffer = Napi::Buffer<char>::New(Env(),
                                                      data,
                                                      size,
                                                      [](Napi::Env, char*, gsl::owner<std::vector<char>*> v) {
                                                          delete v;
                                                      },
                                                      result_.release());
                Callback().Call({Env().Null(), buffer});
            }
            else
            {
                Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())});
            }
        }
    }

    std::unique_ptr<std::vector<char>> result_ = nullptr;
    bool const louder_;
    bool const buffer_;
    std::string const name_;
};
*/

// This V2 worker is overriding `GetResult` to return the arguments
// passed to the Callback invoked by the default OnOK() implementation.
// Above is alternative implementation with OnOK() method calling
// Callback with appropriate args. Both implementations use default OnError().
struct AsyncHelloWorker_v2 : Napi::AsyncWorker
{
    using Base = Napi::AsyncWorker;
    // ctor
    AsyncHelloWorker_v2(bool louder,
                        bool buffer,
                        std::string name,
                        Napi::Function const& cb)
        : Base(cb),
          louder_(louder),
          buffer_(buffer),
          name_(std::move(name)) {}

    // The Execute() function is getting called when the worker starts to run.
    // - You only have access to member variables stored in this worker.
    // - You do not have access to Javascript v8 objects here.
    void Execute() override
    {
        try
        {
            result_ = detail::do_expensive_work(name_, louder_);
        }
        catch (std::exception const& e)
        {
            SetError(e.what());
        }
    }

    std::vector<napi_value> GetResult(Napi::Env env) override
    {
        if (result_)
        {
            if (buffer_)
            {
                char* data = result_->data();
                std::size_t size = result_->size();
                auto buffer = Napi::Buffer<char>::New(
                    env,
                    data,
                    size,
                    [](Napi::Env /*unused*/, char* /*unused*/, gsl::owner<std::vector<char>*> v) {
                        delete v;
                    },
                    result_.release());
                return {env.Null(), buffer};
            }
            return {env.Null(), Napi::String::New(env, result_->data(), result_->size())};
        }
        return Base::GetResult(env); // returns an empty vector (default)
    }

    std::unique_ptr<std::vector<char>> result_ = nullptr;
    bool const louder_;
    bool const buffer_;
    std::string const name_;
};

Napi::FunctionReference HelloObjectAsync::constructor; // NOLINT

HelloObjectAsync::HelloObjectAsync(Napi::CallbackInfo const& info)
    : Napi::ObjectWrap<HelloObjectAsync>(info)
{
    Napi::Env env = info.Env();
    std::size_t length = info.Length();
    if (length != 1 || !info[0].IsString())
    {
        Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
        return;
    }
    name_ = info[0].As<Napi::String>();
    // ^^ uses std::string() operator to convert to UTF-8 encoded string
    // alternatively Utf8Value() method can be used e.g
    // name_ = info[0].As<Napi::String>().Utf8Value();
    if (name_.empty())
    {
        Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException();
    }
}

Napi::Value HelloObjectAsync::helloAsync(Napi::CallbackInfo const& info)
{
    bool louder = false;
    bool buffer = false;

    Napi::Env env = info.Env();
    if (!(info.Length() == 2 && info[1].IsFunction()))
    {
        Napi::TypeError::New(env, "second arg 'callback' must be a function").ThrowAsJavaScriptException();
        return env.Null();
    }

    Napi::Function callback = info[1].As<Napi::Function>();

    // Check first argument, should be an 'options' object
    if (!info[0].IsObject())
    {
        return utils::CallbackError(env, "first arg 'options' must be an object", callback);
    }
    Napi::Object options = info[0].As<Napi::Object>();

    // Check options object for the "louder" property, which should be a boolean
    // value
    if (options.Has(Napi::String::New(env, "louder")))
    {
        Napi::Value louder_val = options.Get(Napi::String::New(env, "louder"));
        if (!louder_val.IsBoolean())
        {
            return utils::CallbackError(env, "option 'louder' must be a boolean", callback);
        }
        louder = louder_val.As<Napi::Boolean>().Value();
    }
    // Check options object for the "buffer" property, which should be a boolean
    // value
    if (options.Has(Napi::String::New(env, "buffer")))
    {
        Napi::Value buffer_val = options.Get(Napi::String::New(env, "buffer"));
        if (!buffer_val.IsBoolean())
        {
            return utils::CallbackError(env, "option 'buffer' must be a boolean", callback);
        }
        buffer = buffer_val.As<Napi::Boolean>().Value();
    }

    auto* worker = new AsyncHelloWorker_v2{louder, buffer, name_, callback}; // NOLINT
    worker->Queue();
    return info.Env().Undefined(); // NOLINT
}

Napi::Object HelloObjectAsync::Init(Napi::Env env, Napi::Object exports)
{
    Napi::Function func = DefineClass(env, "HelloObjectAsync", {InstanceMethod("helloAsync", &HelloObjectAsync::helloAsync)});
    // Create a peristent reference to the class constructor. This will allow
    // a function called on a class prototype and a function
    // called on instance of a class to be distinguished from each other.
    constructor = Napi::Persistent(func);
    // Call the SuppressDestruct() method on the static data prevent the calling
    // to this destructor to reset the reference when the environment is no longer
    // available.
    constructor.SuppressDestruct();
    exports.Set("HelloObjectAsync", func);
    return exports;
}

} // namespace object_async


text:
#pragma once
#include <napi.h>

namespace object_async {

/**
 * HelloObject class
 * This is in a header file so we can access it across other .cpp files if
 * necessary
 * Also, this class adheres to the rule of Zero because we define no custom
 * destructor or copy constructor
 */
class HelloObjectAsync : public Napi::ObjectWrap<HelloObjectAsync>
{
  public:
    // initializer
    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    explicit HelloObjectAsync(Napi::CallbackInfo const& info);
    Napi::Value helloAsync(Napi::CallbackInfo const& info);

  private:
    // member variable
    // specific to each instance of the class
    static Napi::FunctionReference constructor;
    std::string name_ = "";
};
} // namespace object_async


text:
#pragma once
#include <napi.h>

namespace standalone_promise {

// hello, custom sync method
// method's logic lives in hello_promise.cpp
Napi::Value helloPromise(Napi::CallbackInfo const& info);

} // namespace standalone_promise


text:
#include "hello_promise.hpp"

#include <iostream>
#include <utility>

namespace standalone_promise {

// async worker that handles the Deferred methods
struct PromiseWorker : Napi::AsyncWorker
{
    // constructor / ctor
    PromiseWorker(Napi::Env const& env, std::string phrase, int multiply)
        : Napi::AsyncWorker(env),
          phrase_(std::move(phrase)),
          multiply_(multiply),
          deferred_(Napi::Promise::Deferred::New(env)) {}

    // The Execute() function is getting called when the worker starts to run.
    // - You only have access to member variables stored in this worker.
    // - You do not have access to Javascript v8 objects here.
    void Execute() override
    {
        for (int i = 0; i < multiply_; ++i)
        {
            output_ += phrase_;
        }
    }

    // The OnOK() is getting called when Execute() successfully
    // completed.
    // - In case Execute() invoked SetErrorMessage("") this function is not
    // getting called.
    // - You have access to Javascript v8 objects again
    // - You have to translate from C++ member variables to Javascript v8 objects
    // - Finally, you call the user's callback with your results
    void OnOK() final
    {
        deferred_.Resolve(Napi::String::New(Env(), output_));
    }

    // If anything in the PromiseWorker.Execute method throws an error
    // it will be caught here and rejected.
    void OnError(Napi::Error const& error) override
    {
        deferred_.Reject(error.Value());
    }

    Napi::Promise GetPromise() const
    {
        return deferred_.Promise();
    }

    const std::string phrase_;
    const int multiply_;
    Napi::Promise::Deferred deferred_;
    std::string output_ = "";
};

// entry point
Napi::Value helloPromise(Napi::CallbackInfo const& info)
{
    Napi::Env env = info.Env();

    // default params
    std::string phrase = "hello";
    int multiply = 1;

    // validate inputs
    // - if params is defined, validate contents
    // - - params is an object
    // - - params.multiply is int and greater than zero
    // - - params.phrase is string
    // - otherwise skip and use defaults
    if (!info[0].IsUndefined())
    {
        if (!info[0].IsObject())
        {
            throw Napi::Error::New(env, "options must be an object");
        }
        Napi::Object options = info[0].As<Napi::Object>();

        // phrase must be a string
        if (options.Has(Napi::String::New(env, "phrase")))
        {
            Napi::Value phrase_val = options.Get(Napi::String::New(env, "phrase"));
            if (!phrase_val.IsString())
            {
                throw Napi::Error::New(env, "options.phrase must be a string");
            }
            phrase = phrase_val.As<Napi::String>();
        }

        // multiply must be int > 1
        if (options.Has(Napi::String::New(env, "multiply")))
        {
            Napi::Value multiply_val = options.Get(Napi::String::New(env, "multiply"));
            if (!multiply_val.IsNumber())
            {
                throw Napi::Error::New(env, "options.multiply must be a number");
            }
            multiply = multiply_val.As<Napi::Number>().Int32Value();
            if (multiply < 1)
            {
                throw Napi::Error::New(env, "options.multiply must be 1 or greater");
            }
        }
    }

    // initialize Napi::AsyncWorker
    // This comes with a GetPromise that returns the necessary
    // Napi::Promise::Deferred class that we send the user
    auto* worker = new PromiseWorker{env, phrase, multiply};
    auto promise = worker->GetPromise();

    // begin asynchronous work by queueing it.
    // https://github.com/nodejs/node-addon-api/blob/main/doc/async_worker.md#queue
    worker->Queue();

    // return the deferred promise to the user. Let the
    // async worker resolve/reject accordingly
    return promise;
}

} // namespace standalone_promise


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

CONTAINER=build/binding.xcodeproj
OUTPUT="${CONTAINER}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme"

# Required ENV vars:
# - SCHEME_TYPE: type of the scheme
# - SCHEME_NAME: name of the scheme

# Optional ENV vars:
# - NODE_ARGUMENT (defaults to "")
# - BUILDABLE_NAME (defaults ot SCHEME_NAME)
# - BLUEPRINT_NAME (defaults ot SCHEME_NAME)

# Try to reuse the existing Blueprint ID if the scheme already exists.
if [ -f "${OUTPUT}" ]; then
    BLUEPRINT_ID=$(sed -n "s/[ \t]*BlueprintIdentifier *= *\"\([A-Z0-9]\{24\}\)\"/\\1/p" "${OUTPUT}" | head -1)
fi

NODE_ARGUMENT=${NODE_ARGUMENT:-}
BLUEPRINT_ID=${BLUEPRINT_ID:-$(hexdump -n 12 -v -e '/1 "%02X"' /dev/urandom)}
BUILDABLE_NAME=${BUILDABLE_NAME:-${SCHEME_NAME}}
BLUEPRINT_NAME=${BLUEPRINT_NAME:-${SCHEME_NAME}}

mkdir -p "${CONTAINER}/xcshareddata/xcschemes"

sed "\
s#{{BLUEPRINT_ID}}#${BLUEPRINT_ID}#;\
s#{{BLUEPRINT_NAME}}#${BLUEPRINT_NAME}#;\
s#{{BUILDABLE_NAME}}#${BUILDABLE_NAME}#;\
s#{{CONTAINER}}#${CONTAINER}#;\
s#{{WORKING_DIRECTORY}}#$(pwd)#;\
s#{{NODE_PATH}}#$(dirname `which node`)#;\
s#{{NODE_ARGUMENT}}#${NODE_ARGUMENT}#" \
    scripts/${SCHEME_TYPE}.xcscheme > "${OUTPUT}"


text:
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0730"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "{{BLUEPRINT_ID}}"
               BuildableName = "{{BUILDABLE_NAME}}"
               BlueprintName = "{{BLUEPRINT_NAME}}"
               ReferencedContainer = "container:{{CONTAINER}}">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "{{BLUEPRINT_ID}}"
            BuildableName = "{{BUILDABLE_NAME}}"
            BlueprintName = "{{BLUEPRINT_NAME}}"
            ReferencedContainer = "container:{{CONTAINER}}">
         </BuildableReference>
      </MacroExpansion>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "{{BLUEPRINT_ID}}"
            BuildableName = "{{BUILDABLE_NAME}}"
            BlueprintName = "{{BLUEPRINT_NAME}}"
            ReferencedContainer = "container:{{CONTAINER}}">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


text:
#!/usr/bin/env python

import sys
import json
import os
import re

# Script to generate compile_commands.json based on Makefile output
# Works by accepting Makefile output from stdin, parsing it, and
# turning into json records. These are then printed to stdout.
# More details on the compile_commands format at:
# https://clang.llvm.org/docs/JSONCompilationDatabase.html
#
# Note: make must be run in verbose mode, e.g. V=1 make or VERBOSE=1 make
#
# Usage with node-cpp-skel:
#
# make | ./scripts/generate_compile_commands.py > build/compile_commands.json

# These work for node-cpp-skel to detect the files being compiled
# They may need to be modified if you adapt this to another tool
matcher = re.compile('^(.*) (.+cpp)\n')
build_dir = os.path.join(os.getcwd(),"build")
TOKEN_DENOTING_COMPILED_FILE='NODE_GYP_MODULE_NAME'

def generate():
    compile_commands = []
    for line in sys.stdin.readlines():
        if TOKEN_DENOTING_COMPILED_FILE in line:
            match = matcher.match(line)
            if match:
                compile_commands.append({
                    "directory": build_dir,
                    "command": line.strip(),
                    "file": os.path.normpath(os.path.join(build_dir,match.group(2)))
                })
    print(json.dumps(compile_commands,indent=4))

if __name__ == '__main__':
    generate()


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

export COMMIT_MESSAGE=$(git log --format=%B --no-merges -n 1 | tr -d '\n')

# `is_pr_merge` is designed to detect if a gitsha represents a normal
# push commit (to any branch) or whether it represents travis attempting
# to merge between the origin and the upstream branch.
# For more details see: https://docs.travis-ci.com/user/pull-requests
function is_pr_merge() {
  # Get the commit message via git log
  # This should always be the exactly the text the developer provided
  local COMMIT_LOG=${COMMIT_MESSAGE}

  # Get the commit message via git show
  # If the gitsha represents a merge then this will
  # look something like "Merge e3b1981 into 615d2a3"
  # Otherwise it will be the same as the "git log" output
  export COMMIT_SHOW=$(git show -s --format=%B | tr -d '\n')

  if [[ "${COMMIT_LOG}" != "${COMMIT_SHOW}" ]]; then
     echo true
  fi
}

# Detect if this commit represents a tag. This is useful
# to detect if we are on a travis job that is running due to
# "git tags --push". In this case we don't want to publish even
# if [publish binary] is present since that should refer only to the
# previously job that ran for that commit and not the tag made
function is_tag_commit() {
  export COMMIT_MATCHES_KNOWN_TAG=$(git describe --exact-match $(git rev-parse HEAD) 2> /dev/null)
  if [[ ${COMMIT_MATCHES_KNOWN_TAG} ]]; then
    echo true
  fi
}

# `publish` is used to publish binaries to s3 via commit messages if:
# - the commit message includes [publish binary]
# - the commit message includes [republish binary]
# - the commit is not a pr_merge (checked with `is_pr_merge` function)
function publish() {
  echo "dumping binary meta..."
  ./node_modules/.bin/node-pre-gyp reveal --loglevel=error $@

  echo "determining publishing status..."

  if [[ $(is_pr_merge) ]]; then
      echo "Skipping publishing because this is a PR merge commit"
  elif [[  $(is_tag_commit) ]]; then
      echo "Skipping publishing because this is a tag"
  else
      echo "Commit message was: '${COMMIT_MESSAGE}'"

      if [[ ${COMMIT_MESSAGE} =~ "[publish binary]" ]]; then
          echo "Publishing"
          ./node_modules/.bin/node-pre-gyp package publish $@
      elif [[ ${COMMIT_MESSAGE} =~ "[republish binary]" ]]; then
          echo "Re-Publishing"
          ./node_modules/.bin/node-pre-gyp package unpublish publish $@
      else
          echo "Skipping publishing since we did not detect either [publish binary] or [republish binary] in commit message"
      fi
  fi
}

function usage() {
  >&2 echo "Usage"
  >&2 echo ""
  >&2 echo "$ ./scripts/publish.sh <args>"
  >&2 echo ""
  >&2 echo "All args are forwarded to node-pre-gyp like --debug"
  >&2 echo ""
  exit 1
}

# https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
for i in "$@"
do
case $i in
    -h | --help)
    usage
    shift
    ;;
    *)
    ;;
esac
done

publish $@


text:
#!/usr/bin/env bash

set -eu

# First create new repo on GitHub and copy the SSH repo url
# Then run "./scripts/liftoff.sh" from within your local node-cpp-skel root directory
# and it will create your new local project repo side by side with node-cpp-skel directory

echo "What is the name of your new project? "
read name
echo "What is the remote repo url for your new project? "
read url

mkdir ../$name
cp -R ../node-cpp-skel/. ../$name/
cd ../$name/
rm -rf .git
git init

git checkout -b node-cpp-skel-port
git add .
git commit -m "Port from node-cpp-skel"
git remote add origin $url
git push -u origin node-cpp-skel-port

# Perhaps useful for fresh start, also check out https://github.com/mapbox/node-cpp-skel#add-custom-code
# cp /dev/null CHANGELOG.md
# cp /dev/null README.md


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

# http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation
# https://www.bignerdranch.com/blog/weve-got-you-covered/

make clean
export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping"
export LDFLAGS="-fprofile-instr-generate"
make debug
rm -f *profraw
rm -f *gcov
rm -f *profdata
LLVM_PROFILE_FILE="code-%p.profraw" npm test
CXX_MODULE=$(./node_modules/.bin/node-pre-gyp reveal module --silent)
export PATH=$(pwd)/mason_packages/.link/bin/:${PATH}
llvm-profdata merge -output=code.profdata code-*.profraw
llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color
llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color
llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html
echo "open /tmp/coverage.html for HTML version of this report"


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

: '

Runs clang-format on the code in src/

Return `1` if there are files to be formatted, and automatically formats them.

Returns `0` if everything looks properly formatted.

'

PATH_TO_FORMAT_SCRIPT="$(pwd)/mason_packages/.link/bin/clang-format"

# Run clang-format on all cpp and hpp files in the /src directory
find src/ -type f -name '*.hpp' -o -name '*.cpp' \
 | xargs -I{} ${PATH_TO_FORMAT_SCRIPT} -i -style=file {}

# Print list of modified files
dirty=$(git ls-files --modified src/)

if [[ $dirty ]]; then
    echo "The following files have been modified:"
    echo $dirty
    git diff
    exit 1
else
    exit 0
fi


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

# https://clang.llvm.org/extra/clang-tidy/

: '
Runs clang-tidy on the code in src/
Return `1` if there are files automatically fixed by clang-tidy.
Returns `0` if no fixes by clang-tidy.
TODO: should also return non-zero if clang-tidy emits warnings
or errors about things it cannot automatically fix. However I cannot
figure out how to get this working yet as it seems that clang-tidy
always returns 0 even on errors.
'

PATH_TO_CLANG_TIDY_SCRIPT="$(pwd)/mason_packages/.link/share/run-clang-tidy.py"
# make sure that run-clang-tidy.py can find the right clang-tidy
export PATH=$(pwd)/mason_packages/.link/bin:${PATH}

# build the compile_commands.json file if it does not exist
if [[ ! -f build/compile_commands.json ]]; then
    # We need to clean otherwise when we make the project
    # will will not see all the compile commands
    make clean
    # Create the build directory to put the compile_commands in
    # We do this first to ensure it is there to start writing to
    # immediately (make make not create it right away)
    mkdir -p build
    # Run make, pipe the output to the generate_compile_commands.py
    # and drop them in a place that clang-tidy will automatically find them
    RESULT=0
    make | tee /tmp/make-node-cpp-skel-build-output.txt || RESULT=$?
    if [[ ${RESULT} != 0 ]]; then
        echo "Build failed, could not generate compile commands for clang-tidy, aborting!"
        exit ${RESULT}
    else
        cat /tmp/make-node-cpp-skel-build-output.txt | scripts/generate_compile_commands.py > build/compile_commands.json
    fi

fi

# change into the build directory so that clang-tidy can find the files
# at the right paths (since this is where the actual build happens)
cd build
${PATH_TO_CLANG_TIDY_SCRIPT} -fix
cd ../

# Print list of modified files
dirty=$(git ls-files --modified src/)

if [[ $dirty ]]; then
    echo "The following files have been modified:"
    echo $dirty
    git diff
    exit 1
else
    exit 0
fi


text:
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0730"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "{{BLUEPRINT_ID}}"
               BuildableName = "{{BUILDABLE_NAME}}"
               BlueprintName = "{{BLUEPRINT_NAME}}"
               ReferencedContainer = "container:{{CONTAINER}}">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "YES"
      customWorkingDirectory = "{{WORKING_DIRECTORY}}"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <PathRunnable
         runnableDebuggingMode = "0"
         FilePath = "{{NODE_PATH}}/node">
      </PathRunnable>
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "{{BLUEPRINT_ID}}"
            BuildableName = "{{BUILDABLE_NAME}}"
            BlueprintName = "{{BLUEPRINT_NAME}}"
            ReferencedContainer = "container:{{CONTAINER}}">
         </BuildableReference>
      </MacroExpansion>
      <CommandLineArguments>
         <CommandLineArgument
            argument = "{{NODE_ARGUMENT}}"
            isEnabled = "YES">
         </CommandLineArgument>
      </CommandLineArguments>
      <EnvironmentVariables>
         <EnvironmentVariable
            key = "PATH"
            value = "{{NODE_PATH}}:$PATH"
            isEnabled = "YES">
         </EnvironmentVariable>
      </EnvironmentVariables>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "{{BLUEPRINT_ID}}"
            BuildableName = "{{BUILDABLE_NAME}}"
            BlueprintName = "{{BLUEPRINT_NAME}}"
            ReferencedContainer = "container:{{CONTAINER}}">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


text:
#!/usr/bin/env bash

set -eu
set -o pipefail

: '

Rebuilds the code with the sanitizers and runs the tests

'

# See https://github.com/mapbox/node-cpp-skel/blob/master/docs/extended-tour.md#configuration-files

make clean

# https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso
SHARED_LIB_EXT=.so
if [[ $(uname -s) == 'Darwin' ]]; then
    SHARED_LIB_EXT=.dylib
fi

export MASON_LLVM_RT_PRELOAD=$(pwd)/$(ls mason_packages/.link/lib/clang/*/lib/*/libclang_rt.asan*${SHARED_LIB_EXT})
SUPPRESSION_FILE="/tmp/leak_suppressions.txt"
echo "leak:__strdup" > ${SUPPRESSION_FILE}
echo "leak:v8::internal" >> ${SUPPRESSION_FILE}
echo "leak:node::CreateEnvironment" >> ${SUPPRESSION_FILE}
echo "leak:node::Start" >> ${SUPPRESSION_FILE}
echo "leak:node::Init" >> ${SUPPRESSION_FILE}
# Suppress leak related to https://github.com/libuv/libuv/pull/2480
echo "leak:uv__set_process_title_platform_init" >> ${SUPPRESSION_FILE}
export ASAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer
export MSAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer
export UBSAN_OPTIONS=print_stacktrace=1
export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE}
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
export MASON_SANITIZE="-fsanitize=address,undefined,integer,leak -fno-sanitize=vptr,function"
export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"
export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}"
# Note: to build without stopping on errors remove the -fno-sanitize-recover=all flag
# You might want to do this if there are multiple errors and you want to see them all before fixing
export CXXFLAGS="${MASON_SANITIZE_CXXFLAGS} ${CXXFLAGS:-} -fno-sanitize-recover=all"
export LDFLAGS="${MASON_SANITIZE_LDFLAGS} ${LDFLAGS:-}"
make debug
export ASAN_OPTIONS=fast_unwind_on_malloc=0:${ASAN_OPTIONS}
if [[ $(uname -s) == 'Darwin' ]]; then
    # NOTE: we must call node directly here rather than `npm test`
    # because OS X blocks `DYLD_INSERT_LIBRARIES` being inherited by sub shells
    # If this is not done right we'll see
    #   ==18464==ERROR: Interceptors are not working. This may be because AddressSanitizer is loaded too late (e.g. via dlopen).
    #
    # See https://github.com/mapbox/node-cpp-skel/issues/122
    DYLD_INSERT_LIBRARIES=${MASON_LLVM_RT_PRELOAD} \
      node node_modules/.bin/$(node -e "console.log(require('./package.json').scripts.test)") test/*test.js
else
    LD_PRELOAD=${MASON_LLVM_RT_PRELOAD} \
      npm test
fi


text:
"use strict";

var argv = require('minimist')(process.argv.slice(2));
if (!argv.iterations || !argv.concurrency) {
  console.error('Please provide desired iterations, concurrency');
  console.error('Example: \n\tnode bench/hello_async.bench.js --iterations 50 --concurrency 10');
  console.error('Optional args: \n\t--mem (reports memory stats)');
  process.exit(1);
}

// This env var sets the libuv threadpool size.
// This value is locked in once a function interacts with the threadpool
// Therefore we need to set this value either in the shell or at the very
// top of a JS file (like we do here)
process.env.UV_THREADPOOL_SIZE = argv.concurrency;

var fs = require('fs');
var path = require('path');
var bytes = require('bytes');
var assert = require('assert')
var d3_queue = require('d3-queue');
var module = require('../lib/index.js');
var queue = d3_queue.queue();

var track_mem = argv.mem ? true : false;
var runs = 0;
var memstats = {
  max_rss:0,
  max_heap:0,
  max_heap_total:0
};

function run(cb) {
  module.helloAsync({ louder: false }, function(err, result) {
      if (err) {
        return cb(err);
      }
      ++runs;
      if (track_mem && runs % 1000) {
        var mem = process.memoryUsage();
        if (mem.rss > memstats.max_rss) memstats.max_rss = mem.rss;
        if (mem.heapTotal > memstats.max_heap_total) memstats.max_heap_total = mem.heapTotal;
        if (mem.heapUsed > memstats.max_heap) memstats.max_heap = mem.heapUsed;
      }
      return cb();
  });
}

// Start monitoring time before async work begins within the defer iterator below.
// AsyncWorkers will kick off actual work before the defer iterator is finished,
// and we want to make sure we capture the time of the work of that initial cycle.
var time = +(new Date());

for (var i = 0; i < argv.iterations; i++) {
    queue.defer(run);
}

queue.awaitAll(function(error) {
  if (error) throw error;
  if (runs != argv.iterations) {
    throw new Error('Error: did not run as expected');
  }
  // check rate
  time = +(new Date()) - time;

  if (time == 0) {
    console.log('Warning: ms timer not high enough resolution to reliably track rate. Try more iterations');
  } else {
  // number of milliseconds per iteration
    var rate = runs/(time/1000);
    console.log('Benchmark speed: ' + rate.toFixed(0) + ' runs/s (runs:' + runs + ' ms:' + time + ' )');

    if (track_mem) {
      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));
    } else {
      console.log('Note: pass --mem to track memory usage');
    }
  }

  console.log('Benchmark iterations:',argv.iterations,'concurrency:',argv.concurrency)

  // There may be instances when you want to assert some performance metric
  //assert.equal(rate > 1000, true, 'speed not at least 1000/second ( rate was ' + rate + ' runs/s )');

});


text:
"use strict";

var argv = require('minimist')(process.argv.slice(2));
if (!argv.iterations || !argv.concurrency) {
  console.error('Please provide desired iterations, concurrency');
  console.error('Example: \n\tnode bench/hello_object_async.bench.js --iterations 50 --concurrency 10');
  console.error('Optional args: \n\t--mem (reports memory stats)');
  process.exit(1);
}

// This env var sets the libuv threadpool size.
// This value is locked in once a function interacts with the threadpool
// Therefore we need to set this value either in the shell or at the very
// top of a JS file (like we do here)
process.env.UV_THREADPOOL_SIZE = argv.concurrency;

var fs = require('fs');
var path = require('path');
var bytes = require('bytes');
var assert = require('assert')
var d3_queue = require('d3-queue');
var queue = d3_queue.queue();
var module = require('../lib/index.js');

var H = new module.HelloObjectAsync('park bench');
var track_mem = argv.mem ? true : false;
var runs = 0;
var memstats = {
  max_rss:0,
  max_heap:0,
  max_heap_total:0
};

function run(cb) {
    H.helloAsync({ louder: false }, function(err, result) {
      if (err) {
        return cb(err);
      }
      ++runs;
      if (track_mem && runs % 1000) {
        var mem = process.memoryUsage();
        if (mem.rss > memstats.max_rss) memstats.max_rss = mem.rss;
        if (mem.heapTotal > memstats.max_heap_total) memstats.max_heap_total = mem.heapTotal;
        if (mem.heapUsed > memstats.max_heap) memstats.max_heap = mem.heapUsed;
      }
      return cb();
    });
}

// Start monitoring time before async work begins within the defer iterator below.
// AsyncWorkers will kick off actual work before the defer iterator is finished,
// and we want to make sure we capture the time of the work of that initial cycle.
var time = +(new Date());

for (var i = 0; i < argv.iterations; i++) {
    queue.defer(run);
}

queue.awaitAll(function(error) {
  if (error) throw error;
  if (runs != argv.iterations) {
    throw new Error("Error: did not run as expected");
  }
  // check rate
  time = +(new Date()) - time;

  if (time == 0) {
    console.log("Warning: ms timer not high enough resolution to reliably track rate. Try more iterations");
  } else {
  // number of milliseconds per iteration
    var rate = runs/(time/1000);
    console.log('Benchmark speed: ' + rate.toFixed(0) + ' runs/s (runs:' + runs + ' ms:' + time + ' )');

    if (track_mem) {
      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));
    } else {
      console.log('Note: pass --mem to track memory usage');
    }
  }

  console.log('Benchmark iterations:',argv.iterations,'concurrency:',argv.concurrency);

  // There may be instances when you want to assert some performance metric
  //assert.equal(rate > 1000, true, 'speed not at least 1000/second ( rate was ' + rate + ' runs/s )');

});


text:
"use strict";

const {
  hello,
  helloAsync,
  helloPromise,
  HelloObject,
  HelloObjectAsync
} = require('./binding/module.node');

module.exports = {
  /**
   * This is a synchronous standalone function that logs a string.
   * @name hello
   * @returns {string}
   * @example
   * const { hello } = require('@mapbox/node-cpp-skel');
   * const check = hello();
   * console.log(check); // => "hello world"
   */
  hello,

  /**
   * This is an asynchronous standalone function that logs a string.
   * @name helloAsync
   * @param {Object} args - different ways to alter the string
   * @param {boolean} args.louder - adds exclamation points to the string
   * @param {boolean} args.buffer - returns value as a node buffer rather than a string
   * @param {Function} callback - from whence the hello comes, returns a string
   * @returns {string}
   * @example
   * const { helloAsync } = require('@mapbox/node-cpp-skel');
   * helloAsync({ louder: true }, function(err, result) {
   *   if (err) throw err;
   *   console.log(result); // => "...threads are busy async bees...hello
   * world!!!!"
   * });
   */
  helloAsync,

  /**
   * This is a function that returns a promise. It multiplies a string N times.
   * @name helloPromise
   * @param {Object} [options] - different ways to alter the string
   * @param {string} [options.phrase=hello] - the string to multiply
   * @param {Number} [options.multiply=1] - duplicate the string this number of times
   * @returns {Promise}
   * @example
   * const { helloPromise } = require('@mapbox/node-cpp-skel');
   * const result = await helloAsync({ phrase: 'Howdy', multiply: 3 });
   * console.log(result); // HowdyHowdyHowdy
   */
  helloPromise,

  /**
   * Synchronous class, called HelloObject
   * @class HelloObject
   * @example
   * const { HelloObject } = require('@mapbox/node-cpp-skel');
   * const Obj = new HelloObject('greg');
   */

  /**
   * Say hello
   *
   * @name hello
   * @memberof HelloObject
   * @returns {String}
   * @example
   * const x = Obj.hello();
   * console.log(x); // => '...initialized an object...hello greg'
   */
   HelloObject,

  /**
   * Asynchronous class, called HelloObjectAsync
   * @class HelloObjectAsync
   * @example
   * const { HelloObjectAsync } = require('@mapbox/node-cpp-skel');
   * const Obj = new module.HelloObjectAsync('greg');
   */

  /**
   * Say hello while doing expensive work in threads
   *
   * @name helloAsync
   * @memberof HelloObjectAsync
   * @param {Object} args - different ways to alter the string
   * @param {boolean} args.louder - adds exclamation points to the string
   * @param {buffer} args.buffer - returns object as a node buffer rather then string
   * @param {Function} callback - from whence the hello comes, returns a string
   * @returns {String}
   * @example
   * const { HelloObjectAsync } = require('@mapbox/node-cpp-skel');
   * const Obj = new HelloObjectAsync('greg');
   * Obj.helloAsync({ louder: true }, function(err, result) {
   *   if (err) throw err;
   *   console.log(result); // => '...threads are busy async bees...hello greg!!!'
   * });
   */
   HelloObjectAsync
};


text:
var test = require('tape');
var module = require('../lib/index.js');

test('success: prints expected string via custom constructor', function(t) {
  var H = new module.HelloObjectAsync('carol');
  H.helloAsync({ louder: false }, function(err, result) {
    if (err) throw err;
    t.equal(result, '...threads are busy async bees...hello carol');
    t.end();
  });
});

test('success: prints loud busy world', function(t) {
  var H = new module.HelloObjectAsync('world');
  H.helloAsync({ louder: true }, function(err, result) {
    if (err) throw err;
    t.equal(result, '...threads are busy async bees...hello world!!!!');
    t.end();
  });
});

test('success: return buffer busy world', function(t) {
  var H = new module.HelloObjectAsync('world');
  H.helloAsync({ buffer: true }, function(err, result) {
    if (err) throw err;
    t.equal(result.length, 44);
    t.equal(typeof(result), 'object');
    t.equal(result.toString(), '...threads are busy async bees...hello world');
    t.end();
  });
});

test('error: throws when passing empty string', function(t) {
  try {
    var H = new module.HelloObjectAsync('');
  } catch(err) {
    t.ok(err, 'expected error');
    t.equal(err.message, 'arg must be a non-empty string', 'expected error message')
    t.end();
  }
});

test('error: throws when missing "new"', function(t) {
  try {
    var H = module.HelloObjectAsync('world');
  } catch(err) {
    t.ok(err, 'expected error');
    t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message')
    t.end();
  }
});

test('error: handles non-string arg within constructor', function(t) {
  try {
    var H = new module.HelloObjectAsync(24);
  } catch(err) {
    console.log(err.message);
    t.equal(err.message, 'String expected', 'expected error message');
    t.end();
  }
});

test('error: handles invalid louder value', function(t) {
  var H = new module.HelloObjectAsync('world');
  H.helloAsync({ louder: 'oops' }, function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('option \'louder\' must be a boolean') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles invalid buffer value', function(t) {
  var H = new module.HelloObjectAsync('world');
  H.helloAsync({ buffer: 'oops' }, function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles invalid options value', function(t) {
  var H = new module.HelloObjectAsync('world');
  H.helloAsync('oops', function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('first arg \'options\' must be an object') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles missing callback', function(t) {
  var H = new module.HelloObjectAsync('world');
  try {
    H.helloAsync({ louder: false}, {});
  } catch (err) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('second arg \'callback\' must be a function') > -1, 'expected error message');
    t.end();
  }
});

test('error: handles missing arg', function(t) {
  try {
    var H = new module.HelloObjectAsync();
  } catch (err) {
    t.ok(err, 'expected error');
    t.equal(err.message, 'String expected', 'expected error message');
    t.end();
  }
});


text:
var test = require('tape');
var module = require('../lib/index.js');

test('success: prints expected string', function(t) {
  var H = new module.HelloObject('carol');
  var check = H.helloMethod();
  t.equal(check, 'carol', 'returned expected string');
  t.end();
});

test('error: throws when passing empty string', function(t) {
  try {
    var H = new module.HelloObject('');
  } catch(err) {
    t.ok(err, 'expected error');
    t.equal(err.message, 'arg must be a non-empty string', 'expected error message');
    t.end();
  }
});

test('error: throws when missing "new"', function(t) {
  try {
    var H = module.HelloObject();
  } catch(err) {
    t.ok(err);
    t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message');
    t.end();
  }
});

test('error: handles non-string arg within constructor', function(t) {
  try {
    var H = new module.HelloObject(24);
  } catch(err) {
    t.ok(err, 'expected error');
    t.ok(err.message, 'A string was expected', 'expected error message');
    t.end();
  }
});

test('error: handles missing arg', function(t) {
  try {
    var H = new module.HelloObject();
  } catch (err) {
    t.ok(err, 'expected error');
    t.ok(err.message, 'must provide string arg', 'expected error message');
    t.end();
  }
});


text:
var test = require('tape');
var module = require('../lib/index.js');

test('prints world', function(t) {
  var check = module.hello();
  t.equal(check, 'hello world', 'returned world');
  t.end();
});


text:
var test = require('tape');
var module = require('../lib/index.js');

test('success: prints loud busy world', function(t) {
  module.helloAsync({ louder: true }, function(err, result) {
    if (err) throw err;
    t.equal(result, '...threads are busy async bees...hello world!!!!');
    t.end();
  });
});

test('success: prints regular busy world', function(t) {
  module.helloAsync({ louder: false }, function(err, result) {
    if (err) throw err;
    t.equal(result, '...threads are busy async bees...hello world');
    t.end();
  });
});

test('success: buffer regular busy world', function(t) {
  module.helloAsync({ buffer: true }, function(err, result) {
    if (err) throw err;
    t.equal(result.length, 44);
    t.equal(typeof(result), 'object');
    t.equal(result.toString(), '...threads are busy async bees...hello world');
    t.end();
  });
});

test('error: handles invalid louder value', function(t) {
  module.helloAsync({ louder: 'oops' }, function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('option \'louder\' must be a boolean') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles invalid buffer value', function(t) {
  module.helloAsync({ buffer: 'oops' }, function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles invalid options value', function(t) {
  module.helloAsync('oops', function(err, result) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('first arg \'options\' must be an object') > -1, 'expected error message');
    t.end();
  });
});

test('error: handles missing callback', function(t) {
  try {
    module.helloAsync({ louder: 'oops' }, {});
  } catch (err) {
    t.ok(err, 'expected error');
    t.ok(err.message.indexOf('second arg \'callback\' must be a function') > -1, 'expected error message');
    t.end();
  }
});


text:
'use strict';

const test = require('tape');
const { helloPromise } = require('../lib/index.js');

test('success: no options object', async (assert) => {
  const result = await helloPromise();
  assert.equal(result, 'hello');
  assert.end();
});

test('success: empty options object', async (assert) => {
  const result = await helloPromise({});
  assert.equal(result, 'hello');
  assert.end();
});

test('success: options.phrase', async (assert) => {
  const result = await helloPromise({ phrase: 'Waka' });
  assert.equal(result, 'Waka');
  assert.end();
});

test('success: options.multiply', async (assert) => {
  const result = await helloPromise({ multiply: 4 });
  assert.equal(result, 'hellohellohellohello');
  assert.end();
});

test('success: options.phrase and options.multiply', async (assert) => {
  const result = await helloPromise({ phrase: 'Waka', multiply: 5 });
  assert.equal(result, 'WakaWakaWakaWakaWaka');
  assert.end();
});

test('error: invalid options type', async (assert) => {
  try {
    await helloPromise('not an object');
    assert.fail();
  } catch (err) {
    assert.ok(err);
    assert.equal(err.message, 'options must be an object');
  }
  assert.end();
});

test('error: invalid options.phrase', async (assert) => {
  try {
    await helloPromise({ phrase: 10 });
    assert.fail();
  } catch (err) {
    assert.ok(err);
    assert.equal(err.message, 'options.phrase must be a string');
  }
  assert.end();
});

test('error: invalid options.multiply', async (assert) => {
  try {
    await helloPromise({ multiply: 'not a number' });
    assert.fail();
  } catch (err) {
    assert.ok(err);
    assert.equal(err.message, 'options.multiply must be a number');
  }
  assert.end();
});


text:
var cf = require('@mapbox/cloudfriend');
var package_json = require('../package.json')

module.exports = {
  AWSTemplateFormatVersion: '2010-09-09',
  Description: 'user for publishing to s3://mapbox-node-binary/' + package_json.name,
  Resources: {
    User: {
      Type: 'AWS::IAM::User',
      Properties: {
        Policies: [
          {
            PolicyName: 'list',
            PolicyDocument: {
              Statement: [
                {
                  Action: ['s3:ListBucket'],
                  Effect: 'Allow',
                  Resource: 'arn:aws:s3:::mapbox-node-binary',
                  Condition : {
                    StringLike : {
                      "s3:prefix": [ package_json.name + "/*"]
                    }
                  }
                }
              ]
            }
          },
          {
            PolicyName: 'publish',
            PolicyDocument: {
              Statement: [
                {
                  Action: ['s3:DeleteObject', 's3:GetObject', 's3:GetObjectAcl', 's3:PutObject', 's3:PutObjectAcl'],
                  Effect: 'Allow',
                  Resource: 'arn:aws:s3:::mapbox-node-binary/' + package_json.name + '/*'
                }
              ]
            }
          }
        ]
      }
    },
    AccessKey: {
      Type: 'AWS::IAM::AccessKey',
      Properties: {
        UserName: cf.ref('User')
      }
    }
  },
  Outputs: {
    AccessKeyId: {
      Value: cf.ref('AccessKey')
    },
    SecretAccessKey: {
      Value: cf.getAtt('AccessKey', 'SecretAccessKey')
    }
  }
};


text:
= Olical's dotfiles

This is a collection of configuration for things such as https://sw.kovidgoyal.net/kitty/[Kitty] and https://neovim.io/[Neovim] Arch's `pacman` package manager manager and stow for linking the configuration into the right places.

This is designed to be run within Arch Linux, I use `archinstall` to set everything up since it's minimal and comes with KDE pre-configured.

== Usage

Clone this repo somewhere neat, such as `~/repos/Olical/dotfiles`, `cd` into the directory and execute `script/bootstrap.sh`. This will install all essential packages (as well as a few non-essential but useful ones) and then switch the terminal and shell over to kitty and fish respectively.

Your mileage may vary wildly, this is built for me and may break your entire machine. Be careful, read the code and just take the lines and files you're interested in if you can.

== License

Consider dotfiles unlicensed (https://unlicense.org/), do what you want with anything you want. Feel free to link back if you want, it could help others in the future but I don't mind otherwise.

== Questions?

Feel free to open issues if you're interested in something or tweet me https://twitter.com/OliverCaldwell[@OliverCaldwell].

repo_name: Olical/dotfiles

repo_language: Lua


text:
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>


text:
#!/usr/bin/env bash

set -xe

yay -S --needed \
	kitty fzf cowsay entr fish httpie htop npm nodejs-lts-hydrogen \
	asciinema ripgrep clojure lazygit tree bitwarden-cli \
	babashka lua xclip ttf-fira-code ttf-dejavu-emojiless \
	noto-fonts-emoji gnome-keyring topgrade neovim leiningen \
	difftastic direnv ttf-firacode-nerd luarocks fennel \
	fd sd spectacle rsync bat jq yq


text:
#!/usr/bin/env bash

set -xe

if ! command -v yay &> /dev/null
then
    sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si && cd .. && rm -rf ./yay
fi

yay -S --needed stow git

if [ ! -d "~/.config/nvim" ]; then
  git clone --depth 1 https://github.com/AstroNvim/AstroNvim ~/.config/nvim
fi

stow --target=$HOME stowed
./script/install-packages.sh
chsh -s `which fish`


text:
;; The deps.edn file describes the information needed to build a classpath.
;;
;; When using the `clojure` or `clj` script, there are several deps.edn files
;; that are combined:
;; - install-level
;; - user level (this file)
;; - project level (current directory when invoked)
;;
;; For all attributes other than :paths, these config files are merged left to right.
;; Only the last :paths is kept and others are dropped.

{
  ;; Paths
  ;;   Directories in the current project to include in the classpath

  ;; :paths ["src"]

  ;; External dependencies

  ;; :deps {
  ;;   org.clojure/clojure {:mvn/version "1.10.1"}
  ;; }

  ;; Aliases
	;;   resolve-deps aliases (-R) affect dependency resolution, options:
	;;     :extra-deps - specifies extra deps to add to :deps
	;;     :override-deps - specifies a coordinate to use instead of that in :deps
	;;     :default-deps - specifies a coordinate to use for a lib if one isn't found
	;;   make-classpath aliases (-C) affect the classpath generation, options:
	;;     :extra-paths - vector of additional paths to add to the classpath
	;;     :classpath-overrides - map of lib to path that overrides the result of resolving deps

  ;; :aliases {
  ;;   :deps {:extra-deps {org.clojure/tools.deps.alpha {:mvn/version "0.8.661"}}}
  ;;   :test {:extra-paths ["test"]}
  ;; }

  ;; Provider attributes

  ;; :mvn/repos {
  ;;   "central" {:url "https://repo1.maven.org/maven2/"}
  ;;   "clojars" {:url "https://repo.clojars.org/"}
  ;; }
}


text:
{:lib io.github.clojure/tools.tools
 :coord {:git/tag "v0.3.1"
         :git/sha "859a6156802eaa49f2488ae087421091018586f7"}}


text:
set -gx NPM_DIR "$HOME/.npm-data"
set -gx NODE_PATH "$NPM_DIR/lib/node_modules:$NODE_PATH"
set -gx PATH ~/bin $NPM_DIR/bin $HOME/.local/bin $PATH

mkdir -p ~/bin $NPM_DIR/bin

set -gx fish_greeting ""

test -d ~/.linuxbrew && eval (~/.linuxbrew/bin/brew shellenv)
test -d /home/linuxbrew/.linuxbrew && eval (/home/linuxbrew/.linuxbrew/bin/brew shellenv)

if type -q nvim
  set -gx EDITOR nvim
  set -gx VISUAL nvim
  set -gx MANPAGER "nvim +Man!"
  alias vimdiff="nvim -d"
end

if type -q direnv
  direnv hook fish | source
end

set -gx FZF_DEFAULT_COMMAND "rg --files --hidden --follow -g \"!.git/\" 2> /dev/null"
set -gx FZF_CTRL_T_COMMAND $FZF_DEFAULT_COMMAND

# Other git aliases are in git config
alias g="git"
alias gg="g a .; and g c -a"
alias lg="lazygit"
alias n="nvim"

# Start an SSH agent if required, if not, connect to it.
initialise_ssh_agent

# Local config.
if [ -f ~/.config.fish ]
  source ~/.config.fish
end


text:
set SESSION_FILE ~/.bws-session

function bws
  if test -e $SESSION_FILE
    set -x BW_SESSION (cat $SESSION_FILE)
  end

  if ! bw status | grep unlocked > /dev/null
    set -x BW_SESSION (bw unlock --raw)
    if test $status -ne 0
      echo "bw unlock failed"
      return $status
    else
      echo $BW_SESSION > $SESSION_FILE
    end
  end

  bw $argv
end


text:
function sync
  # Early sudo because it's needed later.
  sudo echo "Updating everything!"

  topgrade --disable git_repos containers vim

  cd ~/repos/Olical/dotfiles
  git pull
  cd -

  nvim +AstroUpdatePackages +qa
end


text:
function fcd
  if set -q argv[1]
    set searchdir $argv[1]
  else
    set searchdir $HOME
  end

  set -l destdir (find $searchdir \( ! -regex '.*/\..*' \) ! -name __pycache__ -type d | fzf)

  if test -z "$destdir"
    return 1
  end

  cd $destdir
end


text:
function is_status_okay
  [ $status = 0 ]
end


text:
function branched
  for dir in $argv
    pushd $dir
    set -l branches (g b)

    if [ "$branches" != '* master' ]
      basename $dir
      g b
    end

    popd
  end
end


text:
function is_git_ahead
  set -l revs (git rev-list origin/(git_branch)..HEAD 2> /dev/null)
  [ "$revs" != "" ]
end


text:
function fish_user_key_bindings
  fish_vi_key_bindings

  if command -s fzf-share >/dev/null
    source (fzf-share)/key-bindings.fish
  end

  if type -q fzf_key_bindings
    fzf_key_bindings
  end

  bind -M insert -m default jk backward-char force-repaint
end


text:
# A lambda (λ) prompt.
# Green and red depending on exit status.
# Underlined if git status is dirty.
# Uppercase (Λ) if ahead of the remote.

function fish_prompt
  if is_status_okay
    set_color green
  else
    set_color red
  end

  if is_git_dirty
    set_color --underline
  end

  if is_git_ahead
    echo -n  'Λ'
  else
    echo -n  'λ'
  end

  set_color normal

  set jobs (job_count)
  if test $jobs -gt 0
    set_color magenta
    echo -n " %$jobs"
  end

  set_color normal
  echo -n ' '
end


text:
setenv SSH_ENV $HOME/.ssh/environment

function initialise_ssh_agent
  if [ -n "$SSH_AGENT_PID" ]
    ps -ef | grep $SSH_AGENT_PID | grep ssh-agent > /dev/null
    if [ $status -eq 0 ]
      test_identities
    end
  else
    if [ -f $SSH_ENV ]
      . $SSH_ENV > /dev/null
    end
    ps -ef | grep $SSH_AGENT_PID | grep -v grep | grep ssh-agent > /dev/null
    if [ $status -eq 0 ]
      test_identities
    else
      start_agent
    end
  end
end

function add_keys
  find ~/.ssh -type f -regex ".*\(id_rsa\|.*\.pem\)" | xargs ssh-add
end

function start_agent
  ssh-agent -c | sed 's/^echo/#echo/' > $SSH_ENV
  chmod 600 $SSH_ENV
  . $SSH_ENV > /dev/null
  add_keys
end

function test_identities
  ssh-add -l | grep "The agent has no identities" > /dev/null
  if [ $status -eq 0 ]
    add_keys
    if [ $status -eq 2 ]
      start_agent
      end
  end
end


text:
function is_git
  git symbolic-ref HEAD &> /dev/null
end


text:
function job_count
  jobs | tail -n +1 | wc -l
end


text:
function fish_mode_prompt --description 'Displays the current mode'
  if test "$fish_key_bindings" = "fish_vi_key_bindings"
    switch $fish_bind_mode
      case default
        set_color red
        echo 🅽
      case insert
        set_color green
        echo 🅸
      case replace_one
        set_color green
        echo 🆁
      case visual
        set_color brmagenta
        echo 🆅
    end
    set_color normal
    printf " "
  end
end


text:
function fish_right_prompt
  if is_git
    set_color yellow
    echo -n (git_branch)
    set_color normal
    echo -n ' '
  end

  set_color blue
  echo -n (basename (pwd | sed "s#$HOME#\~#"))
  set_color normal
end


text:
function gs
  cowsay -f ghostbusters 'gs is ghostscript, not git status!'
end


text:
function is_git_dirty
  is_git; and [ (git status | tail -n1) != "nothing to commit, working tree clean" ]
end


text:
function git_branch
  if is_git
    echo (git rev-parse --abbrev-ref HEAD 2> /dev/null)
  end
end


text:
-- [nfnl] Compiled from util.fnl by https://github.com/Olical/nfnl, do not edit.
local fun = require("user.vendor.fun")
local user = require("user")
local function autoload(name)
  local res = {["aniseed/autoload-enabled?"] = true, ["aniseed/autoload-module"] = false}
  local function ensure()
    if res["aniseed/autoload-module"] then
      return res["aniseed/autoload-module"]
    else
      local m = require(name)
      do end (res)["aniseed/autoload-module"] = m
      return m
    end
  end
  local function _2_(_t, ...)
    return ensure()(...)
  end
  local function _3_(_t, k)
    return ensure()[k]
  end
  local function _4_(_t, k, v)
    ensure()[k] = v
    return nil
  end
  return setmetatable(res, {__call = _2_, __index = _3_, __newindex = _4_})
end
local function last(xs)
  return fun.nth(fun.length(xs), xs)
end
local function reverse(xs)
  local len = fun.length(xs)
  local function _5_(n)
    return fun.nth((len - n), xs)
  end
  return fun.take(fun.length(xs), fun.tabulate(_5_))
end
local function dev_3f(plugin_name)
  return (1 == vim.fn.isdirectory((vim.fn.expand(user.lazy.dev.path) .. "/" .. plugin_name)))
end
local function tx(...)
  local args = {...}
  local len = fun.length(args)
  if ("table" == type(last(args))) then
    local function _6_(acc, n, v)
      acc[n] = v
      return acc
    end
    return fun.reduce(_6_, last(args), fun.zip(fun.range(1, len), fun.take((len - 1), args)))
  else
    return args
  end
end
return {autoload = autoload, ["dev?"] = dev_3f, tx = tx, last = last, reverse = reverse}


text:
{:g {:mapleader " "
     :maplocalleader ","
     :autoformat_enabled true
     :cmp_enabled true
     :autopairs_enabled true
     :diagnostics_mode 3 ;; Visibility of diagnostics (0=off, 1=only show in status line, 2=virtual text off, 3=all on)
     :icons_enabled true
     :ui_notifications_enabled true
     :resession_enabled true

     ;; https://neovide.dev/configuration.html
     :neovide_hide_mouse_when_typing true
     :neovide_cursor_animation_length 0.05
     :neovide_cursor_trail_size 0.5}
 :opt {:signcolumn :auto
       :exrc true
       :number false
       :relativenumber false
       :spell true
       :wrap true
       :sessionoptions "blank,curdir,folds,help,tabpages,winsize"
       :clipboard "unnamedplus"
       :list true
       :listchars "tab:▷ ,trail:·,extends:◣,precedes:◢,nbsp:○"
       :shortmess "atOIc"
       :cmdheight 1
       :guifont "FiraCode Nerd Font:h10"}}


text:
-- [nfnl] Compiled from mappings.fnl by https://github.com/Olical/nfnl, do not edit.
local uu = require("user.util")
return {n = {["<leader>bt"] = uu.tx(":%s/\\s\\+$//e<cr>", {desc = "Delete trailing whitespace"}), ["<leader>bn"] = uu.tx(":tabnew<cr>", {desc = "Create a new tab"})}, t = {["<S-Esc>"] = uu.tx("<c-\\><c-n>", {desc = "Enter Normal mode"})}}


text:
{:colorscheme :astrodark
 :diagnostics {:underline true
               :virtual_text true}
 :lazy {:defaults {:lazy false}
        :dev {:path "~/repos/Olical"}
        :performance {:rtp {:disabled_plugins ["tohtml" "netrwPlugin"]}}}
 :lsp {:formatting {:disabled {}
                    :format_on_save {:enabled true
                                     :ignore_filetypes {}}
                    :timeout_ms 1000}
       :servers {}}
 :polish (fn [])
 :updater {:auto_quit false
           :branch :nightly
           :channel :stable
           :commit nil
           :pin_plugins nil
           :remote :origin
           :remotes {}
           :show_changelog true
           :skip_prompts false
           :version :latest}}


text:
-- [nfnl] Compiled from init.fnl by https://github.com/Olical/nfnl, do not edit.
local function _1_()
end
return {colorscheme = "astrodark", diagnostics = {underline = true, virtual_text = true}, lazy = {defaults = {lazy = false}, dev = {path = "~/repos/Olical"}, performance = {rtp = {disabled_plugins = {"tohtml", "netrwPlugin"}}}}, lsp = {formatting = {disabled = {}, format_on_save = {enabled = true, ignore_filetypes = {}}, timeout_ms = 1000}, servers = {}}, polish = _1_, updater = {branch = "nightly", channel = "stable", commit = nil, pin_plugins = nil, remote = "origin", remotes = {}, show_changelog = true, version = "latest", skip_prompts = false, auto_quit = false}}


text:
(local uu (require :user.util))

{:n {"<leader>bt" (uu.tx ":%s/\\s\\+$//e<cr>" {:desc "Delete trailing whitespace"})
     "<leader>bn" (uu.tx ":tabnew<cr>" {:desc "Create a new tab"})}
 :t {"<S-Esc>" (uu.tx "<c-\\><c-n>" {:desc "Enter Normal mode"})}}


text:
(local fun (require :user.vendor.fun))
(local user (require :user))

(fn autoload [name]
  "Like autoload from Vim Script! A replacement for require that will load the
  module when you first use it. Use it in Aniseed module macros with:

  (module foo {autoload {foo x.y.foo}})

  It's a drop in replacement for require that should speed up your Neovim
  startup dramatically. Only works with table modules, if the module you're
  requiring is a function etc you need to use the normal require.

  Copied from https://github.com/Olical/aniseed"
  (let [res {:aniseed/autoload-enabled? true :aniseed/autoload-module false}]
    (fn ensure []
      (if (. res :aniseed/autoload-module)
          (. res :aniseed/autoload-module)
          (let [m (require name)]
            (tset res :aniseed/autoload-module m)
            m)))

    (setmetatable
      res
      {:__call (fn [_t ...]
                 ((ensure) ...))
       :__index (fn [_t k]
                  (. (ensure) k))
       :__newindex (fn [_t k v]
                     (tset (ensure) k v))})))

(fn last [xs]
  (fun.nth (fun.length xs) xs))

(fn reverse [xs]
  (let [len (fun.length xs)]
    (fun.take
      (fun.length xs)
      (fun.tabulate (fn [n]
                      (fun.nth (- len n) xs))))))

(fn dev? [plugin-name]
  "Do we have a repo cloned at the expected location? If so we can tell lazy to load that rather than install it from GitHub."
  (= 1 (vim.fn.isdirectory
         (.. (vim.fn.expand user.lazy.dev.path)
             "/" plugin-name))))

(fn tx [...]
  "Slightly nicer syntax for things like defining dependencies.
  Anything that relies on the {1 :foo :bar true} syntax can use this."
  (let [args [...]
        len (fun.length args)]
    (if (= :table (type (last args)))
      (fun.reduce
        (fn [acc n v]
          (tset acc n v)
          acc)
        (last args)
        (fun.zip (fun.range 1 len) (fun.take (- len 1) args)))
      args)))

{: autoload
 : dev?
 : tx
 : last
 : reverse}


text:
# AstroNvim User Configuration

A user configuration template for [AstroNvim](https://github.com/AstroNvim/AstroNvim).


text:
-- [nfnl] Compiled from plugins/autopairs.fnl by https://github.com/Olical/nfnl, do not edit.
local uu = require("user.util")
local fun = require("user.vendor.fun")
local lisps = {"scheme", "lisp", "clojure", "fennel"}
local function _1_()
  local autopairs = require("nvim-autopairs")
  do end (fun.head(autopairs.get_rules("'")))["not_filetypes"] = lisps
  fun.head(autopairs.get_rules("`"))["not_filetypes"] = lisps
  return nil
end
return uu.tx("windwp/nvim-autopairs", {init = _1_})


text:
-- [nfnl] Compiled from plugins/sexp.fnl by https://github.com/Olical/nfnl, do not edit.
local uu = require("user.util")
local function _1_()
  vim.g.sexp_filetypes = "clojure,scheme,lisp,timl,fennel,janet"
  return nil
end
return {uu.tx("guns/vim-sexp", {init = _1_}), uu.tx("tpope/vim-sexp-mappings-for-regular-people")}


text:
(local uu (require :user.util))

(uu.tx
  :NMAC427/guess-indent.nvim
  {:opts {:filetype_exclude [:clojure :fennel]}})


text:
(local uu (require :user.util))

(uu.tx
  :rcarriga/nvim-notify
  {:opts {:render :compact
          :stages :static}})


text:
[:AstroNvim/astrocommunity
 {:import :astrocommunity.pack.rust}
 {:import :astrocommunity.pack.lua}
 {:import :astrocommunity.pack.markdown}
 {:import :astrocommunity.pack.docker}
 {:import :astrocommunity.pack.bash}
 {:import :astrocommunity.pack.go}
 {:import :astrocommunity.pack.html-css}
 {:import :astrocommunity.pack.json}
 {:import :astrocommunity.pack.yaml}
 {:import :astrocommunity.pack.julia}
 {:import :astrocommunity.pack.python}
 {:import :astrocommunity.pack.terraform}
 {:import :astrocommunity.pack.typescript-all-in-one}]


text:
-- [nfnl] Compiled from plugins/neotree.fnl by https://github.com/Olical/nfnl, do not edit.
local uu = require("user.util")
return uu.tx("nvim-neo-tree/neo-tree.nvim", {opts = {filesystem = {filtered_items = {hide_by_pattern = {"/home/*/.config/nvim/lua/user/**.lua"}}, cwd_target = {current = "none"}}}})