# `gmbuilder`

This command line interface enables you to package your Node.js project into an executable that can be run even on devices without Node.js installed.

## Use Cases

- Make a commercial version of your application without sources
- Make a demo/evaluation/trial version of your app without sources
- Instantly make executables for other platforms (cross-compilation)
- Make some kind of self-extracting archive or installer
- No need to install Node.js and npm to run the packaged application
- No need to download hundreds of files via `npm install` to deploy your application. Deploy it as a single file
- Put your assets inside the executable to make it even more portable
- Test your app against new Node.js version without installing it

## Usage

```sh
npm install -g gmbuilder
```

The entrypoint of your project. It may be:

- Path to `package.json`. `Gmbuilder` will follow `bin` property of the specified `package.json` and use it as entry file.
- Path to directory. `Gmbuilder` will look for `package.json` in the specified directory.

### Targets

`gmbuilder` can generate executables for several target machines at a time. A canonical target consists of 3 elements, separated by dashes, for example `node18-macos-x64` or `node14-linux-arm64`:

- **nodeRange** (node8), node10, node12, node14, node16 or latest
- **platform** alpine, linux, linuxstatic, win, macos, (freebsd)
- **arch** x64, arm64, (armv6, armv7)

(element) is unsupported, but you may try to compile yourself.

You may omit any element (and specify just `node14` for example). The omitted elements will be taken from the current platform or system-wide Node.js installation (its version and arch). There is also an alias `host`, that means that all 3 elements are taken from the current platform/Node.js. By default, targets are `linux,macos,win` for the current Node.js version and arch.

If you want to generate executable for different architectures, note that by default `gmbuilder` has to run the executable of the target arch to generate bytecodes:

