Compare commits

..

2 Commits

Author SHA1 Message Date
calebboyd ba351e0e3a chore: publish 1.1.3 2017-07-05 23:19:54 -05:00
James M. Greene dc76aea5c8 Prepend the Python path for Windows for overriding 2017-07-05 16:57:58 -05:00
89 changed files with 3531 additions and 6865 deletions
-9
View File
@@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
#https://github.com/Microsoft/vscode/issues/23983#event-1087502108
#trim_trailing_whitespace = true
insert_final_newline = true
+11 -8
View File
@@ -1,12 +1,15 @@
.DS_Store
temp/
coverage
node_modules
*.log
*.exe
*.bin
!winsw.exe
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
.idea
.nexe
lib
+3
View File
@@ -0,0 +1,3 @@
node_modules
test
src
-12
View File
@@ -1,12 +0,0 @@
language: node_js
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y g++-multilib
node_js:
- '6'
script: npm run asset-compile
env:
global:
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
notifications:
email: false
-5
View File
@@ -1,5 +0,0 @@
{
"recommendations": [
"esbenp.prettier-vscode"
]
}
-19
View File
@@ -1,19 +0,0 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"],
"editor.trimAutoWhitespace": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"search.exclude": {
"lib/**/*": true,
"**/node_modules": true,
"**/bower_components": true
}
}
+27 -40
View File
@@ -1,64 +1,51 @@
# 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
* Resource storage/access rewrite
* stdin interface
* Optional, pluggable 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-2017 Nexe Contributors
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
+210 -188
View File
@@ -1,227 +1,249 @@
<p align="center"><img src="https://cloud.githubusercontent.com/assets/2391349/23598327/a17bb68a-01ee-11e7-8f55-88a5fc96e997.png" /></p>
<p align="center">
<a href="https://circleci.com/gh/nexe/nexe"><img src="https://img.shields.io/circleci/project/nexe/nexe.svg" alt="Build Status"></a>
<a href="https://circleci.com/gh/jaredallard/nexe"><img src="https://img.shields.io/circleci/project/jaredallard/nexe.svg" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/dt/nexe.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/v/nexe.svg" alt="Version"></a>
<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 -g</code></p>
<p align="center">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
<p align="center">
<img src="https://user-images.githubusercontent.com/5818726/30999006-df7e0ae0-a497-11e7-96db-9ce87ae67b34.gif"/>
</p>
![screenshot](https://cloud.githubusercontent.com/assets/2391349/23597575/e333884c-01e8-11e7-9fbf-3a83088bc748.png)
## Motivation and Features
## 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
- Self contained applications
- Ability to run multiple applications with *different* node.js runtimes.
- Distribute binaries without needing node / npm.
- Idempotent builds
- Start and deploy faster.
- Distributable binaries without needing node / npm.
- Starts faster.
- Lockdown specific application versions, and easily rollback.
- Flexible build pipeline
- Cross platform builds
- Faster deployments.
## Usage
## Building Requirements
- Application entrypoint:
- Linux / Mac / BSD / Windows
- Python 2.6 or 2.7 (use --python if not in PATH)
- Windows: Visual Studio 2010+
`nexe my-app.js`
## Caveats
- stdin interface
### Doesn't support native modules
`rollup -c | nexe --resource "./public/**/*" -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.
For more CLI options see: `nexe --help`
### Doesn't support dynamic require statements
### Examples
- `nexe server.js -r public/**/*.html`
- `nexe my-bundle.js --no-bundle -o app.exe`
- `nexe --build`
- `nexe -t x86-8.0.0`
## Resources
Additional files or 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, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptbale version of python 2. eg. Systems that have python2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486)
## Node.js API
#### Example
Such As:
```javascript
const { compile } = require('nexe')
compile({
input: './my-app.js',
build: true, //required to use patches
patches: [
async (compiler, next) => {
await compiler.setFileContentsAsync(
'lib/new-native-module.js',
'module.exports = 42'
)
return next()
}
]
}).then(() => {
console.log('success')
})
var x = require(someVar);
```
## NexeOptions
### `options: object`
In this case nexe won't bundle the file
- #### `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 | object`
- Combination of platform-arch-version. e.g. `'win32-ia32-6.10.3'`
- each segment is optional, and will be merged with the current environment
- default: `process`
- #### `bundle: string | boolean`
- If a string is provided it must be a valid relative module path
and should provide an export with the following signature:
```typescript
export function createBundle (options: NexeOptions): Promise<string>
```
- default: true
- #### `name: string`
- Module friendly name of the application
- default: basename of the input file, or `nexe_${Date.now()}`
- #### `cwd: string`
- Directory nexe will operate on as though it is the cwd
- default: process.cwd()
- #### `build: boolean`
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
- #### `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: string[]`
- Array of node runtime flags to build node with.
- Example: `['--expose-gc']`
- default: `[]`
- #### `configure: string[]`
- Array of arguments for the node build configure step
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: string[]`
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- #### `make: string[]`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
- #### `resources: 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:
```javascript
{
CompanyName: "ACME Corp",
PRODUCTVERSION: "17,3,0,0",
FILEVERSION: "1,2,3,4"
...
}
```
- default: `{}`
- #### `clean: boolean`
- If included, nexe will remove temporary files for the accompanying configuration and exit
- #### `enableNodeCli: boolean`
- Enable the original Node CLI (will prevent application cli from working)
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `sourceUrl: string`
- Provide an alternate url for the node source code
- Note: temporary files will still be created for this under the specified version
- #### `loglevel: string`
- Set the loglevel, info, silent, or verbose
- default: `'info'`
- #### `patches: NexePatch[]`
- Userland patches for patching or modifying node source
- default: `[]`
- #### `plugins: NexePatch[]`
- Userland plugins for modifying nexe executable behavior
- default: `[]`
```javascript
var x;
if (someCheck) {
x = require("./ver1.js");
} else {
x = require("./ver2.js");
}
```
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
In this case nexe will bundle both files.
Patches and Plugins are just a middleware functions that take 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. Patches also require that [`--build`](#build-boolean) be set, while plugins do not.
Workarounds:
1) for dynamic requires that you want bundled add the following into your project
For examples, see the built in patches: [src/patches](src/patches)
A plugin
```javascript
var dummyToForceIncludeForBundle = false;
if (dummyToForceIncludeForBundle) {
require("./loadedDynamicallyLater.js");
// ...
}
```
this will trick the bundler into including them.
### `NexeCompiler`
2) for dynamic files getting included that you don't want to be
- `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<NexeFile>`
- Access (or create) a file within the downloaded Node.js source.
- `addResource(filename: string, contents: Buffer): void`
- Add a resource to the nexe bundle
- `files: NexeFile[]`
- The cache of the currently read, modified, or created files within the downloaded Node.js source.
```javascript
var moduleName = "./ver2.js";
if (someCheck) {
moduleName = "./ver1.js";
}
var x = require(moduleName);
```
Note: neither file will be bundled.
#### `NexeFile`
- `contents: string`
- `absPath: string`
- `filename: string`
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.
Any modifications made to `NexeFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
### &#95;&#95;dirname
## Native Modules
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.
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 (windows) [example](examples/native-build/build.js)
If you had a data file at `/dev/myNodeApp/stateManager/handler/data/some.csv`
and a file at `/dev/myNodeApp/stateManager/handler/loader.js`
- [ ] Implement support `node-pre-gyp#find`.
```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');
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
});
```
### package.json inclusion
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.
```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
}
}
```
Notes:
* output: can use ^$ for platform specific file extension
* js-flags: this is also known as v8 flags, and supports *all* v8 flags.
### Browserify Require Issues
If you have requires that aren't resolving well, you can do two things.
Try adding it to `nexe.browserify.requires` in your `package.json`
```json
"nexe": {
.......
"browserify": {
"requires": [
{
"file": "myfile.js",
"expose": "mymodule"
},
"mymodule.js"
],
"excludes": [],
"paths": []
},
.......
}
```
Or, if that doesn't work (it tends to not work sometimes), you can try altering
browserify.paths like so:
```json
"nexe": {
.......
"browserify": {
"requires": []
"excludes": [],
"paths": ["/path/to/my/loc"]
},
.......
}
```
If it *still* doesn't work, file a bug with what you tried! (also try using `nexe@0.4.2`)
## Maintainers
[![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) |
## Contributing
Building
```
$ git clone git@github.com:nexe/nexe.git
$ cd nexe
$ yarn
```
Testing
```
$ npm test
```
### Former
- [Craig Condon](http://crcn.codes/)
* __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)
-18
View File
@@ -1,18 +0,0 @@
environment:
CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
install:
- ps: Install-Product node 6
- yarn
build: off
test: off
build_script:
- yarn run asset-compile
artifacts:
- path: .nexe\**\node.exe
cache:
- node_modules
Executable
+115
View File
@@ -0,0 +1,115 @@
#!/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);
}
}
);
}
+6 -1
View File
@@ -1,3 +1,8 @@
machine:
node:
version: 5.7.0
test:
override:
- npm run asset-compile
- npm test:
timeout: 5400
-13
View File
@@ -1,13 +0,0 @@
const nexe = require('../..')
nexe.compile({
output: 'native-build',
silent: true,
native: {
zmq: {
additionalFiles: [
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
]
}
}
})
-18
View File
@@ -1,18 +0,0 @@
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)
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())
})
-16
View File
@@ -1,16 +0,0 @@
{
"name": "native-build",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "node build"
},
"author": "",
"license": "ISC",
"dependencies": {
"sqlite3": "^3.1.10",
"zmq": "^2.15.3"
},
"devDependencies": {}
}
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env node
const options = require('./lib/options')
if (require.main === module) {
//fast path for help/version
const argv = options.argv
const eol = require('os').EOL
const showHelp = argv.help || argv._.some(x => x === 'help')
const showVersion = argv.version || argv._.some(x => x === 'version')
if (showHelp || showVersion) {
process.stderr.write(showHelp ? options.help : options.version + eol)
} else {
const nexe = require('./lib/nexe')
nexe.compile(argv).catch((e) => {
process.stderr.write(eol + e.stack)
})
}
} else {
module.exports = require('./lib/nexe')
}
+2 -1
View File
@@ -11,7 +11,7 @@ If this is a bug report, What are the steps to reproduce?
```
<br/><br/>
Please also provide:
Please also provide:
- Platform(OS/Version):
- Host Node Version:
@@ -19,3 +19,4 @@ Please also provide:
- Nexe version:
- Python Version:
+137
View File
@@ -0,0 +1,137 @@
/**
* 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
@@ -0,0 +1,73 @@
/**
* 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
@@ -0,0 +1 @@
module.exports = require("./exe");
+57
View File
@@ -0,0 +1,57 @@
/**
* 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
@@ -0,0 +1,63 @@
/**
* 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;
+39 -52
View File
@@ -1,68 +1,55 @@
{
"author": "Jared Allard <jaredallard@outlook.com>",
"name": "nexe",
"description": "Create a single executable out of your Node.js application",
"description": "create single executables out of your [node/io].js applications",
"license": "MIT",
"version": "2.0.0-rc.11",
"version": "1.1.3",
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>"
{
"name": "Criag Condon",
"email": "craig.j.condon@gmail.com",
"url": "http://crcn.io/"
}
],
"scripts": {
"asset-compile": "ts-node tasks/asset-compile",
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm test && npm run build",
"test": "mocha test/**/*.spec.ts",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
"build": "tsc --declaration",
"postbuild": "ts-node tasks/post-build"
"test": "echo node: $(node -v) && node_modules/.bin/mocha --reporter mocha-circleci-reporter test/test.js"
},
"repository": {
"type": "git",
"url": "git://github.com/nexe/nexe.git"
},
"files": [
"lib",
"source-map-support.js"
],
"typings": "lib/nexe.d.ts",
"main": "index.js",
"bin": {
"nexe": "index.js"
},
"engines": {
"node": ">=6"
"url": "git://github.com/jaredallard/nexe.git"
},
"main": "./lib/index.js",
"dependencies": {
"app-builder": "^5.1.0",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "2.2.31",
"globby": "^6.1.0",
"minimist": "^1.2.0",
"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",
"ora": "^1.2.0",
"pifi": "1.0.0",
"rimraf": "^2.6.1",
"uglify-js": "3.0.28"
"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,
"bin": {
"nexe": "bin/nexe"
},
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/chalk": "^0.4.31",
"@types/execa": "^0.7.0",
"@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
"@types/mocha": "^2.2.42",
"@types/ora": "^0.3.31",
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28",
"chai": "^4.1.2",
"execa": "^0.8.0",
"got": "^7.1.0",
"mocha": "^3.5.0",
"prettier": "^1.6.1",
"ts-node": "3.3.0",
"typescript": "^2.4.1"
"mocha": "^2.4.5",
"mocha-circleci-reporter": "0.0.1"
}
}
-22
View File
@@ -1,22 +0,0 @@
{
"name": "nexe-daemon",
"version": "1.0.0",
"description": "Plugin for nexe to create installable services",
"main": "index.js",
"scripts": {
"clean": "rimraf *.js",
"build": "tsc",
"postpublish": "npm run clean"
},
"files": [
"*.js",
"winsw.exe"
],
"author": "Caleb Boyd",
"license": "MIT",
"devDependencies": {
"nexe": "^2.0.0-rc.6",
"rimraf": "^2.6.2",
"typescript": "^2.5.2"
}
}
-50
View File
@@ -1,50 +0,0 @@
import { NexeCompiler, NexeOptions } from 'nexe'
import { readFileSync } from 'fs'
import { join } from 'path'
interface NexeDaemonOptions {
id: string
name: string
description: string
executable: string
}
type DaemonOptions = NexeOptions & { daemon: { windows: NexeDaemonOptions } }
function renderWinswConfig(options: any) {
return '<configuration>\r\n' +
`${Object.keys(options).reduce((config: string, element: string) => {
return config += `<${element}>${options[element]}</${element}>\r\n`
}, '')}</configuration>\r\n`
}
export default function daemon (compiler: NexeCompiler<DaemonOptions>, next: () => Promise<void>) {
if (compiler.target.platform !== 'windows') {
return next()
}
compiler.addResource(
'./nexe/plugin/daemon/winsw.exe',
readFileSync(require.resolve('./winsw.exe'))
)
const name = compiler.options.name,
options = compiler.options.daemon.windows,
defaults: NexeDaemonOptions = {
id: name,
name,
description: name,
executable: '%BASE%\\' + compiler.output
}
compiler.addResource(
'./nexe/plugin/daemon/winsw-config.xml',
Buffer.from(renderWinswConfig(Object.assign(defaults, options)))
)
compiler.addResource(
'./nexe/plugin/daemon/app.js',
Buffer.from(compiler.input)
)
compiler.input = '{{replace:plugins/nexe-daemon/lib/nexe-daemon.js}}'
return next()
}
-4
View File
@@ -1,4 +0,0 @@
console.log('hello world')
if (true) {
console.log('wat')
}
-13
View File
@@ -1,13 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [ "es2016" ],
"outDir": "lib",
"strict": true
},
"include": [
"src/**/*.ts"
],
"exclude": []
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-161
View File
@@ -1,161 +0,0 @@
import * as fs from 'fs'
import * as path from 'path'
import * as child from 'child_process'
import { createHash } from 'crypto'
export interface ExtractNodeModuleOptions {
[key: string]: { additionalFiles: string[] } | true
}
function hashName(name: string | Buffer) {
return createHash('md5')
.update(name)
.digest('hex')
.toString()
.slice(0, 8)
}
export function embedDotNode(
options: ExtractNodeModuleOptions,
file: { contents: string; absPath: string }
) {
const contents = fs.readFileSync(file.absPath)
const modulePathParts = file.absPath.split(path.sep).reverse()
const module = modulePathParts[modulePathParts.findIndex(x => x === 'node_modules') - 1]
const bindingName = path.basename(file.absPath)
const settings = 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 || 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 = fs.readFileSync(path.join(path.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)
`
}
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
isRequire(node: any, moduleName: string) {
return node.callee.name === 'require' && node.arguments[0].value === moduleName
}
onNode(absolutePath: string, node: any, parent: any) {
if (node.type === 'CallExpression') {
if (this.isRequire(node, 'bindings') && 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.isRequire(node, 'bindings') && 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
}
}
}
}
}
-69
View File
@@ -1,69 +0,0 @@
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
export interface FuseBoxFile {
info: { absPath: string }
absPath: string
contents: string
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
consume(): void
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export default function(options: ExtractNodeModuleOptions = {}) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test = /node_modules.*(\.js|\.node)$|\.node$/
public limit2Project = false
private modules: (keyof ExtractNodeModuleOptions)[]
constructor(public options = {}) {}
init(context: any) {
context.allowExtension('.node')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
embedDotNode(this.options, file)
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
}
}
}
]
})
}
}
-1
View File
@@ -1 +0,0 @@
export * from './fuse-native-module-plugin'
-256
View File
@@ -1,256 +0,0 @@
import { dirname, normalize, join } from 'path'
import { Buffer } from 'buffer'
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { Readable } from 'stream'
import { spawn } from 'child_process'
import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util'
import { NexeOptions, version } from './options'
import { NexeTarget } from './target'
import download = require('download')
import { getLatestGitRelease } from './releases'
import { IncomingMessage } from 'http'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
const configure = isWindows ? 'configure' : './configure'
export interface NexeFile {
filename: string
absPath: string
contents: string
}
export { NexeOptions }
interface NexeHeader {
resources: { [key: string]: number[] }
version: string
}
export class NexeCompiler<T extends NexeOptions = NexeOptions> {
private start = Date.now()
private env = { ...process.env }
private compileStep: { modify: Function; log: Function }
public log = new Logger(this.options.loglevel)
public src: string
public files: NexeFile[] = []
public shims: string[] = []
public input: string
public bundledInput?: string
public targets: NexeTarget[]
public target: NexeTarget
public resources: { bundle: Buffer; index: { [key: string]: number[] } } = {
index: {},
bundle: Buffer.from('')
}
public output = isWindows
? `${(this.options.output || this.options.name).replace(/\.exe$/, '')}.exe`
: `${this.options.output || this.options.name}`
private nodeSrcBinPath: string
constructor(public options: T) {
const { python } = (this.options = options)
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
this.src = join(this.options.temp, this.target.version)
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + version, 'info')
if (python) {
if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
} else {
this.env.PYTHON = python
}
}
}
@bound
addResource(file: string, contents: Buffer) {
const { resources } = this
resources.index[file] = [resources.bundle.byteLength, contents.byteLength]
resources.bundle = Buffer.concat([resources.bundle, contents])
}
@bound
async readFileAsync(file: string) {
this.assertBuild()
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
if (!cachedFile) {
const absPath = join(this.src, file)
cachedFile = {
absPath,
filename: file,
contents: await readFileAsync(absPath, 'utf-8').catch(x => {
if (x.code === 'ENOENT') return ''
throw x
})
}
this.files.push(cachedFile)
}
return cachedFile
}
@bound
writeFileAsync(file: string, contents: string | Buffer) {
this.assertBuild()
return writeFileAsync(join(this.src, file), contents)
}
@bound
async replaceInFileAsync(file: string, replace: string | RegExp, value: string) {
const entry = await this.readFileAsync(file)
entry.contents = entry.contents.replace(replace, value)
}
@bound
async setFileContentsAsync(file: string, contents: string) {
const entry = await this.readFileAsync(file)
entry.contents = contents
}
quit() {
const time = Date.now() - this.start
this.log.write(`Finished in ${time / 1000}s`)
return this.log.flush()
}
assertBuild() {
if (!this.options.build) {
throw new Error('This feature is only available with `--build`')
}
}
public getNodeExecutableLocation(target?: NexeTarget) {
if (target) {
return join(this.options.temp, target.toString())
}
return this.nodeSrcBinPath
}
private _runBuildCommandAsync(command: string, args: string[]) {
return new Promise((resolve, reject) => {
spawn(command, args, {
cwd: this.src,
env: this.env,
stdio: 'ignore'
})
.once('error', reject)
.once('close', (code: number) => {
if (code != 0) {
const error = `${command} ${args.join(' ')} exited with code: ${code}`
reject(new Error(error))
}
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.length
? ': ' + this.options.configure
: '...'}`
)
await this._configureAsync()
const buildOptions = this.options.make
this.compileStep.log(
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
)
await this._runBuildCommandAsync(make, buildOptions)
return createReadStream(this.getNodeExecutableLocation())
}
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
const downloadOptions: any = {
headers: {
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
}
const githubRelease = await getLatestGitRelease(downloadOptions)
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
if (!asset) {
throw new Error(`${assetName} not available, create it using the --build flag`)
}
const filename = this.getNodeExecutableLocation(target)
await download(
asset.browser_download_url,
dirname(filename),
downloadOptions
).on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
})
return createReadStream(filename)
}
getHeader() {
const version =
['configure', 'vcBuild', 'make'].reduce((a, c) => {
return (a += (this.options as any)[c]
.slice()
.sort()
.join())
}, '') + this.options.enableNodeCli
const header = {
resources: this.resources.index,
version: createHash('md5')
.update(version)
.digest('hex')
}
return `process.__nexe=${JSON.stringify(header)};`
}
async compileAsync(target: NexeTarget) {
const step = (this.compileStep = this.log.step('Compiling result'))
const build = this.options.build
const location = this.getNodeExecutableLocation(build ? undefined : target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
if (!build && !binary) {
step.modify('Fetching prebuilt binary')
binary = await this._fetchPrebuiltBinaryAsync(target)
}
if (!binary) {
binary = await this._buildAsync()
step.log('Node binary compiled')
}
return this._assembleDeliverable(binary)
}
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
if (this.options.empty) {
return binary
}
const artifact = new Readable({ read() {} })
binary.on('data', (chunk: Buffer) => {
artifact.push(chunk)
})
binary.on('close', () => {
const content = [this.shims.join(''), this.input].join(';')
artifact.push(content)
artifact.push(this.resources.bundle)
const lengths = Buffer.from(Array(16))
lengths.writeDoubleLE(Buffer.byteLength(content), 0)
lengths.writeDoubleLE(this.resources.bundle.byteLength, 8)
artifact.push(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
artifact.push(null)
})
return artifact
}
}
-68
View File
@@ -1,68 +0,0 @@
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, method: string = 'succeed'): LogStep {
if (this.silent) {
return { modify() {}, log() {} }
}
if (!this.ora.id) {
this.ora.start().text = text
if (method !== 'succeed') {
this.ora[method]()
}
} else {
this.ora[method]().text = text
this.ora.start()
}
return {
modify: this.modify,
log: this.verbose ? this.write : this.modify
} as LogStep
}
}
-49
View File
@@ -1,49 +0,0 @@
import { compose, Middleware } from 'app-builder'
import resource from './steps/resource'
import { NexeCompiler } from './compiler'
import { argv, version, help, normalizeOptionsAsync, NexeOptions, NexePatch } from './options'
import cli from './steps/cli'
import bundle from './steps/bundle'
import download from './steps/download'
import shim from './steps/shim'
import artifacts from './steps/artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = await normalizeOptionsAsync(compilerOptions)
const compiler = new NexeCompiler(options)
const build = compiler.options.build
if (options.clean) {
let path = compiler.src
if (!options.build) {
path = compiler.getNodeExecutableLocation(compiler.options.targets[0] as NexeTarget)
}
const step = compiler.log.step('Cleaning up nexe build artifacts...')
step.log(`Deleting contents at: ${path}`)
await rimrafAsync(path)
step.log(`Deleted contents at: ${path}`)
return compiler.quit()
}
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
const nexe = compose(resource, bundle, cli, buildSteps, shim, options.plugins as NexePatch[])
return callback
? void nexe(compiler).then(
() => callback && callback(null),
(e: Error) => {
if (callback) callback(e)
else throw e
}
)
: nexe(compiler)
}
export { argv, compile, version, NexeCompiler, NexeOptions, help }
-254
View File
@@ -1,254 +0,0 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { isWindows, padRight } from './util'
import { basename, extname, join, isAbsolute, relative, dirname } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os'
import * as c from 'chalk'
export const version = '{{replace:0}}'
export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
}
export interface NexeOptions {
build: boolean
input: string
output: string
targets: (string | NexeTarget)[]
name: string
cwd: string
flags: string[]
configure: string[]
vcBuild: string[]
make: string[]
snapshot?: string
resources: string[]
temp: string
rc: { [key: string]: string }
enableNodeCli: boolean
bundle: boolean | string
patches: (string | NexePatch)[]
plugins: (string | NexePatch)[]
native: any
empty: boolean
sourceUrl?: string
enableStdIn?: boolean
python?: string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
fakeArgv?: boolean
verbose?: boolean
info?: boolean
ico?: string
debugBundle?: boolean
warmup?: string
compress?: boolean
clean?: boolean
/**
* Api Only
*/
downloadOptions: any
}
const defaults = {
flags: [],
cwd: process.cwd(),
configure: [],
make: [],
targets: [],
vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: false,
compress: false,
build: false,
bundle: true,
patches: [],
plugins: []
}
const alias = {
i: 'input',
o: 'output',
v: 'version',
t: 'target',
b: 'build',
n: 'name',
r: 'resource',
a: 'resource',
p: 'python',
f: 'flag',
c: 'configure',
m: 'make',
h: 'help',
l: 'loglevel',
'fake-argv': 'fakeArgv'
}
const argv = parseArgv(process.argv, { alias, default: { ...defaults, enableStdIn: true } })
const g = c.gray
let help = `
${c.bold('nexe <entry-file> [options]')}
${c.underline.bold('Options:')}
-i --input -- application entry point
-o --output -- path to output file
-t --target -- node version description
-n --name -- main app module name
-r --resource -- *embed files (glob) within the binary
--plugin -- extend nexe runtime behavior
${c.underline.bold('Building from source:')}
-b --build -- build from source
-p --python -- python2 (as python) executable path
-f --flag -- *v8 flags to include during compilation
-c --configure -- *arguments to the configure step
-m --make -- *arguments to the make/build step
--snapshot -- path to a warmup snapshot
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--enableNodeCli -- enable node cli enforcement (blocks app cli)
${c.underline.bold('Other options:')}
--bundle -- custom bundling module with 'createBundle' export
--temp -- temp file storage default './nexe'
--cwd -- set the current working directory for the command
--fake-argv -- fake argv[1] with entry file
--clean -- force download of sources
--silent -- disable logging
--verbose -- set logging to verbose
-* variable key name * option can be used more than once`.trim()
help = EOL + help + EOL
function flatten(...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(cwd: string) {
let filename
try {
const file = require.resolve(cwd)
filename = basename(file).replace(extname(file), '')
} catch (_) {}
return filename ? filename : 'nexe_' + Date.now()
}
function extractLogLevel(options: NexeOptions) {
if (options.loglevel) return options.loglevel
if (options.silent) return 'silent'
if (options.verbose) return 'verbose'
return 'info'
}
function isName(name: string) {
return name && name !== 'index'
}
function extractName(options: NexeOptions) {
let name = options.name
if (!isName(name) && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '')
}
if (!isName(name)) {
name = tryResolveMainFileName(options.cwd)
}
if (!isName(name) && basename(options.cwd)) {
name = basename(options.cwd)
}
return name.replace(/\.exe$/, '')
}
function isEntryFile(filename: string) {
return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
}
function findInput(input: string, cwd: string) {
const maybeInput = argv._.slice().pop() || ''
if (input) {
return input
}
if (isEntryFile(maybeInput)) {
return maybeInput
}
if (!process.stdin.isTTY) {
return ''
}
try {
const main = require.resolve(cwd)
return './' + relative(cwd, main)
} catch (e) {
void e
}
return ''
}
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions> {
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
options.temp = options.temp || process.env.NEXE_TEMP || join(options.cwd, '.nexe')
options.input = findInput(options.input, options.cwd)
options.name = extractName(options)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget)
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (!options.targets.length) {
options.targets.push(getTarget())
}
if (options.build) {
const { arch } = options.targets[0] as NexeTarget
if (isWindows) {
options.make = Array.from(new Set(options.make.concat(arch)))
} else {
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
}
}
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.plugins = options.plugins.map(requireDefault)
options.patches = options.patches.map(requireDefault)
Object.keys(alias)
.filter(k => k !== 'rc')
.forEach(x => delete opts[x])
return Promise.resolve(options)
}
export { argv, normalizeOptionsAsync, help }
-53
View File
@@ -1,53 +0,0 @@
const fs = require('fs')
const fd = fs.openSync(process.execPath, 'r')
const stat = fs.statSync(process.execPath)
const footer = Buffer.from(Array(32))
fs.readSync(fd, footer, 0, 32, stat.size - 32)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
throw 'Invalid Nexe binary'
}
const contentSize = footer.readDoubleLE(16)
const resourceSize = footer.readDoubleLE(24)
const contentStart = stat.size - 32 - resourceSize - contentSize
const resourceStart = contentStart + contentSize
Object.defineProperty(
process,
'__nexe',
(function() {
let nexeHeader: any = null
return {
get: function() {
return nexeHeader
},
set: function(value: any) {
if (nexeHeader) {
throw new Error('__nexe cannot be reconfigured')
}
nexeHeader = Object.assign({}, value, {
layout: {
stat,
contentSize,
contentStart,
resourceSize,
resourceStart
}
})
Object.freeze(nexeHeader)
},
enumerable: false,
configurable: false
}
})()
)
const contentBuffer = Buffer.from(Array(contentSize))
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart)
fs.closeSync(fd)
const Module = require('module')
process.mainModule = new Module(process.execPath, null)
process.mainModule!.loaded = true
;(process.mainModule as any)._compile(contentBuffer.toString(), process.execPath)
-17
View File
@@ -1,17 +0,0 @@
import { NexeCompiler } from '../compiler'
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.target.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
@@ -1,12 +0,0 @@
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
@@ -1,16 +0,0 @@
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()
}
-21
View File
@@ -1,21 +0,0 @@
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
@@ -1,12 +0,0 @@
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()
}
-12
View File
@@ -1,12 +0,0 @@
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, NexeOptions } from '../compiler'
const patches = [gyp, nexePatches, buildFixes, cli, flags, ico, rc]
export default patches
-27
View File
@@ -1,27 +0,0 @@
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 = file.contents.replace(
new RegExp(`VALUE "${key}",*`),
`VALUE "${key}", ${value}`
)
})
;['PRODUCTVERSION', 'FILEVERSION'].forEach(x => {
if (options[x]) {
file.contents = file.contents.replace(new RegExp(x + ' .*$', 'm'), `${x} ${options[x]}`)
}
})
return next()
}
-19
View File
@@ -1,19 +0,0 @@
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()
}
-11
View File
@@ -1,11 +0,0 @@
import { NexeCompiler } from '../compiler'
import { readFileSync } from 'fs'
import { join } from 'path'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
'{{replace:lib/patches/boot-nexe.js}}'
)
return next()
}
-68
View File
@@ -1,68 +0,0 @@
import got = require('got')
import download = require('download')
import {
NodePlatform,
NodeArch,
platforms,
architectures,
NexeTarget,
getTarget,
targetsEqual
} from './target'
export { NexeTarget }
export interface GitAsset {
name: string
url: string
browser_download_url: string
}
export interface GitRelease {
tag_name: string
assets_url: string
upload_url: string
assets: GitAsset[]
}
interface NodeRelease {
version: string
}
async function getJson<T>(url: string, options?: any) {
return JSON.parse((await got(url, options)).body) as T
}
//TODO only build the latest of each major...?
function isBuildableVersion(version: string) {
const major = +version.split('.')[0]
return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4'
}
export function getLatestGitRelease(options?: any) {
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options)
}
export async function getUnBuiltReleases(options?: any) {
const nodeReleases = await getJson<NodeRelease[]>(
'https://nodejs.org/download/release/index.json'
)
const existingVersions = (await getLatestGitRelease(options)).assets.map(x => getTarget(x.name))
const versionMap: { [key: string]: true } = {}
return nodeReleases
.reduce((versions: NexeTarget[], { version }) => {
version = version.replace('v', '').trim()
if (!isBuildableVersion(version) || versionMap[version]) {
return versions
}
versionMap[version] = true
platforms.forEach(platform => {
architectures.forEach(arch => {
if (arch === 'x86' && platform === 'mac') return
versions.push(getTarget({ platform, arch, version }))
})
})
return versions
}, [])
.filter(x => !existingVersions.some(t => targetsEqual(t, x)))
}
-70
View File
@@ -1,70 +0,0 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from '../compiler'
import pify = require('pify')
const mkdirpAsync = pify(mkdirp)
const unlinkAsync = pify(unlink)
const readdirAsync = pify(readdir)
function readDirAsync(dir: string): Promise<string[]> {
return readdirAsync(dir).then(paths => {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : path as any))
})
).then(result => {
return [].concat(...(result as any))
})
})
}
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 Promise.all(
tmpFiles.map(async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
)
await next()
await Promise.all(tmpFiles.map(x => unlinkAsync(x)))
return Promise.all(
compiler.files.map(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)
})
)
}
-61
View File
@@ -1,61 +0,0 @@
import { NexeCompiler } from '../compiler'
import { readFileAsync, writeFileAsync } from '../util'
import { resolve, relative } from 'path'
import NativeModulePlugin from '../bundling/fuse-native-module-plugin'
import { NexeOptions } from '../options'
function createBundle(options: NexeOptions) {
const { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } = require('fuse-box')
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin(), NativeModulePlugin(options.native)]
if (options.compress) {
plugins.push(
QuantumPlugin({
target: 'server',
uglify: true,
bakeApiIntoBundle: options.name
})
)
}
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_LOG) || false,
homeDir: options.cwd,
sourceMaps: false,
writeBundles: false,
output: '$name.js',
target: 'server',
plugins
})
const input = relative(options.cwd, options.input).replace(/\\/g, '/')
fuse.bundle(options.name).instructions(`> ${input}`)
return fuse.run().then((x: any) => {
let output = ''
x.bundles.forEach((y: any) => (output = y.context.output.lastPrimaryOutput.content.toString()))
return output
})
}
export default async function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
compiler.input = await readFileAsync(compiler.options.input, 'utf-8')
return next()
}
if (compiler.options.empty || !compiler.options.input) {
compiler.input = ''
return next()
}
let producer = createBundle
if (typeof compiler.options.bundle === 'string') {
producer = require(resolve(compiler.options.cwd, compiler.options.bundle)).createBundle
}
compiler.input = await producer(compiler.options)
if ('string' === typeof compiler.options.debugBundle) {
await writeFileAsync(compiler.options.debugBundle, compiler.input)
}
return next()
}
-69
View File
@@ -1,69 +0,0 @@
import { normalize } from 'path'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function getStdIn(stdin: NodeJS.ReadStream): Promise<string> {
return new Promise(resolve => {
let out = ''
stdin
.setEncoding('utf8')
.on('readable', () => {
let current
while ((current = stdin.read())) {
out += current
}
})
.on('end', () => resolve(out))
})
}
/**
* The "cli" step detects the appropriate input. If no input options are passed,
* 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 { log } = compiler
let stdInUsed = false
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
stdInUsed = true
compiler.input = dequote(await getStdIn(process.stdin))
}
await next()
const target = compiler.options.targets.shift() as NexeTarget
const deliverable = await compiler.compileAsync(target)
return new Promise((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') //todo fix erroneous rw mode change
step.log(
`Entry: '${stdInUsed
? compiler.options.empty ? '[empty]' : '[stdin]'
: compiler.options.input}' written to: ${compiler.output}`
)
resolve(compiler.quit())
}
})
})
}
-45
View File
@@ -1,45 +0,0 @@
import download = require('download')
import { pathExistsAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
return download(url, dest, 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: ${dest}`))
}
/**
* 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, targets: [{ version }] } = compiler
const { 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 pathExistsAsync(src)) {
step.log('Source already downloaded')
return next()
}
if (compiler.options.build) {
await fetchNodeSourceAsync(src, url, step, downloadOptions)
}
return next()
}
-25
View File
@@ -1,25 +0,0 @@
import { readFileAsync, isDirectoryAsync, each } 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)
compiler.addResource(file, contents)
})
step.log(`Included ${count} file(s). ${(resources.bundle.byteLength / 1e6).toFixed(3)} MB`)
return next()
}
-93
View File
@@ -1,93 +0,0 @@
import { Stats } from 'fs'
import { ok } from 'assert'
import { resolve, normalize } from 'path'
const binary = (process as any).__nexe as NexeBinary
ok(binary)
const manifest = binary.resources
const isString = (x: any): x is string => typeof x === 'string' || x instanceof String
if (Object.keys(manifest).length) {
const fs = require('fs')
const originalReadFile = fs.readFile
const originalReadFileSync = fs.readFileSync
const resourceStart = binary.layout.resourceStart
let setupManifest = () => {
const manifest = binary.resources
Object.keys(manifest).forEach(key => {
const absolutePath = resolve(key)
if (!manifest[absolutePath]) {
manifest[absolutePath] = manifest[key]
}
const normalizedPath = normalize(key)
if (!manifest[normalizedPath]) {
manifest[normalizedPath] = manifest[key]
}
})
setupManifest = () => {}
}
//TODO track inflight fs reqs??
var nfs = {
readFile: function readFile(file: any, options: any, callback: any) {
setupManifest()
const entry = manifest[file]
if (!entry || !isString(file)) {
return originalReadFile.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const encoding = isString(options) ? options : null
callback = typeof options === 'function' ? options : callback
fs.open(process.execPath, 'r', function(err: Error, fd: number) {
if (err) return callback(err, null)
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function(
error: Error,
bytesRead: number,
result: Buffer
) {
if (error) {
return fs.close(fd, function() {
callback(error, null)
})
}
fs.close(fd, function(err: Error) {
if (err) {
return callback(err, result)
}
callback(err, encoding ? result.toString(encoding) : result)
})
})
})
},
readFileSync: function readFileSync(file: any, options: any) {
setupManifest()
const entry = manifest[file]
if (!entry || !isString(file)) {
return originalReadFileSync.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + 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)
return encoding ? result.toString(encoding) : result
}
}
Object.assign(fs, nfs)
}
interface NexeBinary {
resources: { [key: string]: number[] }
version: string
layout: {
stat: Stats
contentSize: number
contentStart: number
resourceSize: number
resourceStart: number
}
}
-25
View File
@@ -1,25 +0,0 @@
import { NexeCompiler } from '../compiler'
function wrap(code: string) {
return '!(function () {' + code + '})();'
}
export default function(compiler: NexeCompiler, next: () => Promise<void>) {
compiler.shims.push(wrap(compiler.getHeader()))
if (compiler.options.resources.length) {
compiler.shims.push(wrap('{{replace:lib/steps/shim-fs.js}}'))
}
if (compiler.options.fakeArgv) {
const nty = !process.stdin.isTTY
const input = nty ? '[stdin]' : compiler.options.input
compiler.shims.push(
wrap(`
var r = require('path').resolve;
process.argv.splice(1,0, ${nty ? `'${input}'` : `r("${input}")`});`)
)
}
return next()
}
-91
View File
@@ -1,91 +0,0 @@
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
export type NodeArch = 'x86' | 'x64'
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
architectures: NodeArch[] = ['x86', 'x64']
export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform | string
arch: NodeArch | string
}
//TODO bsd
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
win: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
linux: 'linux',
static: 'alpine',
alpine: 'alpine'
}
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
}
function isVersion(x: string) {
if (!x) {
return false
}
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ''))
}
function isPlatform(x: string): x is NodePlatform {
return x in prettyPlatform
}
function isArch(x: string): x is NodeArch {
return x in prettyArch
}
class Target implements NexeTarget {
constructor(public arch: NodeArch, public platform: NodePlatform, public version: string) {}
toJSON() {
return this.toString()
}
toString() {
return `${this.platform}-${this.arch}-${this.version}`
}
}
export function targetsEqual(a: NexeTarget, b: NexeTarget) {
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
}
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
const currentArch = process.arch
let arch = currentArch in prettyArch ? prettyArch[process.arch] : process.arch as NodeArch,
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
if (typeof target !== 'string') {
target = `${target.platform}-${target.arch}-${target.version}`
}
target
.toLowerCase()
.split('-')
.forEach(x => {
if (isVersion(x)) {
version = x.replace(/v/g, '')
}
if (isPlatform(x)) {
platform = prettyPlatform[x]
}
if (isArch(x)) {
arch = prettyArch[x]
}
})
return new Target(arch, platform, version)
}
-23
View File
@@ -1,23 +0,0 @@
declare module 'got' {
interface GotFn {
(url: string, options?: any): Promise<{ body: string }>
stream(url: string, optoins?: any): any
}
function got(url: string, options?: any): Promise<{ body: string }>
export = got as GotFn
}
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
}
-90
View File
@@ -1,90 +0,0 @@
import { readFile, writeFile, stat } from 'fs'
import { execFile } from 'child_process'
import pify = require('pify')
import rimraf = require('rimraf')
const rimrafAsync = pify(rimraf)
export async function each<T>(
list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any>
) {
const l = await list
for (let i = 0; i < l.length; i++) {
await action(l[i], i, l)
}
}
function falseOnEnoent(e: any) {
if (e.code === 'ENOENT') {
return false
}
throw e
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
const bound: MethodDecorator = function bound<T>(
target: Object,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>
) {
const configurable = true
return {
configurable,
get(this: T) {
const value = (descriptor.value as any).bind(this)
Object.defineProperty(this, propertyKey, { configurable, value, writable: true })
return value
}
}
}
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): Promise<Buffer>
(path: string, encoding: string): Promise<string>
}
const readFileAsync = pify(readFile)
const writeFileAsync = pify(writeFile)
const statAsync = pify(stat)
const execFileAsync = pify(execFile)
const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) {
return statAsync(path)
.then(x => true)
.catch(falseOnEnoent)
}
function isDirectoryAsync(path: string) {
return statAsync(path)
.then(x => x.isDirectory())
.catch(falseOnEnoent)
}
export {
dequote,
padRight,
bound,
isWindows,
rimrafAsync,
statAsync,
execFileAsync,
readFileAsync,
pathExistsAsync,
isDirectoryAsync,
writeFileAsync
}
-104
View File
@@ -1,104 +0,0 @@
import * as nexe from '../lib/nexe'
import {
getUnBuiltReleases,
getLatestGitRelease
} from '../lib/releases'
import * as ci from './ci'
import { runAlpineBuild } from './docker'
import { getTarget } from '../lib/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../lib/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.CIRCLECI),
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER)
|| Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
|| Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
'Authorization': 'token ' + env.GITHUB_TOKEN,
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
if (require.main === module) {
if (!isPullRequest) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
}
async function build () {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
return
}
const windowsBuild = releases.find(x => x.platform === 'windows')
const macBuild = releases.find(x => x.platform === 'mac')
const linuxOrAlpine = releases.find(x => x.platform === 'linux' || x.platform === 'alpine')
if (linuxOrAlpine) {
await ci.triggerDockerBuild(linuxOrAlpine, branchName)
}
if (macBuild) {
await ci.triggerMacBuild(macBuild, branchName)
}
if (windowsBuild) {
await ci.triggerWindowsBuild(windowsBuild)
}
}
if (env.NEXE_VERSION) {
const
target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
build: true,
target,
output
}
const stop = keepalive()
if (target.platform === 'alpine') {
await runAlpineBuild(target)
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
})
console.log(target + ' uploaded.')
}
}
}
function keepalive () {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary (file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
-52
View File
@@ -1,52 +0,0 @@
import { NexeTarget } from '../lib/releases'
import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerMacBuild(release: NexeTarget, branch: string) {
assert.ok(env.CIRCLE_TOKEN)
const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/${
branch}?circle-token=${env.CIRCLE_TOKEN}`
return got(circle, {
json: true,
body: {
build_parameters: {
NEXE_VERSION: release.toString()
}
}
})
}
export function triggerDockerBuild (release: NexeTarget, branch: string) {
assert.ok(env.TRAVIS_TOKEN)
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
return got(travis, {
json: true,
body: {
request: {
branch: branch,
config: {
merge_mode: 'deep_merge',
env: {
//use matrix so that secure global variable is merged
matrix: {
NEXE_VERSION: release.toString()
}
}
}
}
},
headers: {
'Travis-API-Version': '3',
'Authorization': `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerWindowsBuild (release: NexeTarget) {
const hasVersion = 'NEXE_VERSION' in env
env.NEXE_VERSION = hasVersion
? env.NEXE_VERSION!.trim()
: release.toString()
return Promise.resolve()
}
-54
View File
@@ -1,54 +0,0 @@
import { NexeTarget } from '../lib/target'
import { writeFileAsync, readFileAsync } from '../lib/util'
import { spawn } from 'child_process'
import got = require('got')
import execa = require('execa')
import { appendFileSync } from 'fs'
function alpine (target: NexeTarget) {
return `
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
ENV NODE_VERSION=${target.version}
ENV NEXE_VERSION=beta
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
mkdir /nexe_temp && mv /node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
cd /nexe_temp/\${NODE_VERSION} && \
./configure --prefix=/usr --fully-static && \
make && make install && \
paxctl -cm /usr/bin/node
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
`.trim()
}
export async function runAlpineBuild (target: NexeTarget) {
await writeFileAsync('Dockerfile', alpine(target))
const outFilename = 'nexe-alpine-build-log.txt'
await writeFileAsync(outFilename, '')
let output: any = []
try {
output.push(await execa.shell(`docker build -t nexe-alpine .`))
output.push(await execa.shell(`docker run -d --name nexe nexe-alpine sh`))
output.push(await execa.shell(`docker cp nexe:/out out`))
output.push(await execa.shell(`docker rm nexe`))
} catch(e) {
console.log('Error running docker', e)
} finally {
output.forEach((x: any) => {
appendFileSync(outFilename, x.stderr)
appendFileSync(outFilename, x.stdout)
})
await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
body: await readFileAsync(outFilename),
method: 'PUT'
})
.then(x => console.log('Posted docker log: ', x.body))
.catch(e => console.log('Error posting log', e))
}
}
-30
View File
@@ -1,30 +0,0 @@
import { writeFileSync, readFileSync } from 'fs'
/**
* post build step to insert code files into code files.
* '{{replace:path/to/file}}' => "file contents"
* And the package.json version.
*/
//inject('plugins/nexe-daemon/lib/index.js')
inject('lib/patches/third-party-main.js')
inject('lib/steps/shim.js')
inject('lib/options.js', JSON.stringify(require('../package.json').version))
function inject (filename: string, ...replacements: string[]) {
let contents = readFileSync(filename, 'utf8')
contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => {
if (!matches || !matches[1]) {
return substring
}
const [replace, file] = matches[1].split(':')
if (replace !== 'replace') {
return substring
}
console.log('Replacing: ', substring)
return replacements[+file]
? replacements[+file]
: JSON.stringify(readFileSync(file, 'utf8'))
})
writeFileSync(filename, contents)
console.log(`Wrote: ${filename}`)
}
+1
View File
@@ -0,0 +1 @@
true
+11
View File
@@ -0,0 +1,11 @@
/**
* Embedded Files Test
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
const nexeres = require('nexeres');
process.stdout.write(nexeres.get('hw.txt').toString('ascii'));
+28
View File
@@ -0,0 +1,28 @@
{
"name": "embededfiles-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node index.js"
},
"dependencies": {
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"resourceFiles": [
"hw.txt"
],
"browserify": {
"excludes": [],
"requires": []
},
"temp": "../src",
"debug": false,
"runtime": {
"nodeConfigureOpts": ["--fully-static"],
"framework": "node",
"version": "5.7.0"
}
}
}
+61
View File
@@ -0,0 +1,61 @@
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var jade = require('jade');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('express-test:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
process.stdout.write('true');
process.exit(0);
+28
View File
@@ -0,0 +1,28 @@
{
"name": "express-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
},
"nexe": {
"input": "bin/www",
"output": "test.nex",
"temp": "../src",
"debug": false,
"runtime": {
"framework": "node",
"version": "5.10.0",
"ignoreFlags": true
}
}
}
@@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
+9
View File
@@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
+9
View File
@@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
+6
View File
@@ -0,0 +1,6 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}
+5
View File
@@ -0,0 +1,5 @@
extends layout
block content
h1= title
p Welcome to #{title}
+7
View File
@@ -0,0 +1,7 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
+13
View File
@@ -0,0 +1,13 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// "use strict"; w/o --use_strict
let isStrict = (function() { return !this; })();
console.log(isStrict);
+15
View File
@@ -0,0 +1,15 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": false,
"js-flags": "--use_strict",
"node-args": ""
}
}
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
let gulp = require('gulp');
let nexe = require('nexe');
gulp.task( "compile", ( callback ) => {
let options = {
input: "index.js",
output: "test.nex",
python: "python",
nodeTempDir: "./tmp",
nodeVersion: "latest",
flags: false,
framework: "nodejs"
};
nexe.compile( options, ( err ) => {
console.log( err );
callback( err );
} );
} );
gulp.task('default', ['compile']);
+2
View File
@@ -0,0 +1,2 @@
// it probably executed.
console.log(true);
+26
View File
@@ -0,0 +1,26 @@
{
"name": "gulp-test-170",
"version": "0.0.1",
"description": "gulpfile.js issue #170",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"gulp": "^3.9.0",
"nexe": "^1.0.5"
},
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// test.nex --help
let status = false;
// console.log(process.argv);
if(process.argv[2]) {
status = true;
}
console.log(status);
+15
View File
@@ -0,0 +1,15 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
},
"dependencies": {}
}
+3
View File
@@ -0,0 +1,3 @@
require('js-yaml');
console.log(true);
+23
View File
@@ -0,0 +1,23 @@
{
"name": "js-yaml-148",
"version": "1.0.0",
"description": "#148 js-yaml fails to work",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.5.2"
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"version": "5.7.0",
"framework": "node"
}
}
}
+1 -3
View File
@@ -1,3 +1 @@
--check-leaks
--require ts-node/register
--recursive
--timeout 100000000
-28
View File
@@ -1,28 +0,0 @@
import { padRight, isWindows } from '../src/util'
import { expect } from 'chai'
import { blue as b } from 'chalk'
import { getTarget, NexeTarget } from '../src/target'
const arch = process.arch === 'ia32' ? 'x86' : process.arch
describe('Targets', () => {
[
['win-ia32-6.11.2', 'windows-x86-6.11.2'],
[{ version: '6.11.2', platform: 'win', arch: 'ia32' }, 'windows-x86-6.11.2'],
['win32-x64-6.11.2', 'windows-x64-6.11.2'],
['darwin-x64-v8.4.0', 'mac-x64-8.4.0'],
['static-x86-6.10.3', 'alpine-x86-6.10.3'],
['linux-x32', `linux-x86-${process.version.slice(1)}`],
['alpine-notsupported-6.10.3', `alpine-${arch}-6.10.3`],
['not-a-thing', getTarget(process).toString()]
].forEach(([input, expected]) => {
it(`should accept: ${padRight(JSON.stringify(input), 53)} ${b('->')} ${expected}`, () => {
expect(getTarget(input).toString()).to.equal(expected)
})
})
it ('should stringify and toString', () => {
expect(JSON.stringify(getTarget(process))).to.equal(`"${getTarget(process)}"`)
})
})
+249
View File
@@ -0,0 +1,249 @@
/**
* Test runner for nexe
*
* @author Jared Allard <jaredallard@outlook.com>
* @license MIT
**/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const spawn = require('child_process').spawn;
// our vars
const NEXE_VERSION = require('../package.json').version;
const testBase = __dirname;
const LOG_FILE = path.join(testBase, 'test.log');
if(fs.existsSync(LOG_FILE)) {
fs.unlinkSync(LOG_FILE);
}
let logfile = fs.createWriteStream(LOG_FILE, {
autoClose: false
});
const compileTest = function(test, cb) {
let testDir = path.join(testBase, test);
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
let npminst = spawn('npm', ['install'], {
cwd: testDir
});
npminst.on('error', function() {
throw new Error('Failed to execute NPM');
return cb(false);
});
npminst.on('close', function(code) {
if(code !== 0) {
throw new Error('NPM exited with non-zero status');
return cb(false);
}
let testinst = spawn('../../bin/nexe', [], {
cwd: testDir
});
testinst.on('error', function() {
throw new Error('Test failed to compile');
return cb(false);
});
testinst.on('close', function(code) {
let err = null;
if(code !== 0) {
throw new Error('Test gave non-zero code');
err = false;
}
logfile = fs.createWriteStream(LOG_FILE);
return cb(err);
});
testinst.stdout.on('data', function(d) {
process.stdout.write(d.toString('ascii'));
});
});
};
/**
* Run a test-generated binary
*
* @param {string} test - test name (dir)
* @param {array} optional args - array of args to pass to the binary
* @param {function} cb - callback
**/
const runTest = function(test, args, cb) {
let testDir = path.join(testBase, test);
let testBin = path.join(testDir, 'test.nex')
// check params
if(!Array.isArray(args)) {
cb = args;
args = [];
}
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
if(!fs.existsSync(testBin)) {
throw new Error('Test binary not found');
return cb(false);
}
let returned = false;
let testinst = spawn(testBin, args, {
cwd: testDir
})
testinst.stdout.on('data', function(d) {
let status = d.toString('ascii');
if(status === true || status === 'true' || status === 'true\n') {
testinst.stdin.pause();
testinst.kill();
// make it know we returned.
returned = true;
return cb();
}
})
testinst.on('close', function(c) {
if(returned !== true) {
throw new Error('Test failed (stdout never contained true)');
return cb(false);
}
});
}
console.log('NOTICE: The first test may take awhile as it may compile Node.js');
console.log(' Monitor the log file (test.log) for progress.');
after(function() {
console.log('notice: closing test.log file descriptor.')
logfile.end();
});
/**
* Tests express compatability.
**/
describe('nexe can bundle express', function() {
let testname = 'express-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe and embedding files', function() {
let testname = 'embeded-files';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('v8 flags test (strict mode)', function() {
let testname = 'flags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can be utilized in gulp test', function() {
let testname = 'gulp-test-170';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can ignore node.js flags test', function() {
let testname = 'ignoreFlags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, ['--help'], function(err) {
return next(err);
});
});
});
});
describe('nexe supports js-yaml test', function() {
let testname = 'js-yaml-148';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
-13
View File
@@ -1,13 +0,0 @@
{
"compilerOptions": {
"target":"es5",
"experimentalDecorators": true,
"lib": ["es2017"],
"module": "commonjs",
"outDir": "./lib",
"strict": true
},
"include":[
"src/**/*.ts"
]
}
+1079 -2079
View File
File diff suppressed because it is too large Load Diff