Compare commits

..

2 Commits

Author SHA1 Message Date
calebboyd 1e57f3268b fix: handle relative paths for input 2017-09-27 16:21:45 -05:00
James M. Greene dc76aea5c8 Prepend the Python path for Windows for overriding 2017-07-05 16:57:58 -05:00
95 changed files with 4070 additions and 6992 deletions
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env bash
export NVS_HOME="$HOME/.nvs"
. "$NVS_HOME/nvs.sh" install && nvs link lts && nvs use default
set -e -x
npm run asset-compile
-28
View File
@@ -1,28 +0,0 @@
version: 2
jobs:
build:
macos:
xcode: "9.0"
steps:
- checkout
- restore_cache:
keys:
- package-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }}
- package- # Fallback for the latest package.json cache
- run:
name: setup
command: .circleci/setup.sh
- save_cache:
key: package-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run:
name: build
command: .circleci/build.sh
workflows:
version: 2
test:
jobs:
- build
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -e
export NVS_HOME="$HOME/.nvs"
git clone --depth=1 https://github.com/jasongin/nvs "$NVS_HOME"
. "$NVS_HOME/nvs.sh" install
nvs add lts && nvs link lts nvs use default
node -v
npm -v
npm i
-8
View File
@@ -1,8 +0,0 @@
root = true
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
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
-15
View File
@@ -1,15 +0,0 @@
dist: xenial
language: node_js
node_js:
- '6'
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y g++-multilib
node_js:
- '8'
script: npm run asset-compile
env:
global:
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
notifications:
email: false
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": [ "ms-vscode.vscode-typescript-tslint-plugin" ]
}
-18
View File
@@ -1,18 +0,0 @@
{
"tslint.enable": true,
"tslint.run": "onType",
"tslint.autoFixOnSave": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"search.exclude": {
"lib/**/*": true,
"**/node_modules": true,
"**/bower_components": true
},
"typescript.tsdk": "node_modules\\typescript\\lib"
}
+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-2018 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
+219 -218
View File
@@ -1,248 +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/github/nexe/nexe/master.svg" alt="Mac Build Status"></a>
<a href="https://travis-ci.org/nexe/nexe/builds"><img src="https://img.shields.io/travis/nexe/nexe/master.svg" alt="Linux Build Status"></a>
<a href="https://ci.appveyor.com/project/calebboyd/nexe/history"><img src="https://img.shields.io/appveyor/ci/calebboyd/nexe/master.svg" alt="Windows 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 --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 acceptable 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')
})
```
## NexeOptions
### `options: object`
- #### `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`
- An object or string describing platform-arch-version. e.g. `'windows-ia32-10.13.0'`
- each segment is optional, and will be merged with the current environment
- Examples: ([full list](https://github.com/nexe/nexe/releases))
- `'win32-x86-10.13.0`
- `{ platform: 'alpine' }`
- `darwin-10.13.0`
- `linux-x64`
- `macos-10.13.0`
See [test/target.spec.ts](test/target.spec.ts)
- If the [`build`](#build-boolean) flag is set, the platform portion of the target is ignored.
- 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()
- #### `mangle: boolean`
- If set to false, nexe will not include the virtual filesystem (your application and resources) on the output.
- This will cause the output to error as an "Invalid Binary" unless a userland patch alters the contents of lib/_third_party_main.js in the nodejs source.
- default: true
- #### `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)
- #### `asset: string`
- Provide a pre-built nexe binary asset, this can either be an http or https URL or a file path.
- default: `null`
- #### `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
- default: `[]`
- #### `vcBuild: string[]`
- Options for windows build
- default: `['nosign', 'release']`
- #### `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`
- #### `ico: string`
- Path to a user provided icon to be used (Windows only). Requires `--build` to be set.
- #### `rc: object`
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
- Example (keys may vary depending ont the version. Reference the file linked above):
```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).
- Node CLI arguments passed via the [NODE_OPTIONS](https://nodejs.org/api/cli.html#cli_node_options_options) environment
variable will still be processed. NODE_OPTIONS support can be disabled with the `--without-node-options` configure flag.
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `ghToken: string`
- Provide a Github Token for accessing nexe releases
- This is usually needed in CI environments
- default: `process.env.GITHUB_TOKEN`
- #### `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: `[]`
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
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.
For examples, see the built in patches: [src/patches](src/patches).
### `NexeCompiler`
- `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.
#### `NexeFile`
- `contents: string`
- `absPath: string`
- `filename: string`
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`.
## Native Modules
TODO
- [ ] Implement support for `node-pre-gyp#find`.
## Contributing
Building
```
$ git clone git@github.com:nexe/nexe.git
$ cd nexe
$ yarn
var x = require(someVar);
```
Testing
In this case nexe won't bundle the file
```javascript
var x;
if (someCheck) {
x = require("./ver1.js");
} else {
x = require("./ver2.js");
}
```
$ npm test
In this case nexe will bundle both files.
Workarounds:
1) for dynamic requires that you want bundled add the following into your project
```javascript
var dummyToForceIncludeForBundle = false;
if (dummyToForceIncludeForBundle) {
require("./loadedDynamicallyLater.js");
// ...
}
```
this will trick the bundler into including them.
2) for dynamic files getting included that you don't want to be
```javascript
var moduleName = "./ver2.js";
if (someCheck) {
moduleName = "./ver1.js";
}
var x = require(moduleName);
```
Note: neither file will be bundled.
Using these two techniques you can change your application code so modules are not bundles, and generate a includes.js file as part of your build process so that the right files get bundled for your build configuration.
### &#95;&#95;dirname
Once the module is bundled it is part of the executable. &#95;&#95;dirname is therefore the executable dir (process.execPath). Thus if you put resources on a relative path from the the executable your app will be able to access them.
If you had a data file at `/dev/myNodeApp/stateManager/handler/data/some.csv`
and a file at `/dev/myNodeApp/stateManager/handler/loader.js`
```javascript
module.exports = fw.readFileSync(path.join(__dirname, "./data/some.csv"));
```
You would need to deploy some.csv in a sub dir `data/` along side your executable
There are potential use cases for &#95;&#95;dirname where the executable path is not the correct substitution, and could result in a silent error (possibly even in a dependency that you are unaware of).
Note: &#95;&#95;filename will be 'undefined'
### child_process.fork
child_process.spawn works is unmodified, but child_process.fork will make an attempt to launch a new instance of your executable and run the bundled module.
## Installation
Via NPM:
```bash
npm install nexe [-g]
```
Or git:
```bash
git clone https://github.com/jaredallard/nexe.git
```
### CLI Usage
```text
Usage: nexe -i [sources] -o [binary] [options]
Options:
-i, --input The entry javascript files [default: cwd]
-o, --output The output binary [default: out.nex]
-r, --runtime The node.js runtime to use [default: "latest"]
-t, --temp The path to store node.js sources [default: ./tmp/nexe]
-f, --flags Don't parse node and v8 flags, pass through app flags [default: false]
-v, --version Display version number
-p, --python Set path of python to use. [default: "python"]
-F, --framework Set the framework to use. [default: "nodejs"]
```
### Code Usage
```javascript
var nexe = require('nexe');
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) | [![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) | [Dustin Greif](https://github.com/dgreif) |
### 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)
-25
View File
@@ -1,25 +0,0 @@
image:
- Visual Studio 2017
- Visual Studio 2015
environment:
CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: 6ni/E6Rbmv6cFWW7QNLkpQY2fVpT7wr7y12GEtektN0=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
install:
- rmdir C:\cygwin /s /q
- rmdir C:\QT /s /q
- ps: Install-Product node 8
- curl -L -o nasm.exe https://www.nasm.us/pub/nasm/releasebuilds/2.13.03/win64/nasm-2.13.03-installer-x64.exe
- start /wait nasm.exe /S
- ps: $env:path="C:\Program Files\nasm;$($env:path)"
- npm i
build: off
test: off
build_script:
- npm run asset-compile
artifacts:
- path: $(HOMEPATH)\.nexe\**\node.exe
Executable
+101
View File
@@ -0,0 +1,101 @@
#!/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();
}
function toAbsolute(pt) {
if (path.isAbsolute(pt)) {
return pt
}
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 {
nexe.compile({
input: require.resolve(toAbsolute(argv.i)),
output: toAbsolute(argv.o),
flags: argv.f,
nodeVersion: argv.r,
python: argv.python,
nodeTempDir: toAbsolute(argv.t),
framework: argv.F
},
function(error) {
if (error) {
return console.log(error.message);
}
}
);
}
+8
View File
@@ -0,0 +1,8 @@
machine:
node:
version: 5.7.0
test:
override:
- npm test:
timeout: 5400
-8
View File
@@ -1,8 +0,0 @@
const express = require('express')
const path = require('path')
const app = express()
app.use('/', express.static(path.join(__dirname, 'public')))
app.listen(8888)
-373
View File
@@ -1,373 +0,0 @@
{
"name": "express-app",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
"requires": {
"mime-types": "~2.1.18",
"negotiator": "0.6.1"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
"integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
"requires": {
"bytes": "3.0.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.1",
"http-errors": "~1.6.2",
"iconv-lite": "0.4.19",
"on-finished": "~2.3.0",
"qs": "6.5.1",
"raw-body": "2.3.2",
"type-is": "~1.6.15"
}
},
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.16.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
"integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
"requires": {
"accepts": "~1.3.5",
"array-flatten": "1.1.1",
"body-parser": "1.18.2",
"content-disposition": "0.5.2",
"content-type": "~1.0.4",
"cookie": "0.3.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.1.1",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.3",
"qs": "6.5.1",
"range-parser": "~1.2.0",
"safe-buffer": "5.1.1",
"send": "0.16.2",
"serve-static": "1.13.2",
"setprototypeof": "1.1.0",
"statuses": "~1.4.0",
"type-is": "~1.6.16",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"statuses": "~1.4.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.0",
"statuses": ">= 1.4.0 < 2"
}
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
},
"mime-db": {
"version": "1.35.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
"integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
},
"mime-types": {
"version": "2.1.19",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
"requires": {
"mime-db": "~1.35.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.8.0"
}
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
},
"raw-body": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
"requires": {
"bytes": "3.0.0",
"http-errors": "1.6.2",
"iconv-lite": "0.4.19",
"unpipe": "1.0.0"
},
"dependencies": {
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
},
"http-errors": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
"requires": {
"depd": "1.1.1",
"inherits": "2.0.3",
"setprototypeof": "1.0.3",
"statuses": ">= 1.3.1 < 2"
}
},
"setprototypeof": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
}
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"send": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.6.2",
"mime": "1.4.1",
"ms": "2.0.0",
"on-finished": "~2.3.0",
"range-parser": "~1.2.0",
"statuses": "~1.4.0"
}
},
"serve-static": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.2",
"send": "0.16.2"
}
},
"setprototypeof": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
},
"statuses": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.18"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"name": "express-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
}
}
-1
View File
@@ -1 +0,0 @@
<h1>Hello World!</h1>
-32
View File
@@ -1,32 +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((error) => {
const NexeError = require('./lib/compiler').NexeError
const chalk = require('chalk')
const isSilent = Boolean(argv.silent === true || argv.loglevel === 'silent')
if (!isSilent) {
if (error instanceof NexeError) {
process.stderr.write(eol + chalk.red('Error: ') + error.message + eol
+ eol + 'See nexe -h for usage..' + eol + eol
)
} else {
process.stderr.write(error.stack + eol)
}
}
process.exit(1)
})
}
} else {
module.exports = require('./lib/nexe')
}
+13 -10
View File
@@ -1,19 +1,22 @@
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->
This is a
- [ ] Bug Report
- [ ] Feature Request
- [ ] Other
**Is this a BUG or a FEATURE REQUEST?:**
<br/>
If this is a bug report, What are the steps to reproduce?
**What happened:**
```
//provide configuration here
```
**What you expected to happen:**
**How to reproduce it (as minimally and precisely as possible):**
**Anything else we need to know?:**
**Environment**
<br/><br/>
Please also provide:
- Platform(OS/Version):
- Host Node Version:
- Target Node Version:
- 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;
-3581
View File
File diff suppressed because it is too large Load Diff
+39 -60
View File
@@ -1,76 +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": "3.0.2",
"version": "1.1.6",
"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",
"test": "mocha",
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
"prepare": "npm run lint && npm run build && npm test",
"prebuild": "rimraf lib",
"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"
],
"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": {
"@calebboyd/semaphore": "^1.3.1",
"app-builder": "^5.2.0",
"caw": "^2.0.1",
"chalk": "^2.4.2",
"cherow": "1.6.9",
"download": "^7.1.0",
"globby": "^9.1.0",
"got": "^9.6.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",
"multistream": "^2.1.1",
"ora": "^3.2.0",
"pify": "^4.0.1",
"resolve-dependencies": "^2.2.2",
"rimraf": "^2.6.3"
"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.1.7",
"@types/download": "^6.2.4",
"@types/execa": "^0.9.0",
"@types/globby": "^9.1.0",
"@types/got": "^9.4.1",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.5.2",
"@types/mocha": "^5.2.6",
"@types/multistream": "^2.1.1",
"@types/ora": "^3.2.0",
"@types/pify": "3.0.2",
"@types/rimraf": "2.0.2",
"@types/semver": "^5.5.0",
"chai": "^4.2.0",
"execa": "^1.0.0",
"mocha": "^6.0.2",
"prettier": "^1.16.4",
"ts-node": "^8.0.3",
"tslint": "^5.14.0",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.4.1"
"mocha": "^2.4.5",
"mocha-circleci-reporter": "0.0.1"
}
}
-316
View File
@@ -1,316 +0,0 @@
import { delimiter, resolve, normalize, join } from 'path'
import { Buffer } from 'buffer'
import { createReadStream, ReadStream } from 'fs'
import { spawn } from 'child_process'
import { Logger, LogStep } from './logger'
import {
readFileAsync,
writeFileAsync,
pathExistsAsync,
statAsync,
dequote,
isWindows,
bound,
semverGt
} from './util'
import { NexeOptions, version } from './options'
import { NexeTarget } from './target'
import combineStreams = require('multistream')
import { Bundle, toStream } from './fs/bundle'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
const configure = isWindows ? 'configure' : './configure'
type StringReplacer = (match: string) => string
export interface NexeFile {
filename: string
absPath: string
contents: string
}
export { NexeOptions }
export class NexeError extends Error {
constructor(m: string) {
super(m)
Object.setPrototypeOf(this, NexeError.prototype)
}
}
export class NexeCompiler {
/**
* Epoch of when compilation started
*/
private start = Date.now()
private compileStep: LogStep | undefined
public log = new Logger(this.options.loglevel)
/**
* Copy of process.env
*/
public env = { ...process.env }
/**
* Virtual FileSystem
*/
public bundle: Bundle
/**
* Root directory for the source of the current build
*/
public src: string
/**
* In memory files that are being manipulated by the compiler
*/
public files: NexeFile[] = []
/**
* Standalone pieces of code run before the application entrypoint
*/
public shims: string[] = []
/**
* The last shim (defaults to "require('module').runMain()")
*/
public startup: string = ''
/**
* The main entrypoint filename for your application - eg. node mainFile.js
*/
public entrypoint: string | undefined
/**
* Not used
*/
public targets: NexeTarget[]
/**
* Current target of the compiler
*/
public target: NexeTarget
/**
* Output filename (-o myapp.exe)
*/
public output = this.options.output
/**
* Flag to indicate whether or notstdin was used for input
*/
public stdinUsed = false
/**
* Path to the configure script
*/
public configureScript: string
/**
* The file path of node binary
*/
public nodeSrcBinPath: string
/**
* Remote asset path if available
*/
public remoteAsset: string
constructor(public options: NexeOptions) {
const { python } = (this.options = options)
//SOMEDAY iterate over multiple targets with `--outDir`
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
if (options.asset && options.asset.startsWith('http')) {
this.remoteAsset = options.asset
} else {
this.remoteAsset =
'https://github.com/nexe/nexe/releases/download/v3.0.0/' + this.target.toString()
}
this.src = join(this.options.temp, this.target.version)
this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '')
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + version, 'info')
this.bundle = new Bundle(options)
if (isWindows) {
const originalPath = process.env.PATH
delete process.env.PATH
this.env = { ...process.env }
this.env.PATH = python
? (this.env.PATH = dequote(normalize(python)) + delimiter + originalPath)
: originalPath
process.env.PATH = originalPath
} else {
this.env = { ...process.env }
python && (this.env.PYTHON = python)
}
}
@bound
addResource(file: string, content?: Buffer | string) {
return this.bundle.addResource(file, content)
}
get binaryConfiguration() {
return { resources: this.bundle.index }
}
get resourceSize() {
return this.bundle.blobSize
}
@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 | StringReplacer) {
const entry = await this.readFileAsync(file)
entry.contents = entry.contents.replace(replace, value as any)
}
@bound
async setFileContentsAsync(file: string, contents: string) {
const entry = await this.readFileAsync(file)
entry.contents = contents
}
quit(error?: any) {
const time = Date.now() - this.start
this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green')
return this.log.flush()
}
assertBuild() {
if (!this.options.build) {
throw new NexeError('This feature is only available with `--build`')
}
}
public getNodeExecutableLocation(target?: NexeTarget) {
if (this.options.asset && !this.options.asset.startsWith('http')) {
return resolve(this.options.cwd, this.options.asset)
}
if (target) {
return join(this.options.temp, target.toString())
}
return this.nodeSrcBinPath
}
private _runBuildCommandAsync(command: string, args: string[]) {
if (this.log.verbose) {
this.compileStep!.pause()
}
return new Promise((resolve, reject) => {
spawn(command, args, {
cwd: this.src,
env: this.env,
stdio: this.log.verbose ? 'inherit' : 'ignore'
})
.once('error', (e: Error) => {
if (this.log.verbose) {
this.compileStep!.resume()
}
reject(e)
})
.once('close', (code: number) => {
if (this.log.verbose) {
this.compileStep!.resume()
}
if (code != 0) {
const error = `${command} ${args.join(' ')} exited with code: ${code}`
reject(new NexeError(error))
}
resolve()
})
})
}
private _configureAsync() {
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
this.configureScript,
...this.options.configure
])
}
public async build(): Promise<ReadStream> {
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 _shouldCompileBinaryAsync(
binary: NodeJS.ReadableStream | null,
location: string | undefined
) {
//SOMEDAY combine make/configure/vcBuild/and modified times of included files
const { snapshot, build } = this.options
if (!binary) {
return true
}
if (build && snapshot != null && (await pathExistsAsync(snapshot))) {
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs
const binaryLastModified = (await statAsync(location)).mtimeMs
return snapshotLastModified > binaryLastModified
}
return false
}
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 (await this._shouldCompileBinaryAsync(binary, location)) {
binary = await this.build()
step.log('Node binary compiled')
}
return this._assembleDeliverable(binary!)
}
code() {
return [this.shims.join(''), this.startup].join(';')
}
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
if (!this.options.mangle) {
return binary
}
const startup = this.code(),
codeSize = Buffer.byteLength(startup)
const lengths = Buffer.from(Array(16))
lengths.writeDoubleLE(codeSize, 0)
lengths.writeDoubleLE(this.bundle.blobSize, 8)
return combineStreams([
binary,
toStream(startup),
this.bundle.toStream(),
toStream(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
])
}
}
-40
View File
@@ -1,40 +0,0 @@
# nexe-fs
### Getting Started:
This module contains a set of patches used to create (and use) the Nexe Virtual FileSystem
In order for NVFS to work with node's `require` method. A custom build of node must be used. If using the `nexe` cli this will be handled automatically
The customization should be applied as early as possbile when node is starting up.
i.e. The contents of `nexe-fs/bootstrap` should be put [here](https://github.com/nodejs/node/blob/0827c80920311fa9d1e6989c8a73aaaeca962eb7/lib/internal/bootstrap/node.js#L27-L28).
### Creating a NVFS:
To create a virtual file system use the `Bundle` Object, in your build process:
```javascript
const { Bundle } = require('nexe-fs')
const bundle = new Bundle()
await bundle.addResource(absoluteFileName)
bundle.toStream().pipe(fs.createWriteStream('./myFsBlob'))
```
In the entrypoint of your application, the fs patch needs to be applied (executed as early as possible)
For example, In the build process, the application's entrypoint could be prepended with the following:
```javascript
const codeToPrependToEntrypoint = fs.readFileSync(require.resolve('nexe-fs/patch'), 'utf-8') + `
shimFs({
blobPath: 'location/of/myFsBlob',
resources: ${JSON.stringify(bundle)},
layout: {
resourceStart: 0 // the offset within the blob that is referenced by the bundle index
}
})`
```
Since a custom node build is being used. A great place for this code would be the node source file: `_third_party_main.js`
-20
View File
@@ -1,20 +0,0 @@
if (true) {
const __nexe_patches = (process.nexe = { patches: {} }).patches
const slice = [].slice
const __nexe_noop_patch = function (original) {
const args = slice.call(arguments, 1)
return original.apply(this, args)
}
const __nexe_patch = function (obj, method, patch) {
const original = obj[method]
if (!original) return
__nexe_patches[method] = patch
obj[method] = function() {
const args = [original].concat(slice.call(arguments))
return __nexe_patches[method].apply(this, args)
}
}
__nexe_patch((process).binding('fs'), 'internalModuleReadFile', __nexe_noop_patch)
__nexe_patch((process).binding('fs'), 'internalModuleReadJSON', __nexe_noop_patch)
__nexe_patch((process).binding('fs'), 'internalModuleStat', __nexe_noop_patch)
}
-70
View File
@@ -1,70 +0,0 @@
import { stat as getStat, Stats, createReadStream } from 'fs'
import { relative } from 'path'
import { Readable } from 'stream'
import combineStreams = require('multistream')
const stat = (file: string): Promise<Stats> => {
return new Promise((resolve, reject) => {
getStat(file, (err, stats) => (err ? reject(err) : resolve(stats)))
})
}
function makeRelative(cwd: string, path: string) {
return './' + relative(cwd, path)
}
export function toStream(content: Buffer | string) {
const readable = new Readable({ read() {} })
readable.push(content)
readable.push(null)
return readable
}
export type File = { absPath: string; contents: string; deps: FileMap }
export type FileMap = { [absPath: string]: File | null }
export interface BundleOptions {
entries: string[]
cwd: string
expand: boolean
loadContent: boolean
files: FileMap
}
export class Bundle {
constructor({ cwd }: { cwd: string } = { cwd: process.cwd() }) {
this.cwd = cwd
}
cwd: string
blobSize: number = 0
index: { [relativeFilePath: string]: [number, number] } = {}
streams: (Readable | (() => Readable))[] = []
async addResource(absoluteFileName: string, content?: Buffer | string) {
let length = 0
if (content !== undefined) {
length = Buffer.byteLength(content)
} else {
const stats = await stat(absoluteFileName)
length = stats.size
}
const start = this.blobSize
this.blobSize += length
this.index[makeRelative(this.cwd, absoluteFileName)] = [start, length]
this.streams.push(() => (content ? toStream(content) : createReadStream(absoluteFileName)))
}
concat() {
throw new Error('Not Implemented')
}
toStream() {
return combineStreams(this.streams)
}
toJSON() {
return this.index
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"name": "nexe-fs",
"version": "1.0.1",
"description": "Virtual File System used by nexe",
"main": "bundle.js",
"files": [
"*.d.ts",
"*.js"
],
"author": "Caleb Boyd",
"dependencies": {
"multistream": "^2.1.1"
},
"license": "MIT"
}
-339
View File
@@ -1,339 +0,0 @@
import { Stats } from 'fs'
export interface NexeBinary {
blobPath: string
resources: { [key: string]: number[] }
layout: {
stat: Stats
resourceStart: number
contentSize?: number
contentStart?: number
resourceSize?: number
}
}
let originalFsMethods: any = null
let nexeBinary: NexeBinary | null = null
function restoreFs(fs: any = require('fs')): NexeBinary | false {
if (!nexeBinary) {
return false
}
const source = nexeBinary
Object.assign(fs, originalFsMethods)
nexeBinary = originalFsMethods = null
return source
}
function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (originalFsMethods !== null) {
return
}
originalFsMethods = Object.assign({}, fs)
nexeBinary = binary
const { blobPath, resources: manifest } = binary
const { resourceStart, stat } = binary.layout
const directories: { [key: string]: { [key: string]: boolean } } = {},
notAFile = '!@#$%^&*',
isWin = process.platform.startsWith('win'),
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
isNotFile = () => false,
isNotDirectory = isNotFile,
isFile = () => true,
noop = () => {},
isDirectory = isFile,
path = require('path')
let log = (text: string) => true
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
log = (text: string) => process.stderr.write('[nexe] - ' + text + '\n')
}
const getKey = function getKey(filepath: string | Buffer | null): string {
if (Buffer.isBuffer(filepath)) {
filepath = filepath.toString()
}
if (!isString(filepath)) {
return notAFile
}
let key = path.resolve(filepath)
if (isWin && key.substr(1, 2) === ':\\') {
key = key[0].toUpperCase() + key.substr(1)
}
return key
}
const statTime = function() {
return {
dev: 0,
ino: 0,
nlink: 0,
rdev: 0,
uid: 123,
gid: 500,
blksize: 4096,
blocks: 0,
atime: new Date(stat.atime),
atimeMs: stat.atime.getTime(),
mtime: new Date(stat.mtime),
mtimeMs: stat.mtime.getTime(),
ctime: new Date(stat.ctime),
ctimMs: stat.ctime.getTime(),
birthtime: new Date(stat.birthtime),
birthtimeMs: stat.birthtime.getTime()
}
}
const createStat = function(extensions: any) {
return Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions)
}
const ownStat = function(filepath: any) {
setupManifest()
const key = getKey(filepath)
if (directories[key]) {
let mode = binary.layout.stat.mode
mode |= fs.constants.S_IFDIR
mode &= ~fs.constants.S_IFREG
return createStat({ mode, size: 0 })
}
if (manifest[key]) {
return createStat({ size: manifest[key][1] })
}
}
function makeLong(filepath: string) {
return (path as any)._makeLong && (path as any)._makeLong(filepath)
}
function fileOpts(options: any) {
return !options ? {} : isString(options) ? { encoding: options } : options
}
let setupManifest = () => {
Object.keys(manifest).forEach(filepath => {
const entry = manifest[filepath]
const absolutePath = getKey(filepath)
const longPath = makeLong(absolutePath)
const normalizedPath = path.normalize(filepath)
if (!manifest[absolutePath]) {
manifest[absolutePath] = entry
}
if (longPath && !manifest[longPath]) {
manifest[longPath] = entry
}
if (!manifest[normalizedPath]) {
manifest[normalizedPath] = manifest[filepath]
}
let currentDir = path.dirname(absolutePath)
let prevDir = absolutePath
while (currentDir !== prevDir) {
directories[currentDir] = directories[currentDir] || {}
directories[currentDir][path.basename(prevDir)] = true
const longDir = makeLong(currentDir)
if (longDir && !directories[longDir]) {
directories[longDir] = directories[currentDir]
}
prevDir = currentDir
currentDir = path.dirname(currentDir)
}
})
;(manifest[notAFile] as any) = false
;(directories[notAFile] as any) = false
setupManifest = noop
}
//naive patches intended to work for most use cases
const nfs: any = {
existsSync: function existsSync(filepath: string) {
setupManifest()
const key = getKey(filepath)
if (manifest[key] || directories[key]) {
return true
}
return originalFsMethods.existsSync.apply(fs, arguments)
},
realpath: function realpath(filepath: any, options: any, cb: any): void {
setupManifest()
const key = getKey(filepath)
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
return process.nextTick(() => cb(null, filepath))
}
return originalFsMethods.realpath.call(fs, filepath, options, cb)
},
realpathSync: function realpathSync(filepath: any, options: any) {
setupManifest()
const key = getKey(filepath)
if (manifest[key]) {
return filepath
}
return originalFsMethods.realpathSync.call(fs, filepath, options)
},
readdir: function readdir(filepath: string | Buffer, options: any, callback: any) {
setupManifest()
const dir = directories[getKey(filepath)]
if (dir) {
if ('function' === typeof options) {
callback = options
options = { encoding: 'utf8' }
}
process.nextTick(() => callback(null, Object.keys(dir)))
} else {
return originalFsMethods.readdir.apply(fs, arguments)
}
},
readdirSync: function readdirSync(filepath: string | Buffer, options: any) {
setupManifest()
const dir = directories[getKey(filepath)]
if (dir) {
return Object.keys(dir)
}
return originalFsMethods.readdirSync.apply(fs, arguments)
},
readFile: function readFile(filepath: any, options: any, callback: any) {
setupManifest()
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.readFile.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const encoding = fileOpts(options).encoding
callback = typeof options === 'function' ? options : callback
fs.open(blobPath, '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)
})
})
})
},
createReadStream: function createReadStream(filepath: any, options: any) {
setupManifest()
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.createReadStream.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const opts = fileOpts(options)
return fs.createReadStream(
blobPath,
Object.assign({}, opts, {
start: resourceOffset,
end: resourceOffset + length - 1
})
)
},
readFileSync: function readFileSync(filepath: any, options: any) {
setupManifest()
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.readFileSync.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const encoding = fileOpts(options).encoding
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
},
statSync: function statSync(filepath: string | Buffer) {
const stat = ownStat(filepath)
if (stat) {
return stat
}
return originalFsMethods.statSync.apply(fs, arguments)
},
stat: function stat(filepath: string | Buffer, callback: any) {
const stat = ownStat(filepath)
if (stat) {
process.nextTick(() => {
callback(null, stat)
})
} else {
return originalFsMethods.stat.apply(fs, arguments)
}
}
}
if (typeof fs.exists === 'function') {
nfs.exists = function(filepath: string, cb: Function) {
cb = cb || noop
const exists = nfs.existsSync(filepath)
process.nextTick(() => cb(exists))
}
}
const patches = (process as any).nexe.patches || {}
delete (process as any).nexe
patches.internalModuleReadFile = function(this: any, original: any, ...args: any[]) {
const [filepath] = args
setupManifest()
if (manifest[filepath]) {
log('read (hit) ' + filepath)
return nfs.readFileSync(filepath, 'utf-8')
}
log('read (miss) ' + filepath)
return original.call(this, ...args)
}
patches.internalModuleReadJSON = patches.internalModuleReadFile
patches.internalModuleStat = function(this: any, original: any, ...args: any[]) {
setupManifest()
const [filepath] = args
if (manifest[filepath]) {
log('stat (hit) ' + filepath + ' ' + 0)
return 0
}
if (directories[filepath]) {
log('stat dir (hit) ' + filepath + ' ' + 1)
return 1
}
const res = original.call(this, ...args)
if (res === 0) {
log('stat (miss) ' + filepath + ' ' + res)
} else if (res === 1) {
log('stat dir (miss) ' + filepath + ' ' + res)
} else {
log('stat (fail) ' + filepath + ' ' + res)
}
return res
}
if (typeof fs.exists === 'function') {
nfs.exists = function(filepath: string, cb: Function) {
cb = cb || noop
const exists = nfs.existsSync(filepath)
if (!exists) {
return originalFsMethods.exists(filepath, cb)
}
process.nextTick(() => cb(exists))
}
}
Object.assign(fs, nfs)
return true
}
export { shimFs, restoreFs }
-72
View File
@@ -1,72 +0,0 @@
import colors from 'chalk'
import ora from 'ora'
const frameLength = 120
export interface LogStep {
modify(text: string, color?: string): void
log(text: string, color?: string): void
pause(): void
resume(): void
}
export class Logger {
public 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() {}, pause() {}, resume() {} }
}
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,
pause: () => this.ora.stopAndPersist(),
resume: () => this.ora.start()
} as LogStep
}
}
-49
View File
@@ -1,49 +0,0 @@
import { EOL } from 'os'
import { compose } from 'app-builder'
import { NexeCompiler, NexeError } from './compiler'
import { normalizeOptions, NexeOptions, NexePatch } from './options'
import resource from './steps/resource'
import clean from './steps/clean'
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'
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
let error = null,
options: NexeOptions | null = null,
compiler: NexeCompiler | null = null
try {
options = normalizeOptions(compilerOptions)
compiler = new NexeCompiler(options)
await compose(
clean,
resource,
cli,
bundle,
shim,
download,
options.build ? [artifacts, ...patches, ...(options.patches as NexePatch[])] : [],
options.plugins as NexePatch[]
)(compiler)
} catch (e) {
error = e
}
if (error) {
compiler && compiler.quit(error)
if (callback) return callback(error)
return Promise.reject(error)
}
if (callback) callback(null)
}
export { compile, NexeCompiler }
export { argv, version, NexeOptions, help } from './options'
-299
View File
@@ -1,299 +0,0 @@
import * as parseArgv from 'minimist'
import { NexeCompiler, NexeError } from './compiler'
import { isWindows, STDIN_FLAG } from './util'
import { basename, extname, join, isAbsolute, resolve } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL, homedir } from 'os'
import chalk from 'chalk'
import { resolveFileNameSync } from 'resolve-dependencies'
const caw = require('caw')
const c = process.platform === 'win32' ? chalk.constructor({ enabled: false }) : 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
asset: 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
mangle: boolean
ghToken: string
sourceUrl?: string
enableStdIn?: boolean
python?: string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
fakeArgv?: boolean
verbose?: boolean
info?: boolean
ico?: string
debugBundle?: boolean
warmup?: string
clean?: boolean
/**
* Api Only
*/
downloadOptions: any
}
const defaults = {
flags: [],
cwd: process.cwd(),
configure: [],
mangle: true,
make: [],
targets: [],
vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: 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',
'gh-token': 'ghToken'
}
const argv = parseArgv(process.argv, { alias, default: { ...defaults } })
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
-a --asset -- alternate asset path, file or url pointing to a base (nexe) 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
--patch -- module with middelware default export for adding a build patch
--no-mangle -- used when generating base binaries, or when patching _third_party_main manually.
--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: any, option: any) => {
const key = option.split('-')[1]
map[key] = options[option]
delete options[option]
return map
},
{} as any
)
}
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' && name !== STDIN_FLAG
}
function extractName(options: NexeOptions) {
let name = options.name
//try and use the input filename as the output filename if its not index
if (!isName(name) && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '')
}
//try and use the directory as the filename
if (!isName(name) && basename(options.cwd)) {
name = basename(options.cwd)
}
return name.replace(/\.exe$/, '')
}
function padRelative(input: string) {
let prefix = ''
if (!input.startsWith('.')) {
prefix = './'
}
return prefix + input
}
function isEntryFile(filename?: string): filename is string {
return Boolean(filename && !isAbsolute(filename))
}
export function resolveEntry(
input: string,
cwd: string,
maybeEntry: string | undefined,
bundle: boolean | string
) {
let result = null
if (input === '-' || maybeEntry === '-') {
return STDIN_FLAG
}
if (input && isAbsolute(input)) {
return input
}
if (input) {
const inputPath = padRelative(input)
result = resolveFileNameSync(cwd, inputPath)
}
if (isEntryFile(maybeEntry) && (!result || !result.absPath)) {
const inputPath = padRelative(maybeEntry)
result = resolveFileNameSync(cwd, inputPath)
}
if (!process.stdin.isTTY && (!result || !result.absPath) && bundle === defaults.bundle) {
return STDIN_FLAG
}
if (!result || !result.absPath) {
result = resolveFileNameSync(cwd, '.')
}
if (!result.absPath) {
throw new NexeError(`Entry file "${input || ''}" not found!`)
}
return result.absPath
}
function isCli(options?: Partial<NexeOptions>) {
return argv === options
}
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
const cwd = (options.cwd = resolve(options.cwd))
options.temp = options.temp
? resolve(cwd, options.temp)
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
const maybeEntry = isCli(input) ? argv._[argv._.length - 1] : undefined
options.input = resolveEntry(options.input, cwd, maybeEntry, options.bundle)
options.enableStdIn = isCli(input) && options.input === STDIN_FLAG
options.name = extractName(options)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget)
if (!options.targets.length) {
options.targets.push(getTarget())
}
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
options.make = flatten(isWindows ? options.vcBuild : options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.downloadOptions = options.downloadOptions || {}
options.downloadOptions.headers = options.downloadOptions.headers || {}
options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)'
options.downloadOptions.agent = process.env.HTTPS_PROXY
? caw(process.env.HTTPS_PROXY, { protocol: 'https' })
: options.downloadOptions.agent || require('https').globalAgent
options.downloadOptions.rejectUnauthorized = process.env.HTTPS_PROXY ? false : true
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
options.output =
(options.targets[0] as NexeTarget).platform === 'windows'
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
: `${options.output || options.name}`
options.output = resolve(cwd, options.output)
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.mangle = 'mangle' in opts ? opts.mangle : true
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
if ((!options.mangle && !options.bundle) || options.patches.length) {
options.build = true
}
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}`])))
}
}
Object.keys(alias)
.filter(k => k !== 'rc')
.forEach(x => delete opts[x])
return options
}
export { argv, normalizeOptions, help }
-57
View File
@@ -1,57 +0,0 @@
const fs = require('fs'),
fd = fs.openSync(process.execPath, 'r'),
stat = fs.statSync(process.execPath),
tailSize = Math.min(stat.size, 16000),
tailWindow = Buffer.from(Array(tailSize))
fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
if (footerPosition == -1) {
throw 'Invalid Nexe binary'
}
const footer = tailWindow.slice(footerPosition, footerPosition + 32),
contentSize = footer.readDoubleLE(16),
resourceSize = footer.readDoubleLE(24),
contentStart = stat.size - tailSize + footerPosition - resourceSize - contentSize,
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('This property is readonly')
}
nexeHeader = Object.assign({}, value, {
blobPath: process.execPath,
layout: {
stat,
contentSize,
contentStart,
resourceSize,
resourceStart
}
})
Object.freeze(nexeHeader)
},
enumerable: false,
configurable: false
}
})()
)
const contentBuffer = Buffer.from(Array(contentSize)),
Module = require('module')
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart)
fs.closeSync(fd)
new Module(process.execPath, null)._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()
}
-44
View File
@@ -1,44 +0,0 @@
import { NexeCompiler } from '../compiler'
import { semverGt } from '../util'
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
if (compiler.options.enableNodeCli) {
return next()
}
if (semverGt(compiler.target.version, '11.6.0')) {
await compiler.replaceInFileAsync(
'src/node.cc',
/(?<!int )ProcessGlobalArgs\(/g,
'0;//ProcessGlobalArgs('
)
} else if (semverGt(compiler.target.version, '10.9')) {
await compiler.replaceInFileAsync('src/node.cc', /(?<!void )ProcessArgv\(/g, '//ProcessArgv(')
} else if (semverGt(compiler.target.version, '9.999')) {
await compiler.replaceInFileAsync(
'src/node.cc',
'int i = 1; i < v8_argc; i++',
'int i = v8_argc; i < v8_argc; i++'
)
let matches = 0
await compiler.replaceInFileAsync('src/node.cc', /v8_argc > 1/g, match => {
if (matches++) {
return 'false'
}
return match
})
} else {
const nodeccMarker = 'argv[index][0] =='
await compiler.replaceInFileAsync(
'src/node.cc',
`${nodeccMarker} '-'`,
// allow NODE_OPTIONS, introduced in 8.0
semverGt(compiler.target.version, '7.99')
? `(${nodeccMarker} (is_env ? '-' : ']'))`
: `(${nodeccMarker} ']')`
)
}
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 bootNexe 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 snapshot from './snapshot'
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc, snapshot]
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()
}
-21
View File
@@ -1,21 +0,0 @@
import { resolve } from 'path'
import { NexeCompiler } from '../compiler'
export default async function(compiler: NexeCompiler, next: () => Promise<void>) {
const { snapshot, warmup, cwd } = compiler.options
if (!snapshot) {
return next()
}
await compiler.replaceInFileAsync(
compiler.configureScript,
'def configure_v8(o):',
`def configure_v8(o):\n o['variables']['embed_script'] = r'${resolve(
cwd,
snapshot
)}'\n o['variables']['warmup_script'] = r'${resolve(cwd, warmup || snapshot)}'`
)
return next()
}
-64
View File
@@ -1,64 +0,0 @@
import { NexeCompiler } from '../compiler'
import { parse } from 'cherow'
import { semverGt } from '../util'
function walkSome(node: any, visit: Function) {
if (!node || typeof node.type !== 'string' || node._visited) {
return false
}
visit(node)
node._visited = true
for (let childNode in node) {
const child = node[childNode]
if (Array.isArray(child)) {
for (let i = 0; i < child.length; i++) {
if (walkSome(child[i], visit)) {
return true
}
}
} else if (walkSome(child, visit)) {
return true
}
}
return false
}
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
let bootFile = 'lib/internal/bootstrap_node.js'
const { version } = compiler.target
if (version.startsWith('4.')) {
bootFile = 'src/node.js'
} else if (semverGt(version, '9.10.1')) {
bootFile = 'lib/internal/bootstrap/node.js'
}
const file = await compiler.readFileAsync(bootFile),
ast = parse(file.contents, {
loc: true,
tolerant: true,
next: true,
globalReturn: true,
node: true,
skipShebang: true
}),
location = { start: { line: 0 } }
walkSome(ast, (node: any) => {
if (!location.start.line && node.type === 'BlockStatement') {
//Find the first block statement and mark the location
Object.assign(location, node.loc)
return true
}
})
const fileLines = file.contents.split('\n')
fileLines.splice(location.start.line, 0, '{{replace:lib/fs/bootstrap.js}}' + '\n')
file.contents = fileLines.join('\n')
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
'{{replace:lib/patches/boot-nexe.js}}'
)
return next()
}
-60
View File
@@ -1,60 +0,0 @@
import got = require('got')
import { platforms, architectures, NexeTarget, getTarget, targetsEqual } from './target'
export { NexeTarget }
const versionsToSkip = [0, 1, 2, 3, 4, 5, 7, 9, 11]
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
}
function isBuildableVersion(version: string) {
const major = Number(version.split('.')[0])
return !versionsToSkip.includes(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
if (arch.includes('arm')) return
versions.push(getTarget({ platform, arch, version }))
})
})
return versions
}, [])
.filter(x => !existingVersions.some(t => targetsEqual(t, x)))
}
-69
View File
@@ -1,69 +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)
})
)
}
-62
View File
@@ -1,62 +0,0 @@
import { NexeCompiler, NexeError } from '../compiler'
import { resolve, relative } from 'path'
import { each } from '@calebboyd/semaphore'
import resolveFiles from 'resolve-dependencies'
import { dequote, STDIN_FLAG } from '../util'
import { Readable } from 'stream'
function getStdIn(stdin: Readable): 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))
})
}
export default async function bundle(compiler: NexeCompiler, next: any) {
const { bundle, cwd, input } = compiler.options
compiler.entrypoint = './' + relative(cwd, input)
compiler.startup = ';require("module").runMain();'
if (!bundle) {
await compiler.addResource(resolve(cwd, input))
return next()
}
let code = ''
if (typeof bundle === 'string') {
code = await require(bundle).createBundle(compiler.options)
}
if (input === STDIN_FLAG) {
compiler.stdinUsed = true
compiler.entrypoint = './__nexe_stdin.js'
code = code || dequote(await getStdIn(process.stdin))
await compiler.addResource(resolve(cwd, compiler.entrypoint), code)
return next()
}
const { files, warnings } = await resolveFiles(
input,
...Object.keys(compiler.bundle.index).filter(x => x.endsWith('.js')),
{ cwd, expand: true, loadContent: false }
)
if (
warnings.filter(x => x.startsWith('Error parsing file') && !x.includes('node_modules')).length
) {
throw new NexeError('Parsing Error:\n' + warnings.join('\n'))
}
await each(Object.keys(files), (filename: string) => compiler.addResource(filename), {
concurrency: 10
})
return next()
}
-19
View File
@@ -1,19 +0,0 @@
import { NexeCompiler } from '../compiler'
import { rimrafAsync } from '../util'
import { NexeTarget } from '../target'
export default async function clean(compiler: NexeCompiler, next: () => Promise<any>) {
const { options } = compiler
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()
}
return next()
}
-55
View File
@@ -1,55 +0,0 @@
import { dirname, normalize, relative } from 'path'
import { createWriteStream, chmodSync, statSync } from 'fs'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
import { STDIN_FLAG } from '../util'
import mkdirp = require('mkdirp')
/**
* 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:
*
* @param {*} compiler
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
await next()
const { log } = compiler,
target = compiler.options.targets.shift() as NexeTarget,
deliverable = await compiler.compileAsync(target),
output = normalize(compiler.output!)
mkdirp.sync(dirname(output))
return new Promise((resolve, reject) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(output))
.on('error', reject)
.once('close', (e: Error) => {
if (e) {
reject(e)
} else if (compiler.output) {
const output = compiler.output,
mode = statSync(output).mode | 0o111,
inputFileLogOutput = relative(process.cwd(), compiler.options.input),
outputFileLogOutput = relative(process.cwd(), output)
chmodSync(output, mode.toString(8).slice(-3))
step.log(
`Entry: '${
compiler.stdinUsed
? compiler.options.mangle
? STDIN_FLAG
: '[none]'
: inputFileLogOutput
}' written to: ${outputFileLogOutput}`
)
resolve(compiler.quit())
}
})
})
}
-78
View File
@@ -1,78 +0,0 @@
import download = require('download')
import { pathExistsAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler, NexeError } from '../compiler'
import { dirname } from 'path'
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}`))
}
async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
const { target, remoteAsset } = compiler,
filename = compiler.getNodeExecutableLocation(target)
try {
await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on(
'response',
(res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
})
}
)
} catch (e) {
if (e.statusCode === 404) {
throw new NexeError(`${remoteAsset} is not available, create it using the --build flag`)
} else {
throw new NexeError('Error downloading prebuilt binary: ' + e)
}
}
}
/**
* 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, target } = compiler,
{ version } = target,
{ sourceUrl, downloadOptions, build } = compiler.options,
url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`,
step = log.step(
`Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}`
),
exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target),
downloadExists = await pathExistsAsync(build ? src : exeLocation)
if (downloadExists) {
step.log('Already downloaded...')
return next()
}
if (build) {
await fetchNodeSourceAsync(src, url, step, downloadOptions)
} else {
await fetchPrebuiltBinary(compiler, step)
}
return next()
}
-22
View File
@@ -1,22 +0,0 @@
import { isDirectoryAsync, each } from '../util'
import globs from 'globby'
import { NexeCompiler } from '../compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<any>) {
const { cwd } = compiler.options
if (!compiler.options.resources.length) {
return next()
}
const step = compiler.log.step('Bundling Resources...')
let count = 0
await each(globs(compiler.options.resources, { cwd }), async file => {
if (await isDirectoryAsync(file)) {
return
}
count++
step.log(`Including file: ${file}`)
await compiler.addResource(file)
})
step.log(`Included ${count} file(s). ${(compiler.resourceSize / 1e6).toFixed(3)} MB`)
return next()
}
-31
View File
@@ -1,31 +0,0 @@
import { NexeCompiler } from '../compiler'
import { wrap } from '../util'
export default function(compiler: NexeCompiler, next: () => Promise<void>) {
compiler.shims.push(
wrap(
`process.__nexe = ${JSON.stringify(compiler.binaryConfiguration)};\n` +
'{{replace:lib/fs/patch.js}}' +
'\nshimFs(process.__nexe)'
)
)
compiler.shims.push(
wrap(`
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
const cluster = require('cluster')
cluster._setupWorker()
delete process.env.NODE_UNIQUE_ID
}
`)
)
compiler.shims.push(
wrap(`
if (!process.send) {
process.argv.splice(1,0, require.resolve(${JSON.stringify(compiler.entrypoint)}))
}
`)
)
return next()
}
-96
View File
@@ -1,96 +0,0 @@
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
export type NodeArch = 'x86' | 'x64' | 'arm7l' | 'arm6l' | 'arm64'
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
architectures: NodeArch[] = ['x86', 'x64', 'arm7l', 'arm6l', 'arm64']
export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform | string
arch: NodeArch | string
}
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
win: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
linux: 'linux',
static: 'alpine',
alpine: 'alpine'
}
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
arm6: 'arm6l',
arm64: 'arm64',
arm6l: 'arm6l',
arm: 'arm7l',
arm7: 'arm7l',
arm7l: 'arm7l',
amd64: 'x64',
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
}
const got: GotFn
export = got
}
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
}
-113
View File
@@ -1,113 +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 const STDIN_FLAG = '[stdin]'
export async function each<T>(
list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any>
) {
const l = await list
return Promise.all(l.map(action))
}
export function wrap(code: string) {
return '!(function () {' + code + '})();'
}
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 | Symbol,
descriptor: TypedPropertyDescriptor<T>
) {
const configurable = true
return {
configurable,
get(this: T) {
const value = (descriptor.value as any).bind(this)
Object.defineProperty(this, propertyKey as string, {
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)
}
/**
* @param version See if this version is greather than the second one
* @param operand Version to compare against
*/
function semverGt(version: string, operand: string) {
const [cMajor, cMinor, cPatch] = version.split('.').map(Number)
let [major, minor, patch] = operand.split('.').map(Number)
if (!minor) minor = 0
if (!patch) patch = 0
return (
cMajor > major ||
(cMajor === major && cMinor > minor) ||
(cMajor === major && cMinor === minor && cPatch > patch)
)
}
export {
dequote,
padRight,
semverGt,
bound,
isWindows,
rimrafAsync,
statAsync,
execFileAsync,
readFileAsync,
pathExistsAsync,
isDirectoryAsync,
writeFileAsync
}
-106
View File
@@ -1,106 +0,0 @@
import * as nexe from '../lib/nexe'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import * as ci from './ci'
import { runDockerBuild } from './docker'
import { getTarget, targetsEqual } from '../lib/target'
import { pathExistsAsync, 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),
isWindows = Boolean(env.APPVEYOR),
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
}
console.log('Building: ', target)
const stop = keepalive()
if (['arm7l', 'arm6l', 'arm64', 'alpine'].includes(target.platform)) {
await runDockerBuild(target)
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
const unbuiltReleases = await getUnBuiltReleases({ headers })
if (!unbuiltReleases.some(x => targetsEqual(x, target))) {
console.log(`${target} already uploaded.`)
return
}
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
...headers,
'Content-Type': 'application/octet-stream'
}
}).catch(reason => {
console.log(reason && reason.response && reason.response.body)
throw reason
})
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
})
}
-58
View File
@@ -1,58 +0,0 @@
import { NexeTarget } from '../lib/releases'
import got = require('got')
import * as assert from 'assert'
import { getTarget } from '../lib/target'
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 version = 'NEXE_VERSION' in env ? env.NEXE_VERSION!.trim() : release.toString()
if (getTarget(version).version.startsWith('10.')) {
if ((env.APPVEYOR_BUILD_WORKER_IMAGE || '').includes('2017')) {
env.NEXE_VERSION = version
}
} else {
env.NEXE_VERSION = version
}
return Promise.resolve()
}
-72
View File
@@ -1,72 +0,0 @@
import { NexeTarget, architectures } from '../lib/target'
import { writeFileAsync, readFileAsync } from '../lib/util'
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=latest
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 --no-mangle --temp /nexe_temp -c="--fully-static" -o out --enableStdIn=false
`.trim()
}
function arm(target: NexeTarget) {
return `
FROM hypriot/rpi-node
ENV NEXE_VERSION=latest
WORKDIR /
RUN yarn global add nexe@\${NEXE_VERSION} && \
nexe --build --no-mangle -o out -t ${target.version}
`.trim()
}
export async function runDockerBuild(target: NexeTarget) {
//todo switch on alpine and arm
const dockerfile = alpine(target)
await writeFileAsync('Dockerfile', dockerfile)
const outFilename = 'nexe-docker-build-log.txt'
await writeFileAsync(outFilename, '')
let output: any = []
try {
output.push(await execa.shell(`docker build -t nexe-docker .`))
output.push(await execa.shell(`docker run -d --name nexe nexe-docker 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')
appendFileSync(outFilename, e.message)
} 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))
}
}
-38
View File
@@ -1,38 +0,0 @@
import { writeFileSync, readFileSync } from 'fs'
/**
* post build step to insert code files into code files (naively).
* '{{replace:path/to/file}}' => "file contents"
* And the package.json version.
*/
//inject('plugins/nexe-daemon/lib/index.js')
cp('src/fs/package.json', 'lib/fs/package.json')
cp('src/fs/bootstrap.js', 'lib/fs/bootstrap.js')
cp('src/fs/README.md', 'lib/fs/README.md')
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}`)
}
function cp(from: string, to: string) {
const file = readFileSync(from)
writeFileSync(to, file)
console.log('Copied: ', from, 'To: ', to)
}
+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
-1
View File
@@ -1 +0,0 @@
module.exports = 'abc'
-1
View File
@@ -1 +0,0 @@
console.log('hello world', require('./dependency'))
-3
View File
@@ -1,3 +0,0 @@
{
"main": "entry.js"
}
View File
+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 -4
View File
@@ -1,4 +1 @@
./test/**/*.spec.ts
--check-leaks
--require ts-node/register
--recursive
--timeout 100000000
-56
View File
@@ -1,56 +0,0 @@
import { normalizeOptions } from '../src/options'
import { expect } from 'chai'
import * as path from 'path'
const ext = process.platform === 'win32' ? '.exe' : ''
describe('options', () => {
describe('cwd', () => {
it('should use process.cwd() if nothing is provided', () => {
const options = normalizeOptions()
expect(options.cwd).to.equal(process.cwd())
})
it('should use the main module in a package directory', () => {
const options = normalizeOptions()
expect(options.input).to.equal(path.resolve('./index.js'))
})
it('should resolve relative paths for input', () => {
const options = normalizeOptions({ input: 'test/fixture/entry.js' })
expect(options.input).to.equal(path.resolve('./test/fixture/entry.js'))
})
it('should accept a module entry as input', () => {
const options = normalizeOptions({ input: 'test/fixture' })
expect(options.input).to.equal(path.resolve('./test/fixture/entry.js'))
})
it('should resolve pathed options against cwd', () => {
const cwd = path.join(process.cwd(), 'test/fixture')
const options = normalizeOptions({
cwd,
input: 'entry',
output: 'abc',
temp: './d'
})
expect(options.temp).to.equal(path.resolve(cwd, './d'))
expect(options.input).to.equal(path.resolve(cwd, 'entry.js'))
expect(options.output).to.equal(path.resolve(cwd, `abc${ext}`))
})
})
describe('output', () => {
it('should work', () => {
const options = normalizeOptions({
output: './some-output'
})
expect(options.output).to.equal(path.resolve(`./some-output${ext}`))
})
it('should default to the input file name if not index', () => {
const options = normalizeOptions({
input: './test/fixture'
})
expect(options.output).to.equal(path.resolve(`./entry${ext}`))
})
it('should default to the folder/project name if filename is index', () => {
const options = normalizeOptions({ cwd: './test/fixture2' })
expect(options.output).to.equal(path.resolve(`./test/fixture2/fixture2${ext}`))
})
})
})
-29
View File
@@ -1,29 +0,0 @@
import { padRight, isWindows } from '../src/util'
import { expect } from 'chai'
import chalk from 'chalk'
import { getTarget, NexeTarget } from '../src/target'
const b = chalk.blue
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'],
['win-amd64-6.11.2', 'windows-x64-6.11.2'],
['darwin-x64-v8.4.0', 'mac-x64-8.4.0'],
['macos-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);
});
});
});
});
-14
View File
@@ -1,14 +0,0 @@
{
"compilerOptions": {
"target":"es5",
"skipLibCheck": true,
"experimentalDecorators": true,
"lib": ["es2018"],
"module": "commonjs",
"outDir": "./lib",
"strict": true
},
"include":[
"src/**/*.ts"
]
}
-9
View File
@@ -1,9 +0,0 @@
{
"rulesDirectory": ["tslint-plugin-prettier", "tslint-config-prettier"],
"linterOptions": {
"exclude": ["**/node_modules/**/*"]
},
"rules": {
"prettier": [true, { "semi": false, "printWidth": 100, "singleQuote": true }]
}
}
+1610
View File
File diff suppressed because it is too large Load Diff