- Linux: configure binfmt with [QEMU](https://wiki.debian.org/QemuUserEmulation).
- macOS: possible to build `x64` on `arm64` with `Rosetta 2` but not opposite.
- Windows: possible to build `x64` on `arm64` with `x64 emulation` but not opposite.

`macos-arm64` is experimental. Be careful about the [mandatory code signing requirement](https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11_0_1-universal-apps-release-notes). The final executable has to be signed (ad-hoc signature is sufficient) with `codesign` utility of macOS (or `ldid` utility on Linux). Otherwise, the executable will be killed by the kernel and the end-user has no way to permit it to run at all. `gmbuilder` tries to ad-hoc sign the final executable. If necessary, you can replace this signature with your own trusted Apple Developer ID.

To be able to generate executables for all supported architectures and platforms, run `gmbuilder` on a Linux host with binfmt (`QEMU` emulation) configured and `ldid` installed.

### Config

During the packaging process, `gmbuilder` parses your sources, detects calls to `require`, traverses the dependencies of your project, and includes them into the executable. In most cases, you don't need to specify anything manually.

However, your code may have `require(variable)` calls (so-called non-literal arguments to `require`) or use non-javascript files (for example views, css, images etc).

```json
"pkg": {
  "scripts": "build/**/*.js",
  "assets": "views/**/*",
  "targets": [ "node14-linux-arm64" ],
  "outputPath": "dist", // output path for the executable
  "debug": false, // show more information during the packaging process [off]
  "bytecode": false, // skip or include bytecode generation and include source files as plain js
  "compress": "None", // [default=None] compression algorithm = Brotli or GZip
  "options": "expose-gc,max-heap-size=34" // bake v8 options into executable to run with them on
}
```

The above example will include everything in `assets/` and every .js file in `build/`, build only for `node14-linux-arm64`, and place the executable inside `dist/`.

You may also specify arrays of globs:

```
"assets": [ "assets/**/*", "images/**/*" ]
```

Just be sure to call `gmbuilder package.json` or `gmbuilder .` to make use of `package.json` configuration.

### Scripts

`scripts` is a [glob](https://github.com/sindresorhus/globby) or list of globs. Files specified as `scripts` will be compiled using `v8::ScriptCompiler` and placed

 into the executable without sources. They must conform to the JS standards of those Node.js versions you target (see [Targets](#targets)), i.e. be already transpiled.

### Assets

`assets` is a [glob](https://github.com/sindresorhus/globby) or list of globs. Files specified as `assets` will be packaged into the executable as raw content without modifications. Javascript files may also be specified as `assets`. Their sources will not be stripped as it improves execution performance of the files and simplifies debugging.

See also [Detecting assets in source code](#detecting-assets-in-source-code) and [Snapshot filesystem](#snapshot-filesystem).

### Options

Node.js application can be called with runtime options (belonging to Node.js or V8). To list them type `node --help` or `node --v8-options`.

You can "bake" these runtime options into the packaged application. The app will always run with the options turned on. Just remove `--` from the option name.

You can specify multiple options by joining them in a single string, comma (`,`) separated:

```sh
"options": "expose-gc"
"options": "max_old_space_size=4096"
"options": "max-old-space-size=1024,tls-min-v1.0,expose-gc"
```

### Debug

`gmbuilder` to get a log of the packaging process. If you have issues with some particular file (seems not packaged into executable), it may be useful to look through the log.

### Bytecode (reproducibility)

By default, your source code is precompiled to v8 bytecode before being written to the output file. To disable this feature, pass `bytecode : false` to `gmbuilder`.

#### Why would you want to do this?

If you need a reproducible build process where your executable hashes (e.g. md5, sha1, sha256, etc.) are the same value between builds. Because compiling bytecode is not deterministic (see [here](https://ui.adsabs.harvard.edu/abs/2019arXiv191003478C/abstract) or [here](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775)) it results in executables with differing hashed values. Disabling bytecode compilation allows a given input to always have the same output.

#### Why would you NOT want to do this?

While compiling to bytecode does not make your source code 100% secure, it does add a small layer of security/privacy/obscurity to your source code. Turning off bytecode compilation causes the raw source code to be written directly to the executable file. If you're on \*nix machine and would like an example, run `gmbuilder` with the `bytecode : false` flag, and use the GNU strings tool on the output. You then should be able to grep your source code.

#### Other considerations

Specifying `bytecode : false` will fail if there are any packages in your project that aren't explicitly marked as public by the `license` in their `package.json`. By default, `gmbuilder` will check the license of each package and make sure that stuff that isn't meant for the public will only be included as bytecode.

### Compression

Pass `compress : "Brotli"` or `compress : "GZip"` to `gmbuilder` to compress further the content of the files stored in the executable.

This option can reduce the size of the embedded file system by up to 60%.

The startup time of the application might be reduced slightly.

## Usage of packaged app

A command line call to packaged app `./app a b` is equivalent to `node app.js a b`

## Snapshot filesystem

During the packaging process, `gmbuilder` collects project files and places them into the executable. It is called a snapshot. At runtime, the packaged application has access to the snapshot filesystem where all those files reside.

Packaged files have `/snapshot/` prefix in their paths (or `C:\snapshot\` in Windows). If you used `gmbuilder /path/app.js` command line, then `__filename` value will be likely `/snapshot/path/app.js` at runtime. `__dirname` will be `/snapshot/path` as well. Here is the comparison table of path-related values:

| value                         | with `node`     | packaged                 | comments                       |
| ----------------------------- | --------------- | ------------------------ | ------------------------------ |
| \_\_filename                  | /project/app.js | /snapshot/project/app.js |
| \_\_dirname                   | /project        | /snapshot/project        |
| process.cwd()                 | /project        | /deploy                  | suppose the app is called ...  |
| process.execPath              | /usr/bin/nodejs | /deploy/app-x64          | `app-x64` and run in `/deploy` |
| process.argv[0]               | /usr/bin/nodejs | /deploy/app-x64          |
| process.argv[1]               | /project/app.js | /snapshot/project/app.js |
| process.pkg.entrypoint        | undefined       | /snapshot/project/app.js |
| process.pkg.defaultEntrypoint | undefined       | /snapshot/project/app.js |
| require.main.filename         | /project/app.js | /snapshot/project/app.js |

Hence, in order to make use of a file collected at packaging time (`require` a javascript file or serve an asset), you should take `__filename`, `__dirname`, `process.pkg.defaultEntrypoint` or `require.main.filename` as a base for your path calculations. For javascript files you can just `require` or `require.resolve` because they use the current `__dirname` by default. For assets use `path.join(__dirname, '../path/to/asset')`. Learn more about `path.join` in [Detecting assets in source code](#detecting-assets-in-source-code).

On the other hand, to access the real file system at runtime (pick up a user's external javascript plugin, json configuration, or even get a list of user's directories), you should take `process.cwd()` or `path.dirname(process.execPath)`.

## Detecting assets in source code

When `gmbuilder` encounters `path.join(__dirname, '../path/to

/asset')`, it automatically packages the file specified as an asset. See [Assets](#assets). Pay attention that `path.join` must have two arguments and the last one must be a string literal.

This way you may even avoid creating `gmbuilder` config for your project.

## Native addons

Native addons (`.node` files) use are supported. When `gmbuilder` encounters a `.node` file in a `require` call, it will package this like an asset. In some cases (like with the `bindings` package), the module path is generated dynamically and `gmbuilder` won't be able to detect it. In this case, you should add the `.node` file directly in the `assets` field in `package.json`.

The way Node.js requires native addon is different from a classic JS file. It needs to have a file on disk to load it, but `gmbuilder` only generates one file. To circumvent this, `gmbuilder` will create a temporary file on the disk. These files will stay on the disk after the process has exited and will be used again on the next process launch.

When a package, that contains a native module, is being installed, the native module is compiled against current system-wide Node.js version. Then, when you compile your project with `gmbuilder`, pay attention to `target` option. You should specify the same Node.js version as your system-wide Node.js to make the compiled executable compatible with `.node` files.

Note that fully static Node binaries are not capable of loading native bindings, so you may not use Node bindings with `linuxstatic`.

## Troubleshooting

### Error: ENOENT: no such file or directory, uv_chdir

This error can be caused by deleting the directory the application is run from. Or, generally, deleting `process.cwd()` directory when the application is running.

### Error: ERR_INSPECTOR_NOT_AVAILABLE

This error can be caused by using `NODE_OPTIONS` variable to force to run `node` with the debug mode enabled. Debugging options are disallowed, as **gmbuilder** executables are usually used for production environments.

### Error: require(...).internalModuleStat is not a function

This error can be caused by using `NODE_OPTIONS` variable with some bootstrap or `node` options causing conflicts with **gmbuilder**. Some IDEs, such as **VS Code**, may add this env variable automatically.

You could check on **Unix systems** (Linux/macOS) in `bash`:

```bash
$ printenv | grep NODE
```

## Advanced

### exploring virtual file system embedded in debug mode

When you are using the `debug` flag when building your executable, `gmbuilder` add the ability to display the content of the virtual file system and the symlink table on the console when the application starts, providing that the environment variable DEBUG_PKG is set. This feature can be useful to inspect if symlinks are correctly handled, and check that all the required files for your application are properly incorporated into the final executable.

Note: make sure not to use debug flag in production.
