Compare commits

..

26 Commits

Author SHA1 Message Date
calebboyd 563aa97a89 fix: add bundle directly to third_party_main 2017-08-13 15:28:32 -05:00
calebboyd 88a46dc730 Merge branch 'cb-wip' 2017-08-03 23:00:51 -05:00
calebboyd 1a6b113d6e chore: wip bundling 2017-08-03 22:59:15 -05:00
calebboyd d375c8f214 chore: indicate beta in readme 2017-07-20 16:20:40 -05:00
calebboyd 15d919c302 chore: docs and ts fixes 2017-07-18 11:08:45 -05:00
calebboyd 6adf3c82c3 chore: update fusebox example version 2017-07-17 09:38:40 -05:00
calebboyd 73af22cc92 chore: publish nexe@beta 2017-07-16 22:43:02 -05:00
calebboyd 844d34b29a chore(readme): remove bundling point 2017-07-16 22:39:16 -05:00
calebboyd 548e6454ef chore: typescript and native module bundles 2017-07-16 21:40:23 -05:00
calebboyd ce2c4cefd0 wip: bundling work 2017-07-16 21:40:23 -05:00
James M. Greene ac64063916 Note the minimum Node.js engine required
Fixes #341
2017-07-15 20:01:56 -05:00
James M. Greene 249ee50130 README clarifications and consistency polishing 2017-06-29 11:46:14 -05:00
James M. Greene 76ec6a8be3 Prepend the Python path for Windows for overriding 2017-06-28 17:57:09 -05:00
calebboyd cac2759d3a chore: pr feedback 2017-06-26 20:01:53 -05:00
calebboyd 811f460763 feat: resources 2017-06-26 20:01:53 -05:00
calebboyd e12eb8b289 feat: instant builds 2017-06-26 20:01:53 -05:00
calebboyd 1f35fe498e chore: switch to tar-fs for better stream support 2017-06-26 20:01:53 -05:00
calebboyd ca7874c854 chore: misc cleanup 2017-06-26 20:01:53 -05:00
calebboyd 95e7971a96 chore: remove compiled files from repo 2017-06-26 20:01:53 -05:00
calebboyd f8151854f1 chore: update yarn.lock 2017-06-26 20:01:53 -05:00
calebboyd ac4ca3fe56 chore: default exports 2017-06-26 20:01:53 -05:00
calebboyd 0d67b1dd4c chore: babel + standard 2017-06-26 20:01:53 -05:00
calebboyd b10b3d268d fix: ensure python path is qouoted on windows #210 2017-06-26 20:01:53 -05:00
calebboyd 40f1e6056f feat: add isNexe which detects a nexe environment 2017-06-26 20:01:53 -05:00
calebboyd ec57ed508f fix: add missing depenencies 2017-06-26 20:01:53 -05:00
calebboyd 7668d7b471 feat: next 2017-06-26 20:01:53 -05:00
46 changed files with 4527 additions and 3405 deletions
+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace=true
insert_final_newline = true
+6 -11
View File
@@ -1,15 +1,10 @@
.DS_Store
temp/
coverage
node_modules
npm-debug.log
test/**/src
test/**/out.nex
test/**/test.nex
test/**/tmp
test/**/node_modules
test/**/*.map
test/src
test/test.log
test-results.xml
src
*.log
*.exe
.idea
.nexe
lib
-3
View File
@@ -1,3 +0,0 @@
node_modules
test
src
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"esbenp.prettier-vscode"
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"]
}
+40 -27
View File
@@ -1,51 +1,64 @@
# Nexe ChangeLog
## 2017-05-29, Version v2.0.0-beta.1, @calebboyd
Nexe 2.0 is a rewrite to enable some new features. These include:
* Quick Builds!
* Userland build patches
* New Resource access
* stdin and stdout interfaces
* Opt-in bundling
### Breaking Changes
* New options -- Please see the [readme](README.md#options)
* Bundling is no longer enabled by default
* To access included resources `fs.readFile` and `fs.readFileSync` should be used
## 2015-02-20, Version v0.3.7, @jaredallard
### Noteable Changes
* Fixed #103.
* Made not-available require not a fatal error.
* Stub and system to ignore certain requires.
* Added 'sys' to ignore list.
* We have a gitter!
* Gave win32 a 100 length progress bar.
* Fixed #103.
* Made not-available require not a fatal error.
* Stub and system to ignore certain requires.
* Added 'sys' to ignore list.
* We have a gitter!
* Gave win32 a 100 length progress bar.
### Commits
* [**2cacd83**] Update README.md (@crcn)
* [**0e90ac9**] Update README.md (@crcn)
* [**54967d1**] Added Gitter badge (@jaredallard)
* [**bb489a3**] Fixes #98 by giving win32 a 100 length progress bar. (@jaredallard)
* [**39665a8**] Lighter weight way to accomplish the exclusion of the sys module (@CKarper)
* [**5aca22d**] This handles embedding 'bin' scripts with shebang interpreter... (@CKarper)
* [**e79b0fb**] Stub to ignore require('sys') (@CKarper)
* [**2cacd83**] Update README.md (@crcn)
* [**0e90ac9**] Update README.md (@crcn)
* [**54967d1**] Added Gitter badge (@jaredallard)
* [**bb489a3**] Fixes #98 by giving win32 a 100 length progress bar. (@jaredallard)
* [**39665a8**] Lighter weight way to accomplish the exclusion of the sys module (@CKarper)
* [**5aca22d**] This handles embedding 'bin' scripts with shebang interpreter... (@CKarper)
* [**e79b0fb**] Stub to ignore require('sys') (@CKarper)
## 2015-02-15, Version v0.3.6, @jaredallard
### Noteable Changes
* Now support .json in require.
* Fixed a major --python flag bug.
* Now support .json in require.
* Fixed a major --python flag bug.
### Commits
* [**cac6986**] V bump to solve critical error. (@jaredallard)
* [**b040337**] Fixes #99, resolves #97 by warning on missing file. New examples... (@jaredallard)
* [**ad4da1d**] Support .json extensions in require() resolution (@CKarper)
* [**cac6986**] V bump to solve critical error. (@jaredallard)
* [**b040337**] Fixes #99, resolves #97 by warning on missing file. New examples... (@jaredallard)
* [**ad4da1d**] Support .json extensions in require() resolution (@CKarper)
## 2015-02-14, Version v0.3.5, @jaredallard
### Noteable Changes
* Added new flag: `--python </path/to/python>`
* Added resourceFiles option which allows embedding files and getting them via embed.js
* Updated a bunch of the dependencies.
* `process.argv` is now consistent in a child fork case.
* Added `child_process.fork` support.
* Added new collaborators:
* Jared Allard (@jaredallard)
* Added new flag: `--python </path/to/python>`
* Added resourceFiles option which allows embedding files and getting them via embed.js
* Updated a bunch of the dependencies.
* `process.argv` is now consistent in a child fork case.
* Added `child_process.fork` support.
* Added new collaborators:
* Jared Allard (@jaredallard)
### Commits
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013 Craig Condon
Copyright (c) 2013-2017 Nexe Contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+154 -207
View File
@@ -7,243 +7,190 @@
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/l/nexe.svg" alt="License"></a>
</p>
<p align="center"><code>npm i nexe@beta -g</code></p>
<p align="center">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
![screenshot](https://cloud.githubusercontent.com/assets/2391349/23597575/e333884c-01e8-11e7-9fbf-3a83088bc748.png)
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/5818726/26533446/ce19ee5a-43de-11e7-9540-caf7ebd93370.gif"/>
</p>
## Supporting nexe
nexe is a MIT license project. Please consider joining our [backers](https://github.com/nexe/nexe/BACKERS.md) by contributing to [nexe's Patreon](https://patreon.com/user?u=5264722).
### Motivation
## Motivation and Features
- Supports production ready, ([securable](#security)) builds
- Ability to run multiple applications with *different* node.js runtimes.
- Distributable binaries without needing node / npm.
- Starts faster.
- Distribute binaries without needing node / npm.
- Idempotent builds
- Start and deploy faster.
- Lockdown specific application versions, and easily rollback.
- Faster deployments.
- Flexible build pipeline
- Cross platform builds
## Building Requirements
## Usage (Beta)
- Linux / Mac / BSD / Windows
- Python 2.6 or 2.7 (use --python if not in PATH)
- Windows: Visual Studio 2010+
*Note: V2 API is still subject to change. * For v1 see [V1-EOL](https://github.com/nexe/nexe/tree/V1-EOL)
## Caveats
- Existing application bundle:
### Doesn't support native modules
`nexe -i ./my-app-bundle.js -o ./my-app.exe`
- Use the techniques below for working around dynamic require statements to exclude the module from the bundling, and deploy along side the executable in a node_module folder so your app can find it. Note: On windows you may need to have your app be named node.exe if .node file depends on node.
- stdin interface
### Doesn't support dynamic require statements
`rollup -c | nexe --resource ./public/**/* -o my-app.exe`
For more CLI options see: `nexe --help`
## Including Additional Resources
Additional resources can be added to the binary by passing `-r glob/pattern/**/*`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`
## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. These options are supported out of the box, and subsequent builds with compatible options will result in instant build times.
Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
## Node.js API
Using Nexe Programatically
#### Example
Such As:
```javascript
var x = require(someVar);
```
In this case nexe won't bundle the file
```javascript
var x;
if (someCheck) {
x = require("./ver1.js");
} else {
x = require("./ver2.js");
}
```
In this case nexe will bundle both files.
Workarounds:
1) for dynamic requires that you want bundled add the following into your project
```javascript
var dummyToForceIncludeForBundle = false;
if (dummyToForceIncludeForBundle) {
require("./loadedDynamicallyLater.js");
// ...
}
```
this will trick the bundler into including them.
2) for dynamic files getting included that you don't want to be
```javascript
var moduleName = "./ver2.js";
if (someCheck) {
moduleName = "./ver1.js";
}
var x = require(moduleName);
```
Note: neither file will be bundled.
Using these two techniques you can change your application code so modules are not bundles, and generate a includes.js file as part of your build process so that the right files get bundled for your build configuration.
### &#95;&#95;dirname
Once the module is bundled it is part of the executable. &#95;&#95;dirname is therefore the executable dir (process.execPath). Thus if you put resources on a relative path from the the executable your app will be able to access them.
If you had a data file at `/dev/myNodeApp/stateManager/handler/data/some.csv`
and a file at `/dev/myNodeApp/stateManager/handler/loader.js`
```javascript
module.exports = fw.readFileSync(path.join(__dirname, "./data/some.csv"));
```
You would need to deploy some.csv in a sub dir `data/` along side your executable
There are potential use cases for &#95;&#95;dirname where the executable path is not the correct substitution, and could result in a silent error (possibly even in a dependency that you are unaware of).
Note: &#95;&#95;filename will be 'undefined'
### child_process.fork
child_process.spawn works is unmodified, but child_process.fork will make an attempt to launch a new instance of your executable and run the bundled module.
## Installation
Via NPM:
```bash
npm install nexe [-g]
```
Or git:
```bash
git clone https://github.com/jaredallard/nexe.git
```
### CLI Usage
```text
Usage: nexe -i [sources] -o [binary] [options]
Options:
-i, --input The entry javascript files [default: cwd]
-o, --output The output binary [default: out.nex]
-r, --runtime The node.js runtime to use [default: "latest"]
-t, --temp The path to store node.js sources [default: ./tmp/nexe]
-f, --flags Don't parse node and v8 flags, pass through app flags [default: false]
-v, --version Display version number
-p, --python Set path of python to use. [default: "python"]
-F, --framework Set the framework to use. [default: "nodejs"]
```
### Code Usage
```javascript
var nexe = require('nexe');
const nexe = require('nexe')
nexe.compile({
input: 'input.js', // where the input file is
output: 'path/to/bin', // where to output the compiled binary
nodeVersion: '5.5.0', // node version
nodeTempDir: 'src', // where to store node source.
nodeConfigureArgs: ['opt', 'val'], // for all your configure arg needs.
nodeMakeArgs: ["-j", "4"], // when you want to control the make process.
nodeVCBuildArgs: ["nosign", "x64"], // when you want to control the make process for windows.
// By default "nosign" option will be specified
// You can check all available options and its default values here:
// https://github.com/nodejs/node/blob/master/vcbuild.bat
python: 'path/to/python', // for non-standard python setups. Or python 3.x forced ones.
resourceFiles: [ 'path/to/a/file' ], // array of files to embed.
resourceRoot: [ 'path/' ], // where to embed the resourceFiles.
flags: true, // use this for applications that need command line flags.
jsFlags: "--use_strict", // v8 flags
startupSnapshot: 'path/to/snapshot.js', // when you want to specify a script to be
// added to V8's startup snapshot. This V8
// feature deserializes a heap to save startup time.
// More information in this blog post:
// http://v8project.blogspot.de/2015/09/custom-startup-snapshots.html
framework: "node" // node, nodejs, or iojs
}, function(err) {
if(err) {
return console.log(err);
}
// do whatever
});
input: './my-app-bundle.js'
output: './my-app.exe'
patches: [
async (compiler, next) => {
await compiler.setFileContentsAsync(
'lib/new-native-module.js',
'module.exports = 42'
)
return next()
}
]
}).then(() => {
console.log('success')
})
```
### package.json inclusion
### `options`
As of 0.4.0 you can now embed nexe options into package.json. Note that this Format
is still in works, so it is likely to change.
- #### `build: boolean`
- Build node from source (required in beta)
- #### `input: string`
- Input bundle file path
- default: stdin or the current directory's main file (package.json)
- #### `output: string`
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string`
- Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'`
- default: `[process.platform, process.arch, process.version.slice(1)].join('-')`
- #### `bundle: string`
- Path to a custom bundling function. When executed it should either return a fusebox producer, or a string.
- If it is a string it will be required and the export `nexeBundle` will be used
- Defaults to the internal fuse-box configuration
- #### `name: string`
- Module friendly name of the application
- default: basename of the input file, or `nexe_${Date.now()}`
- #### `version: string`
- The Node version you're building for
- default: `process.version.slice(1)`
- #### `python: string`
- On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed
- default: `null`
- #### `flags: Array<string>`
- Array of node runtime flags to build node with.
- Example: `['--expose-gc']`
- default: `[]`
- #### `configure: Array<string>`
- Array of arguments for the node build configure step
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: Array<string>`
- Array of arguments for the node build make step
- default: `[]`
- #### `vcBuild: Array<string>`
- Array of arguments for the node build step on windows
- default: `['nosign', 'release']`
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
- #### `resources: Array<string>`
- Array of globs with files to include in the build
- Example: `['./public/**/*']`
- default: `[]`
- #### `temp: string`
- Path to use for storing nexe's build files
- Override in the env with `NEXE_TEMP`
- default: `./.nexe` in the cwd
- #### `ico: string`
- Path to a user provided icon to be used (Windows only).
- #### `rc: object`
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
- Example: `{ CompanyName: "ACME Corp" }`
- default: `{}`
- #### `clean: boolean`
- If included, nexe will remove temporary files for accompanying configuration and exit
- #### `enableNodeCli: boolean`
- Enable the original Node CLI (will prevent application cli from working)
- default: `false`
- #### `sourceUrl: string`
- Provide an alternate url for the node source. Should be a `.tar.gz`
- #### `loglevel: string`
- Set the loglevel, info, silent, or verbose
- default: `'info'`
- #### `padding`
- Advanced option for controlling the size available in the executable.
- It must be larger than the bundle + resources in bytes
- default: 3, 6, 9, 16, 25, or 40 MB sizes are selected automatically.
- #### `patches: Array<NexePatch>`
- Userland patches for patching or modifying node source
- default: `[]`
```json
"nexe": {
"input": "./bin/nexe",
"output": "nexe^$",
"temp": "src",
"browserify": {
"requires": [],
"excludes": [],
"paths": []
},
"runtime": {
"framework": "node",
"version": "5.6.0",
"js-flags": "--use_strict",
"ignoreFlags": true
}
}
```
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
Notes:
A patch is just a middleware function that takes two arguments, the `compiler`, and `next`. The compiler is described below, and `next` ensures that the pipeline continues. Its invocation should always be awaited or returned to ensure correct behavior.
* output: can use ^$ for platform specific file extension
* js-flags: this is also known as v8 flags, and supports *all* v8 flags.
For examples, see the built in patches: [src/patches](src/patches)
### Browserify Require Issues
### `NexeCompiler`
If you have requires that aren't resolving well, you can do two things.
- `setFileContentsAsync(filename: string, contents: string): Promise<void>`
- Quickly set a file's contents within the downloaded Node.js source.
- `replaceInFileAsync(filename: string, ...replaceArgs): Promise<void>`
- Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace`
- `readFileAsync(filename: string): Promise<SourceFile>`
- Access (or create) a file within the downloaded Node.js source.
- `files: Array<SourceFile>`
- The cache of currently read, modified, or created files within the downloaded Node.js source.
Try adding it to `nexe.browserify.requires` in your `package.json`
#### `SourceFile`
- `contents: string`
- `filename: string`
```json
"nexe": {
.......
"browserify": {
"requires": [
{
"file": "myfile.js",
"expose": "mymodule"
},
"mymodule.js"
],
"excludes": [],
"paths": []
},
.......
}
```
Any modifications made to `SourceFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
Or, if that doesn't work (it tends to not work sometimes), you can try altering
browserify.paths like so:
### Native Modules
```json
"nexe": {
.......
"browserify": {
"requires": []
"excludes": [],
"paths": ["/path/to/my/loc"]
},
.......
}
```
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
Take a look at the [example](examples/native-build/build.js)
If it *still* doesn't work, file a bug with what you tried! (also try using `nexe@0.4.2`)
Future: Implement support `node-pre-gyp#find`.
## Security
A common use case for Nexe is production deployment. When distributing executables it is important to [sign](https://en.wikipedia.org/wiki/Code_signing) them before distributing. Nexe was designed specifically to not mangle the binary it produces, this allows the checksum and signature of the size and location offsets to be maintained through the code signing process.
## Maintainers
* __Jared Allard__ ([@jaredallard](https://github.com/jaredallard)) &lt;[jaredallard@outlook.com](mailto:jaredallard@outlook.com)&gt; (Active)
* __Christopher Karper__ ([@ckarper](https://github.com/CKarper)) &lt;[Christopher.Karper@gmail.com](mailto:Christopher.Karper@gmail.com)&gt; (Active)
* __Craig Condon__ ([@crcn](https://github.com/crcn)) &lt;[craig.j.condon@gmail.com](mailto:craig.j.condon@gmail.com)&gt; (Old Project Owner)
[![Jared Allard](https://avatars.githubusercontent.com/u/2391349?s=130)](https://jaredallard.me/) | [![Caleb Boyd](https://avatars.githubusercontent.com/u/5818726?s=130)](https://github.com/calebboyd) | [![Christopher Karper](https://avatars.githubusercontent.com/u/653156?s=130)](https://github.com/ckarper) | [![Dustin Greif](https://avatars.githubusercontent.com/u/3026298?s=130)](https://github.com/dgreif) |
---|---|---|---
[Jared Allard](https://github.com/jaredallard) | [Caleb Boyd](http://github.com/calebboyd) | [Christopher Karper](https://github.com/ckarper) | [Dustin Greif](https://github.com/dgreif) |
### Former
- [Craig Condon](http://crcn.codes/)
-115
View File
@@ -1,115 +0,0 @@
#!/usr/bin/env node
var path = require('path'),
fs = require('fs'),
nexe = require('../lib'),
_log = require("../lib/log"),
cli = require('yargs').
usage('Usage: $0 -i [sources] -o [binary]').
options('i', {
demand: true,
alias: 'input',
desc: 'The entry javascript files',
default: process.cwd()
}).
options('o', {
demand: true,
alias: 'output',
desc: 'The output binary',
default: "out.nex"
}).
options('r', {
alias: 'runtime',
default: 'latest',
description: 'The node.js runtime to use'
}).
options('t', {
alias: 'temp',
default: './tmp/nexe',
description: 'The path to store node.js sources'
}).
options('f', {
alias: 'flags',
description: 'Don\'t parse node and v8 flags, pass through app flags',
default: false
}).
options('d', {
alias: 'debug',
default: false,
description: 'generate and use source maps, large file size'
}).
options('j', {
alias: 'jsFlags',
default: false,
description: 'v8 flags for runtime'
}).
options('v', {
alias: 'version',
description: 'Display version number'
}).
options('p', {
alias: 'python',
description: 'Set path of python to use.',
default: 'python'
}).
options('F', {
alias: 'framework',
description: 'Set the framework to use',
default: 'nodejs'
});
var argv = cli.argv;
if (argv.h || argv.help) {
cli.showHelp();
process.exit();
} else if (argv.v || argv.version) {
var pkginfo = require('../package.json');
console.log(pkginfo.version);
process.exit();
}
/**
* TODO: Support network shares?
**/
function toAbsolute(pt) {
//already resolved?
try{
fs.accessSync(pt,fs.F_OK);
return pt;
}catch(e){}
if (pt.substr(0, 1) == "/") return pt; // for *nix "/"
if (pt.substr(0, 2) == "\\\\") return pt; // for windows "\\"
if (pt.substr(1, 3) == ":/") return pt; // for windows "c:/"
// otherwise...
return path.join(process.cwd(), pt);
}
if (fs.lstatSync(argv.i).isDirectory()) {
opts = nexe.package(path.join(argv.i, "package.json"), argv);
nexe.compile(opts, function(error) {
if (error) {
return console.log(error.message);
}
});
} else {
/**
* Call the core
**/
nexe.compile({
input: require.resolve(toAbsolute(argv.i)),
output: toAbsolute(argv.o),
flags: argv.f,
nodeVersion: argv.r,
python: argv.python,
nodeTempDir: toAbsolute(argv.t),
framework: argv.F
},
function(error) {
if (error) {
return console.log(error.message);
}
}
);
}
-8
View File
@@ -1,8 +0,0 @@
machine:
node:
version: 5.7.0
test:
override:
- npm test:
timeout: 5400
+23
View File
@@ -0,0 +1,23 @@
const { FuseBox } = require('fuse-box')
const nexe = '../..'
const { NativeModulePlugin } = require(nexe + '/lib/bundling/fuse')
const fuse = FuseBox.init({
homeDir: './',
cache: false,
log: true,
debug: true,
output: '$name.js',
plugins: [
new NativeModulePlugin({
zmq: {
additionalFiles: [
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
]
}
})
]
})
fuse.bundle('app')
.target('electron')
.instructions(`> index.js`)
fuse.run()
+19
View File
@@ -0,0 +1,19 @@
var zmq = require('zmq')
var pub = zmq.socket('pub')
pub.bindSync('tcp://127.0.0.1:3000')
console.log('Publisher bound to port 3000')
setInterval(function(){
console.log('sending a multipart message envelope')
pub.send(['kitty cats', 'meow!'])
}, 2500)
console.log('global', global.require.main)
var sub = zmq.socket('sub')
sub.connect('tcp://127.0.0.1:3000')
sub.subscribe('kitty cats')
console.log('Subscriber connected to port 3000')
sub.on('message', function(topic, message) {
console.log('received a message related to:', topic.toString(), 'containing message:', message.toString())
})
+18
View File
@@ -0,0 +1,18 @@
{
"name": "native-build",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "node build",
"build-windows": "nexe -i app.js -o app.exe"
},
"author": "",
"license": "ISC",
"dependencies": {
"zmq": "^2.15.3"
},
"devDependencies": {
"fuse-box": "^2.2.1-beta.7"
}
}
Executable
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env node
const nexe = require('./lib/nexe')
module.exports = nexe
if (require.main === module) {
nexe.compile(nexe.argv).catch((e) => {
process.stderr.write(e.message, () => process.exit(1))
})
}
-137
View File
@@ -1,137 +0,0 @@
/**
* Copyright (c) 2013 Craig Condon
* Copyright (c) 2015-2016 Jared Allard
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
**/
'use strict';
let browserify = require('browserify'),
path = require("path"),
spawn = require('child_process').spawn,
insertGlobals = require('insert-module-globals'),
fs = require("fs"),
async = require("async"),
_log = require("./log");
/**
* User browserify to create a "packed" file.
*
* @param {string} input - input file
* @param {string} nc - node compiler dir
* @param {array} options - nexe options
* @param {function} complete - next function to call (async)
**/
function bundle(input, nc, options, complete) {
const bundlePath = path.join(nc, "lib", "nexe.js");
const mapfile = options.output+'.map';
let ws = fs.createWriteStream(bundlePath);
const igv = '__filename,__dirname,_process';
let insertGlobalVars = { isNexe: true },
wantedGlobalVars = igv.split(',');
// parse insertGlobalVars.
Object.keys(insertGlobals.vars).forEach(function (x) {
if (wantedGlobalVars.indexOf(x) === -1) {
insertGlobalVars[x] = undefined;
}
});
let paths = [path.join(nc, 'lib')];
if(options.browserifyPaths) {
paths = paths.concat(options.browserifyPaths);
}
_log('executing browserify via API');
let bproc = browserify([input], {
debug: options.debug,
commondir: false,
paths: paths,
builtins: false,
insertGlobalVars: insertGlobalVars,
detectGlobals: true,
browserField: false
});
if (options.browserifyExcludes && Array.isArray(options.browserifyExcludes)) {
for (let i = 0; i < options.browserifyExcludes.length; i++) {
let lib = options.browserifyExcludes[i];
_log('Excluding \'%s\' from browserify bundle', lib);
bproc.exclude(lib);
}
}
// copy the excludes code for requires for now.
if (options.browserifyRequires && Array.isArray(options.browserifyRequires)) {
for (let i = 0; i < options.browserifyRequires.length; i++) {
let lib = options.browserifyRequires[i];
let name = lib.file || lib; // for object format.
// if `lib` is object then fetch all params without `file`
// otherwise returns empty object
let opts = (lib instanceof Object) && Object.keys(lib)
.filter((key) => key !== 'file')
.reduce((acc, key) => { return acc[key] = lib[key], acc }, {})
|| {};
_log('Force including \'%s\' in browserify bundle', name);
bproc.require(lib, opts);
}
}
if(options.debug) {
bproc.require(require.resolve('source-map-support'))
}
let bprocbun = bproc.bundle() // bundle
.pipe(ws) // pipe to file
// error on require errors, still can't contionue. ffs browserify
bprocbun.on('error', function(err) {
_log('error', '[browserify] '+err);
});
ws.on('error', function(err) {
console.log(err);
_log('error', 'Failed to save stdout to disk');
process.exit(1);
})
ws.on('close', function() {
var source = fs.readFileSync(bundlePath, 'utf8');
source = source.replace(/[^\x00-\x7F]/g, "");
// write the source modified to nexe.js
fs.writeFile(bundlePath, source, 'utf8', function(err) {
if (err) {
_log('error', 'failed to save source');
process.exit(1);
}
complete();
});
});
}
module.exports = bundle;
-73
View File
@@ -1,73 +0,0 @@
/**
* Copyright (c) 2013 Craig Condon
* Copyright (c) 2015-2016 Jared Allard
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
**/
'use strict';
let path = require("path"),
fs = require("fs");
/**
* Accessort for embed
**/
const accessor = function(key) {
if (embeddedFiles.hasOwnProperty(key)) {
return new Buffer(embeddedFiles[key], 'base64');
} else {
//file was not embedded, throw err.
throw new Error('Embedded file not found');
}
}
/**
* Embed files.
*
* @param {array} resourceFiles - array of files to embed.
* @param {string} resourceRoot - root of resources.
* @param {function} compelte - callback
**/
function embed(resourceFiles, resourceRoot, options, complete) {
const encode = function(filePath) {
return fs.readFileSync(filePath).toString('base64');
}
resourceFiles = resourceFiles || [];
resourceRoot = resourceRoot || "";
if (!Array.isArray(resourceFiles)) {
throw new Error("Bad Argument: resourceFiles is not an array");
}
let buffer = "var embeddedFiles = {\n";
for (let i = 0; i < resourceFiles.length; ++i) {
buffer += JSON.stringify(path.relative(resourceRoot, resourceFiles[i])) + ': "';
buffer += encode(resourceFiles[i]) + '",\n';
}
buffer += "\n};\n\nmodule.exports.keys = function () { return Object.keys(embeddedFiles); }\n\nmodule.exports.get = ";
buffer += accessor.toString();
complete(null, buffer);
}
module.exports = embed;
-1052
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
module.exports = require("./exe");
-57
View File
@@ -1,57 +0,0 @@
/**
* Copyright (c) 2013 Craig Condon
* Copyright (c) 2015-2016 Jared Allard
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
**/
var colors = require('colors');
/**
* standard output, takes 3 different types.
* log, error, and warn
*
* @param {any} arguments - Text to output.
* @return undefined
**/
function _log () {
var args = Array.prototype.slice.call(arguments, 0),
level = args.shift();
if (!~["log", "error", "warn"].indexOf(level)) {
args.unshift(level);
level = "log";
}
if(level == "log") {
args[0] = "----> " + args[0];
} else if(level == "error") {
args[0] = "....> " + colors.red("ERROR: ") + args[0]
} else if(level == "warn") {
args[0] = "....> " + colors.yellow("WARNING: ") + args[0]
}
console[level].apply(console, args);
}
// export the log function, for require()
module.exports = _log;
-63
View File
@@ -1,63 +0,0 @@
/**
* Copyright (c) 2013 Craig Condon
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT HOLDERS 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.
*
**/
var async = require("async"),
fs = require("fs"),
_log = require("./log");
/**
* Monkey patch a file.
*
* @param {string} filePath - path to file.
* @param {function} monkeyPatched - function to process contents
* @param {function} processor - TODO: detail what this is
* @param {function} complete - callback
*
* @return undefined
*/
function _monkeypatch (filePath, monkeyPatched, processor, complete) {
async.waterfall([
function read (next) {
fs.readFile(filePath, "utf8", next);
},
// TODO - need to parse gyp file - this is a bit hacker
function monkeypatch (content, next) {
if (monkeyPatched(content)) return complete();
_log("monkey patch %s", filePath);
processor(content, next);
},
function write (content, next) {
fs.writeFile(filePath, content, "utf8", next);
}
], complete);
}
// export the function for require()
module.exports = _monkeypatch;
+2822
View File
File diff suppressed because it is too large Load Diff
+43 -40
View File
@@ -1,55 +1,58 @@
{
"author": "Jared Allard <jaredallard@outlook.com>",
"name": "nexe",
"description": "create single executables out of your [node/io].js applications",
"description": "Create a single executable out of your Node.js application",
"license": "MIT",
"version": "1.1.3",
"version": "2.0.0-beta.4",
"contributors": [
{
"name": "Criag Condon",
"email": "craig.j.condon@gmail.com",
"url": "http://crcn.io/"
}
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>"
],
"scripts": {
"test": "echo node: $(node -v) && node_modules/.bin/mocha --reporter mocha-circleci-reporter test/test.js"
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm run build",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
"build": "tsc --declaration"
},
"repository": {
"type": "git",
"url": "git://github.com/jaredallard/nexe.git"
"url": "git://github.com/nexe/nexe.git"
},
"main": "./lib/index.js",
"dependencies": {
"async": "^1.5.2",
"browserify": "^13.0.0",
"colors": "^1.1.2",
"glob": "^7.0.0",
"gunzip-maybe": "^1.3.1",
"insert-module-globals": "^7.0.1",
"mkdirp": "^0.5.1",
"module-deps": "^4.0.5",
"ncp": "^2.0.0",
"progress": "^1.1.8",
"request": "^2.67.0",
"tar-stream": "^1.3.1",
"yargs": "^4.2.0"
},
"nexe": {
"input": "./bin/nexe",
"output": "nexe^$",
"temp": "src",
"runtime": {
"framework": "nodejs",
"version": "5.5.0",
"ignoreFlags": true
}
},
"preferGlobal": true,
"files": [
"lib"
],
"main": "index.js",
"bin": {
"nexe": "bin/nexe"
"nexe": "index.js"
},
"engines": {
"node": ">=6"
},
"dependencies": {
"app-builder": "^5.1.0",
"bluebird": "^3.5.0",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "^2.2.1",
"globby": "^6.1.0",
"memory-fs": "^0.4.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nigel": "^2.0.2",
"ora": "^1.2.0",
"rimraf": "^2.6.1",
"tar": "^3.1.3"
},
"devDependencies": {
"mocha": "^2.4.5",
"mocha-circleci-reporter": "0.0.1"
"@types/bluebird": "^3.5.8",
"@types/chalk": "^0.4.31",
"@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
"@types/ora": "^0.3.31",
"@types/rimraf": "0.0.28",
"mocha": "^3.2.0",
"prettier": "^1.4.4",
"typescript": "^2.4.1"
}
}
+62
View File
@@ -0,0 +1,62 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from './util'
import { promisify, map } from 'bluebird'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from './compiler'
const mkdirpAsync = promisify(mkdirp)
const unlinkAsync = (promisify(unlink) as any) as (path: string) => PromiseLike<void>
const readdirAsync = promisify(readdir)
function readDirAsync(dir: string): PromiseLike<string[]> {
return readdirAsync(dir)
.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then((x: boolean) => (x ? readDirAsync(path) : path as any))
})
.reduce((a: string[], b: string[] | string) => a.concat(b), [])
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await map(tmpFiles, async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
await next()
await map(tmpFiles, x => unlinkAsync(x))
return map(compiler.files, async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
}
+92
View File
@@ -0,0 +1,92 @@
import * as fs from 'fs'
import * as path from 'path'
import * as child from 'child_process'
function findNativeModulePath(filePath: string, bindingsArg: string) {
const dirname = path.dirname(filePath)
const tempFile = Math.random() * 100 + '.js'
const tempFilePath = path.join(dirname, tempFile)
fs.writeFileSync(
tempFilePath,
`
var bindings = require('bindings');
var Module = require('module');
var originalRequire = Module.prototype.require;
Module.prototype.require = function(path) {
const mod = originalRequire.apply(this, arguments);
process.stdout.write(path)
return mod
};
bindings('${bindingsArg}')
`
)
//using exec because it can be done sync
const nativeFileName = child.execSync('node ' + tempFile, { cwd: dirname }).toString()
fs.unlinkSync(tempFilePath)
const relativePath = './' + path.relative(dirname, nativeFileName).replace(/\\/g, '/')
return relativePath
}
/**
* Traverse all nodes in a file and evaluate usages of the bindings module
* handles two common cases
*/
export class BindingsRewrite {
private bindingsIdNodes: any[] = []
public nativeModulePaths: string[] = []
public rewrite = false
isRequireBindings(node: any) {
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
}
onNode(absolutePath: string, node: any, parent: any) {
if (node.type === 'CallExpression') {
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
/**
* const loadBindings = require('bindings');
* -> const loadBindings = String('');
*/
this.bindingsIdNodes.push(parent.id)
node.callee.name = 'String'
node.arguments[0].value = ''
this.rewrite = true
return
}
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
/**
*const bindings = require('bindings')('native-module')....
* -> const bindings = require('./path/to/native/module.node').....
*/
const bindingsArgNode = parent.arguments[0]
if (bindingsArgNode.type === 'Literal') {
parent.callee = { type: 'Identifier', name: 'require' }
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
this.nativeModulePaths.push(bindingsArgNode.value)
this.rewrite = true
return
}
}
const bindingsInvocationIdx = this.bindingsIdNodes.findIndex(x => node.callee.name === x.name)
if (this.bindingsIdNodes[bindingsInvocationIdx]) {
/**
* const bindings = loadBindings('native-module')
* -> const bindings = require('./path/to/native/module.node')
*/
const bindingsIdNode = this.bindingsIdNodes[bindingsInvocationIdx]
const bindingsArgNode = node.arguments[0]
this.bindingsIdNodes.splice(bindingsInvocationIdx, 1)
if (bindingsArgNode.type === 'Literal') {
node.callee.name = 'require'
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
this.nativeModulePaths.push(bindingsArgNode.value)
this.rewrite = true
return
}
}
}
}
}
+137
View File
@@ -0,0 +1,137 @@
import { BindingsRewrite } from './bindings-rewrite'
import { createHash } from 'crypto'
import { readFileSync } from 'fs'
import { dirname, join, basename } from 'path'
function hashName(name: string | Buffer) {
return createHash('md5').update(name).digest('hex').toString().slice(0, 8)
}
export interface FuseBoxFile {
info: { absPath: string }
absPath: string
contents: string
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export interface NativeModulePluginOptions {
[key: string]:
| {
additionalFiles: string[]
}
| true
}
export default function(options: NativeModulePluginOptions) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test: RegExp
public limit2Project = false
private modules: (keyof NativeModulePluginOptions)[]
constructor(public options: NativeModulePluginOptions) {
this.modules = Object.keys(options)
this.test = new RegExp(`node_modules\/(${Object.keys(options).join('|')}).*\.js|\.node$`)
}
init(context: any) {
context.allowExtension('.node')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
const contents = readFileSync(file.absPath)
const module = this.modules.find(x =>
Boolean(~file.absPath.indexOf(join('node_modules', x)))
)!
const bindingName = basename(file.absPath)
const settings = this.options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
})
let segments = [moduleDir]
while (depth--) {
segments.push(hashName(moduleDir + depth))
}
segments.push(bindingName)
file.contents += `
var cwd = process.cwd()
var bindingFileParts = ${JSON.stringify(segments)};
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
mkdirp(path.dirname(bindingFile));
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
${settings.additionalFiles.reduce((code, filename, i) => {
const contents = readFileSync(join(dirname(file.absPath), filename))
return (code += `
var file${i} = '${contents.toString('base64')}';
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename
.split('../')
.join('')}');
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
return
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}
]
})
}
}
+31
View File
@@ -0,0 +1,31 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin } from 'fuse-box'
import NativeModulePlugin from './fuse'
function bundleProducer(filename: string) {
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
homeDir: './',
writeBundles: false,
target: 'server',
plugins: [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
})
fuse.bundle('nexe').instructions(`> ${filename}`)
return fuse.run().then(x => {
if (x.bundles.size > 1) {
console.log('THROW', x.bundles)
}
let output = ''
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
})
}
export default function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
return next()
}
if (typeof compiler.options.bundle === 'string') {
}
}
+73
View File
@@ -0,0 +1,73 @@
import { normalize } from 'path'
import * as Bluebird from 'bluebird'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { readFileAsync, dequote, isWindows } from './util'
import { NexeCompiler } from './compiler'
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
return new Bluebird(resolve => {
let input = ''
stream.setEncoding('utf-8')
stream.on('data', (x: string) => {
input += x
})
stream.once('end', () => resolve(dequote(input)))
stream.resume && stream.resume()
})
}
/**
* The "cli" step detects whether the process is in a tty. If it is then the input is read into memory.
* Otherwise, it is buffered from stdin. If no input options are passed in the tty, the package.json#main file is used.
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
*
* Configuration:
* - compiler.options.input - file path to the input bundle.
* - fallbacks: stdin, package.json#main
* - compiler.options.output - file path to the output executable.
* - fallbacks: stdout, nexe_ + epoch + ext
*
* @param {*} compiler
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { input, output } = compiler.options
const { log } = compiler
if (!input && !process.stdin.isTTY) {
log.step('Using stdin as input')
compiler.input = await readStreamAsync(process.stdin)
} else if (input) {
log.step(`Using input file as the main module: ${input}`)
compiler.input = await readFileAsync(normalize(input), 'utf-8')
} else if (!compiler.options.empty) {
const bundle = require.resolve(process.cwd())
log.step("Using the cwd's main file as the main module")
compiler.input = await readFileAsync(bundle, 'utf-8')
} else {
log.step('Using empty input as the main module')
compiler.input = ''
}
compiler.input = compiler.input.trim()
await next()
compiler.output = output || `${compiler.options.name}${isWindows ? '.exe' : ''}`
const deliverable = await compiler.compileAsync()
return new Bluebird((resolve, reject) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(normalize(compiler.output!)))
.on('error', reject)
.once('close', (e: Error) => {
if (e) {
reject(e)
} else if (compiler.output) {
chmodSync(compiler.output, '755')
step.log(`Executable written to: ${compiler.output}`)
resolve(compiler.quit())
}
})
})
}
+285
View File
@@ -0,0 +1,285 @@
import { normalize, join } from 'path'
import * as Bluebird from 'bluebird'
import { Buffer } from 'buffer'
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { Readable } from 'stream'
import { spawn } from 'child_process'
import { Stream as Needle } from 'nigel'
import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
import { NexeOptions } from './options'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
const configure = isWindows ? 'configure' : './configure'
const marker = Buffer.from('<nexe~sentinel>').toString('hex')
const tail = `\n//${marker}`
const needle = Buffer.from(marker)
const fixedIntegerLength = 10
const padLeft = (x: string | number, l = fixedIntegerLength, c = '0') => (c.repeat(l) + x).slice(-l)
const inflate = (value: string, size: number) => {
if (!size || value.length >= size) {
return value
}
return value + ' '.repeat(size - value.length)
}
export interface NexeFile {
filename: string
contents: string
}
interface NexeHeader {
/**
* Zero padded number indicating the extra size available in the binary
*/
paddingSize: string
/**
*
*/
binaryOffset: string
version: string
}
export class NexeCompiler {
private start = Date.now()
private env = { ...process.env }
private compileStep: { modify: Function; log: Function }
public log = new Logger(this.options.loglevel)
public src = join(this.options.temp, this.options.version)
public files: NexeFile[] = []
public input: string
public output: string | null
public resources: { bundle: string; index: { [key: string]: number[] } } = {
index: {},
bundle: ''
}
public readFileAsync: (file: string) => Promise<NexeFile>
public writeFileAsync: (file: string, contents: Buffer | string) => Promise<void>
public replaceInFileAsync: (file: string, replacer: any, replaceValue: string) => Promise<void>
public setFileContentsAsync: (file: string, contents: string | Buffer) => Promise<void>
private nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
constructor(public options: NexeOptions) {
const { python } = (this.options = options)
if (python) {
if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
} else {
this.env.PYTHON = python
}
}
this.readFileAsync = async (file: string) => {
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
if (!cachedFile) {
cachedFile = {
filename: file,
contents: await readFileAsync(join(this.src, file), 'utf-8').catch(
{ code: 'ENOENT' },
() => ''
)
}
this.files.push(cachedFile)
}
return cachedFile
}
this.writeFileAsync = (file, contents) => writeFileAsync(join(this.src, file), contents)
this.replaceInFileAsync = async (file, replace: string | RegExp, value: string) => {
const entry = await this.readFileAsync(file)
entry.contents = entry.contents.replace(replace, value)
}
this.setFileContentsAsync = async (file: string, contents: string) => {
const entry = await this.readFileAsync(file)
entry.contents = contents
}
}
quit(code = 0) {
const time = Date.now() - this.start
this.log.write(`Finsihed in ${time / 1000}s`)
return this.log.flush().then(x => process.exit(code))
}
private _findPaddingSize(size: number, override = this.options.padding) {
if (override === 0) {
return 0
}
size = override > size ? override : size
const padding = [3, 6, 9, 16, 25, 40].map(x => x * 1e6).find(p => size <= p)
if (!padding) {
throw new Error(
`No prebuilt target large enough (${(size / 1024).toFixed(
2
)}Mb).\nUse the --build flag and build for the current platform`
)
}
return padding
}
private _getNodeExecutableLocation(target?: string | null) {
if (target) {
return join(this.options.temp, target)
}
return this.nodeSrcBinPath
}
private _runBuildCommandAsync(command: string, args: string[]) {
return new Bluebird((resolve, reject) => {
spawn(command, args, {
cwd: this.src,
env: this.env,
stdio: 'ignore'
})
.once('error', reject)
.once('close', resolve)
})
}
private _configureAsync() {
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
configure,
...this.options.configure
])
}
private async _buildAsync() {
this.compileStep.log(`Configuring node build: ${this.options.configure}`)
await this._configureAsync()
const buildOptions = isWindows ? this.options.vcBuild : this.options.make
this.compileStep.log(`Compiling Node with arguments: ${buildOptions}`)
await this._runBuildCommandAsync(make, buildOptions)
return createReadStream(this._getNodeExecutableLocation())
}
private _fetchPrebuiltBinaryAsync() {
return this._buildAsync()
}
private _getPayload(header: NexeHeader) {
return this._serializeHeader(header) + this.input + '/**' + this.resources.bundle + `**/`
}
private _generateHeader() {
const zeros = padLeft(0)
const version =
['configure', 'vcBuild', 'make'].reduce((a, c) => {
return (a += (this.options as any)[c].slice().sort().join())
}, '') + this.options.enableNodeCli
const header = {
version: padLeft(0, 32),
resources: this.resources.index,
contentSize: zeros,
paddingSize: zeros,
resourceOffset: zeros,
binaryOffset: zeros
}
const serializedHeader = this._serializeHeader(header)
header.contentSize = padLeft(Buffer.byteLength(this._getPayload(header)))
header.paddingSize = padLeft(this._findPaddingSize(+header.contentSize))
header.resourceOffset = padLeft(Buffer.byteLength(serializedHeader + this.input + '/**'))
header.version = createHash('md5').update(version + header.paddingSize).digest('hex')
return header
}
private async _getExistingBinaryHeaderAsync(target: string | undefined | null) {
const filename = this._getNodeExecutableLocation(target)
const existingBinary = await pathExistsAsync(filename)
if (existingBinary) {
return this._extractHeaderAsync(filename)
}
return null
}
private _extractHeaderAsync(path: string): Promise<NexeHeader> {
const binary = createReadStream(path)
const haystack = new Needle(needle)
let needles = 0
let stackCache: Buffer[] = []
return new Promise((resolve, reject) => {
binary
.on('error', reject)
.pipe(haystack)
.on('error', reject)
.on('close', () => reject(new Error(`Binary: ${path} is not compatible with nexe`)))
.on('haystack', (x: Buffer) => needles && stackCache.push(x))
.on('needle', () => {
if (++needles === 2) {
resolve(JSON.parse(Buffer.concat(stackCache).toString()))
binary.close()
haystack.end()
}
})
})
}
private _serializeHeader(header: NexeHeader) {
return `/**${marker}${JSON.stringify(header)}${marker}**/process.__nexe=${JSON.stringify(
header
)};`
}
async setMainModule(compiler: NexeCompiler, next: () => Promise<void>) {
await next()
const header = compiler._generateHeader()
const contents = inflate(this._getPayload(header), +header.paddingSize) + tail
const file = await compiler.readFileAsync('lib/_third_party_main.js')
file.contents += '\n' + contents
}
async compileAsync() {
const step = (this.compileStep = this.log.step('Compiling result'))
let target = this.options.targets.slice().shift()
let prebuiltBinary = null
const header = this._generateHeader()
target = target && `${target}-${header.version.slice(0, 6)}`
step.log(`Scanning existing binary...`)
const existingHeader = await this._getExistingBinaryHeaderAsync(target)
if (existingHeader && existingHeader.version === header.version) {
const location = this._getNodeExecutableLocation(target)
step.log(`Source already built: ${location}`)
prebuiltBinary = createReadStream(location)
}
if (target) {
throw new Error('\nNot Implemented, use --build during beta\n')
// prebuiltBinary = await this._fetchPrebuiltBinaryAsync(target)
}
if (!prebuiltBinary) {
prebuiltBinary = await this._buildAsync()
step.log('Node binary compiled')
}
return this._assembleDeliverable(header, prebuiltBinary)
}
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
const haystack = new Needle(Buffer.concat([Buffer.from('/**'), needle]))
const artifact = new Readable({ read() {} })
let needles = 0
let currentStackSize = 0
binary.pipe(haystack)
haystack
.on('close', () => artifact.push(null))
.on('needle', () => ++needles && haystack.needle(needle))
.on('haystack', (x: Buffer) => {
if (!needles) {
currentStackSize += x.length
artifact.push(x)
}
if (needles === 1 && !+header.binaryOffset) {
header.binaryOffset = padLeft(currentStackSize)
const content = Buffer.from(inflate(this._getPayload(header), +header.paddingSize) + tail)
artifact.push(content)
}
if (needles > 2) {
artifact.push(x)
}
})
return artifact
}
}
+40
View File
@@ -0,0 +1,40 @@
import download = require('download')
import { isDirectoryAsync } from './util'
import { LogStep } from './logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from './compiler'
function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
return download(url, cwd, Object.assign(options, { extract: true, strip: 1 }))
.on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
setText(current / total * 100)
if (current === total) {
step.log('Extracting Node...')
}
})
})
.then(() => step.log(`Node source extracted to: ${cwd}`))
}
/**
* Downloads the node source to the configured temporary directory
* @param {*} compiler
* @param {*} next
*/
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
const { src, log } = compiler
const { version, sourceUrl, downloadOptions } = compiler.options
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
const step = log.step(`Downloading Node.js source from: ${url}`)
if (await isDirectoryAsync(src)) {
step.log('Source already downloaded')
return next()
}
return fetchNodeSourceAsync(src, url, step, downloadOptions).then(next)
}
+65
View File
@@ -0,0 +1,65 @@
import colors = require('chalk')
import ora = require('ora')
const frameLength = 120
export interface LogStep {
modify(text: string, color?: string): void
log(text: string, color?: string): void
}
export class Logger {
private verbose: boolean
private silent: boolean
private ora: any
private modify: Function
public write: (text: string, color?: string) => void
constructor(level: 'verbose' | 'silent' | 'info') {
this.verbose = level === 'verbose'
this.silent = level === 'silent'
if (!this.silent) {
this.ora = ora({
text: 'Starting...',
color: 'blue',
spinner: 'dots'
})
this.ora.stop()
}
const noop = () => {}
this.modify = this.silent ? noop : this._modify.bind(this)
this.write = this.silent ? noop : this._write.bind(this)
}
flush() {
!this.silent && this.ora.succeed()
return new Promise(resolve => setTimeout(resolve, frameLength))
}
_write(update: string, color = 'green') {
this.ora.succeed().text = (colors as any)[color](update)
this.ora.start()
}
_modify(update: string, color = this.ora.color) {
this.ora.text = update
this.ora.color = color
}
step(text: string): LogStep {
if (this.silent) {
return { modify() {}, log() {} }
}
if (!this.ora.id) {
this.ora.start().text = text
} else {
this.ora.succeed().text = text
this.ora.start()
}
return {
modify: this.modify,
log: this.verbose ? this.write : this.modify
} as LogStep
}
}
+32
View File
@@ -0,0 +1,32 @@
import { compose, PromiseConfig, Middleware } from 'app-builder'
import resource from './resource'
import { NexeCompiler } from './compiler'
import { argv, normalizeOptionsAsync, NexeOptions } from './options'
import cli from './cli'
import download from './download'
import artifacts from './artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import * as Bluebird from 'bluebird'
PromiseConfig.constructor = Bluebird
async function compile(compilerOptions: NexeOptions, callback?: (err: Error | null) => void) {
const options = await normalizeOptionsAsync(compilerOptions)
const compiler = new NexeCompiler(options)
const build = compiler.options.build
if (options.clean) {
const step = compiler.log.step('Cleaning up nexe build artifacts...')
step.log(`Deleting directory and contents at: ${compiler.src}`)
await rimrafAsync(compiler.src)
step.log(`Deleted directory and contents at: ${compiler.src}`)
return compiler.quit()
}
const buildSteps = build ? [download, artifacts, ...patches, ...options.patches] : []
const nexe = compose(resource, cli, buildSteps)
return nexe(compiler).asCallback(callback)
}
export { argv, compile }
+214
View File
@@ -0,0 +1,214 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { basename, extname, join } from 'path'
import * as Bluebird from 'bluebird'
import { EOL } from 'os'
export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
}
export interface NexeOptions {
build: boolean
/**
* Entrypoint filepath
*/
input: string
output: string
targets: string[]
/**
* Build name, Used for executable, and stacktraces
*/
name: string
/**
* The node version to be built
*/
version: string
python?: string
/**
* Node flags e.g. "--expose-gc" baked into the executable
*/
flags: string[]
/**
* Pass configuration options to node build configure script
*/
configure: string[]
/**
* Pass make options to node make script
*/
make: string[]
vcBuild: string[]
snapshot?: string
/**
* Array of glob strings describing resources to pull into the bundle
*/
resources: string[]
/**
* Temporary directory where nexe artifacts will be cached
* TODO dot folder in home dir
*/
temp: string
ico?: string
rc: { [key: string]: string }
/**
* Causes nexe to remove all temporary files for current configuration
*/
clean: boolean
enableNodeCli: boolean
sourceUrl?: string
bundle: boolean | string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
verbose?: boolean
info?: boolean
padding: number
patches: NexePatch[]
empty: boolean
warmup?: string
downloadOptions?: any
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
const defaults = {
temp: process.env.NEXE_TEMP || join(process.cwd(), '.nexe'),
version: process.version.slice(1),
flags: [],
configure: [],
make: [],
targets: [],
vcBuild: ['nosign', 'release'],
enableNodeCli: false,
bundle: true,
build: true,
patches: []
}
const alias = {
i: 'input',
o: 'output',
t: 'target',
n: 'name',
v: 'version',
r: 'resource',
a: 'resource',
p: 'python',
f: 'flag',
c: 'configure',
m: 'make',
vc: 'vcBuild',
s: 'snapshot',
cli: 'enableNodeCli',
h: 'help',
l: 'loglevel'
}
const argv = parseArgv(process.argv, { alias, default: defaults })
const help = `
nexe --help CLI OPTIONS
-b --build -- build from source
-i --input =index.js -- application entry point
-o --output =my-app.exe -- path to output file
-t --target =win32-x64-6.10.3 -- *target a prebuilt binary
-n --name =my-app -- main app module name
-v --version =${padRight(process.version.slice(1), 23)}-- node version
-p --python =/path/to/python2 -- python executable
-f --flag ="--expose-gc" -- *v8 flags to include during compilation
-c --configure ="--with-dtrace" -- *pass arguments to the configure command
-m --make ="--loglevel" -- *pass arguments to the make command
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
-r --resource =./paths/**/* -- *embed file bytes within the binary
--bundle =./path/to/config -- pass a module path that exports nexeBundle
--temp =./path/to/temp -- nexe temp files (for downloads and source builds)
--no-bundle -- set when input is already bundled
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--clean -- force download of sources
--enableNodeCli -- enable node cli enforcement (blocks app cli)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--silent -- disable logging
--verbose -- set logging to verbose
-* variable key name * option can be used more than once`.trim()
function flattenFilter(...args: any[]): string[] {
return ([] as string[]).concat(...args).filter(x => x)
}
/**
* Extract keys such as { "rc-CompanyName": "Node.js" } to
* { CompanyName: "Node.js" }
* @param {*} match
* @param {*} options
*/
function extractCliMap(match: RegExp, options: any) {
return Object.keys(options)
.filter(x => match.test(x))
.reduce((map: { [key: string]: string }, option: keyof NexeOptions) => {
const key = option.split('-')[1]
map[key] = options[option]
delete options[option]
return map
}, {})
}
function tryResolveMainFileName() {
let filename
try {
const file = require.resolve(process.cwd())
filename = basename(file).replace(extname(file), '')
} catch (_) {}
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
}
function extractLogLevel(options: NexeOptions) {
if (options.loglevel) return options.loglevel
if (options.silent) return 'silent'
if (options.verbose) return 'verbose'
return 'info'
}
function extractName(options: NexeOptions) {
let name = options.name
if (typeof options.input === 'string' && !name) {
name = basename(options.input).replace(extname(options.input), '')
}
name = name || tryResolveMainFileName()
return name.replace(/\.exe$/, '')
}
function normalizeOptionsAsync(input: Partial<NexeOptions>) {
if (argv.help || argv._.some((x: string) => x === 'version')) {
process.stderr.write(argv.help ? help : '2.0.0-beta.4' + EOL, () => process.exit(0))
}
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
delete opts._
options.loglevel = extractLogLevel(options)
options.name = extractName(options)
options.flags = flattenFilter(opts.flag, options.flags)
options.targets = flattenFilter(opts.target, options.targets)
options.make = flattenFilter(options.make)
options.vcBuild = flattenFilter(options.vcBuild)
options.configure = flattenFilter(options.configure)
options.resources = flattenFilter(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (options.build || options.padding === 0) {
options.targets = []
options.build = true
} else if (!options.targets.length) {
const defaultTarget = [process.platform, process.arch, options.version].join('-')
options.targets = [defaultTarget]
}
Object.keys(alias).filter(k => k !== 'rc').forEach(x => delete opts[x])
return Promise.resolve(options)
}
export { argv, normalizeOptionsAsync }
+17
View File
@@ -0,0 +1,17 @@
import { NexeCompiler } from '../compiler'
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.options.version.startsWith('8.2')) {
return next()
}
const file = await compiler.readFileAsync('./tools/msvs/find_python.cmd')
await compiler.replaceInFileAsync(
'./tools/msvs/find_python.cmd',
'%p%python.exe -V 2>&1',
'"%p%python.exe" -V 2>&1'
)
return next()
}
+12
View File
@@ -0,0 +1,12 @@
import { NexeCompiler } from '../compiler'
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
if (compiler.options.enableNodeCli) {
return next()
}
const nodeccMarker = "argv[index][0] == '-'"
await compiler.replaceInFileAsync('src/node.cc', nodeccMarker, nodeccMarker.replace('-', ']'))
return next()
}
+16
View File
@@ -0,0 +1,16 @@
import { NexeCompiler } from '../compiler'
export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) {
const nodeflags = compiler.options.flags
if (!nodeflags.length) {
return next()
}
await compiler.replaceInFileAsync(
'node.gyp',
"'node_v8_options%': ''",
`'node_v8_options%': '${nodeflags.join(' ')}'`
)
return next()
}
+18
View File
@@ -0,0 +1,18 @@
import { NexeCompiler } from '../compiler'
export default async function nodeGyp(
{ files, replaceInFileAsync }: NexeCompiler,
next: () => Promise<void>
) {
await next()
const nodeGypMarker = "'lib/fs.js',"
await replaceInFileAsync(
'node.gyp',
nodeGypMarker,
`
${nodeGypMarker}
${files.filter(x => x.filename.startsWith('lib')).map(x => `'${x.filename}'`).toString()},
`.trim()
)
}
+12
View File
@@ -0,0 +1,12 @@
import { normalize } from 'path'
import { readFileAsync } from '../util'
import { NexeCompiler } from '../compiler'
export default async function ico(compiler: NexeCompiler, next: () => Promise<void>) {
const iconFile = compiler.options.ico
if (!iconFile) {
return next()
}
await compiler.setFileContentsAsync('src/res/node.ico', await readFileAsync(normalize(iconFile)))
return next()
}
+21
View File
@@ -0,0 +1,21 @@
import gyp from './gyp'
import nexePatches from './third-party-main'
import buildFixes from './build-fixes'
import cli from './disable-node-cli'
import flags from './flags'
import ico from './ico'
import rc from './node-rc'
import { NexeCompiler } from '../compiler'
const patches = [
gyp,
(compiler: NexeCompiler, next: () => Promise<void>) => compiler.setMainModule(compiler, next),
nexePatches,
buildFixes,
cli,
flags,
ico,
rc
]
export default patches
+19
View File
@@ -0,0 +1,19 @@
import { NexeCompiler } from '../compiler'
export default async function nodeRc(compiler: NexeCompiler, next: () => Promise<void>) {
const options = compiler.options.rc
if (!options) {
return next()
}
const file = await compiler.readFileAsync('src/res/node.rc')
Object.keys(options).forEach(key => {
let value = options[key]
const isVar = /^[A-Z_]+$/.test(value)
value = isVar ? value : `"${value}"`
file.contents.replace(new RegExp(`VALUE "${key}",*`), `VALUE "${key}", ${value}`)
})
return next()
}
+19
View File
@@ -0,0 +1,19 @@
import { NexeCompiler } from '../compiler'
export default async function snapshot(compiler: NexeCompiler, next: () => Promise<void>) {
const snapshotFile = compiler.options.snapshot
const warmupScript = compiler.options.warmup
if (!snapshotFile) {
return next()
}
await compiler.replaceInFileAsync(
'configure',
'def configure_v8(o):',
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${warmupScript ||
snapshotFile}'`
)
return next()
}
+80
View File
@@ -0,0 +1,80 @@
import { NexeCompiler } from '../compiler'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
`
const fs = require('fs')
const Buffer = require('buffer').Buffer
const isString = x => typeof x === 'string' || x instanceof String
Object.defineProperty(process, '__nexe', (function () {
let nexeHeader = null
return {
get: function () {
return nexeHeader
},
set: function (value) {
if (nexeHeader) {
throw new Error('__nexe cannot be reconfigured')
}
nexeHeader = value
Object.freeze(nexeHeader)
},
enumerable: false,
configurable: false
}
})());
const originalReadFile = fs.readFile
const originalReadFileSync = fs.readFileSync
fs.readFile = function readFile (file, options, callback) {
const manifest = process.__nexe
const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) {
return originalReadFile.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = +manifest.binaryOffset + +manifest.resourceOffset + offset
const encoding = isString(options) ? options : null
callback = typeof options === 'function' ? options : callback
fs.open(process.execPath, 'r', function (err, fd) {
if (err) return callback(err, null)
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function (error, bytesRead, buffer) {
if (error) {
return fs.close(fd, function () {
callback(error, null)
})
}
fs.close(fd, function (err) {
if (err) {
return callback(err, buffer)
}
const result = Buffer.from(buffer.toString(), 'base64')
callback(err, encoding ? result.toString(encoding) : result)
})
})
})
}
fs.readFileSync = function readFileSync (file, options) {
const manifest = process.__nexe
const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) {
return originalReadFileSync.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = +manifest.binaryOffset + +manifest.resourceOffset + offset
const encoding = isString(options) ? options : null
const fd = fs.openSync(process.execPath, 'r')
const result = Buffer.alloc(length)
fs.readSync(fd, result, 0, length, resourceOffset)
fs.closeSync(fd)
const contents = Buffer.from(result.toString(), 'base64')
return encoding ? contents.toString(encoding) : contents
}`.trim()
)
return next()
}
+33
View File
@@ -0,0 +1,33 @@
import { each } from 'bluebird'
import { readFileAsync, isDirectoryAsync } from './util'
import { Buffer } from 'buffer'
import * as globs from 'globby'
import { NexeCompiler } from './compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
const resources = compiler.resources
if (!compiler.options.resources.length) {
return next()
}
const step = compiler.log.step('Bundling Resources...')
let count = 0
await each(globs(compiler.options.resources), async file => {
if (await isDirectoryAsync(file)) {
return
}
count++
step.log(`Including file: ${file}`)
const contents = await readFileAsync(file)
const commentSafeContents = contents.toString('base64')
resources.index[file] = [
Buffer.byteLength(resources.bundle),
Buffer.byteLength(commentSafeContents)
]
resources.bundle += commentSafeContents
})
step.log(
`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`
)
return next()
}
+35
View File
@@ -0,0 +1,35 @@
declare module 'nigel' {
import { Writable } from 'stream'
interface Needle {
value: Buffer
lastPos: number
last: number
length: number
badCharShift: Buffer
}
export function compile(needle: Buffer): Needle
export function horspool(haystack: Buffer, needle: Needle, start: number): number
export function all(haystack: Buffer, needle: Needle, pos: number): number[]
export class Stream extends Writable {
constructor(needle: Buffer)
needle(needle: Buffer): void
flush(): void
}
}
declare module 'download' {
import { Duplex } from 'stream'
interface DownloadOptions {
extract?: boolean
strip?: number
filename?: string
proxy?: string
}
function download(
url: string,
destination?: string | DownloadOptions,
options?: DownloadOptions
): PromiseLike<Buffer> & Duplex
export = download
}
+47
View File
@@ -0,0 +1,47 @@
import { readFile, writeFile, stat } from 'fs'
import * as Bluebird from 'bluebird'
import rimraf = require('rimraf')
const { promisify } = Bluebird
const rimrafAsync = (promisify(rimraf) as any) as (path: string) => Bluebird<void>
function dequote(input: string) {
input = input.trim()
const singleQuote = input.startsWith("'") && input.endsWith("'")
const doubleQuote = input.startsWith('"') && input.endsWith('"')
if (singleQuote || doubleQuote) {
return input.slice(1).slice(0, -1)
}
return input
}
export interface ReadFileAsync {
(path: string): Bluebird<Buffer>
(path: string, encoding: string): Bluebird<string>
}
const readFileAsync = (promisify(readFile) as any) as ReadFileAsync
const writeFileAsync = (promisify(writeFile) as any) as (
path: string,
contents: string | Buffer
) => Promise<void>
const statAsync = promisify(stat)
const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) {
return statAsync(path).then(x => true).catch({ code: 'ENOENT' }, () => false)
}
function isDirectoryAsync(path: string) {
return statAsync(path).then(x => x.isDirectory()).catch({ code: 'ENOENT' }, () => false)
}
export {
dequote,
isWindows,
rimrafAsync,
readFileAsync,
pathExistsAsync,
isDirectoryAsync,
writeFileAsync
}
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target":"es5",
"lib": ["es2017"],
"module": "commonjs",
"outDir": "./lib",
"strict": true
},
"include":[
"src/**/*.ts"
]
}
-1610
View File
File diff suppressed because it is too large Load Diff