Compare commits
117 Commits
v1.0.0
...
v2.0.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 34c51064f4 | |||
| be48e5a2f3 | |||
| 0340c01b36 | |||
| e62cd19b42 | |||
| fc99386199 | |||
| f949bf3040 | |||
| 0a1e747ca3 | |||
| 91aa014b7d | |||
| fcd803686c | |||
| ba78c7dc54 | |||
| 430d63e7eb | |||
| 3e435ecbf8 | |||
| aeefcbeba3 | |||
| 563aa97a89 | |||
| 88a46dc730 | |||
| 1a6b113d6e | |||
| d375c8f214 | |||
| 15d919c302 | |||
| 6adf3c82c3 | |||
| 73af22cc92 | |||
| 844d34b29a | |||
| 548e6454ef | |||
| ce2c4cefd0 | |||
| ac64063916 | |||
| 249ee50130 | |||
| 76ec6a8be3 | |||
| cac2759d3a | |||
| 811f460763 | |||
| e12eb8b289 | |||
| 1f35fe498e | |||
| ca7874c854 | |||
| 95e7971a96 | |||
| f8151854f1 | |||
| ac4ca3fe56 | |||
| 0d67b1dd4c | |||
| b10b3d268d | |||
| 40f1e6056f | |||
| ec57ed508f | |||
| 7668d7b471 | |||
| 72afbe68a4 | |||
| ad83cf769f | |||
| 8395f588a3 | |||
| df3d667e4e | |||
| f928f3e758 | |||
| 2f4356d6d2 | |||
| 5cd8b5f177 | |||
| de58ad2593 | |||
| b822f58973 | |||
| de23c5e4c8 | |||
| 8f96b2bca9 | |||
| 968d68b218 | |||
| 5136d3ebe9 | |||
| 6a52ab861d | |||
| 9b2b340f6d | |||
| 03374f9f47 | |||
| 5d56a05531 | |||
| c113fbc731 | |||
| 1e2522852f | |||
| f6b8affdbf | |||
| 2b027229f1 | |||
| 970817e0f4 | |||
| 23947c6d80 | |||
| 88e5e05d49 | |||
| 541765e1d1 | |||
| 0c401f3c1e | |||
| f6a18e18db | |||
| da7b6af0fa | |||
| 694602fa22 | |||
| 7994dc56c4 | |||
| f8b9de750f | |||
| cfd01c60c6 | |||
| 4899c1983b | |||
| 040e3c39a6 | |||
| ebdd8055bd | |||
| 220836ea2f | |||
| 599858f799 | |||
| b526d16298 | |||
| d9d43332c6 | |||
| 42314a7083 | |||
| f690b81a97 | |||
| 6faed1838f | |||
| 841f22f0bb | |||
| ab1d92cf68 | |||
| 208c2b2b56 | |||
| 3b25e7409b | |||
| aa6988c85f | |||
| a8c13a436b | |||
| f50e94a781 | |||
| 71f39e5a11 | |||
| ae4baad4f3 | |||
| 0ffbb5be66 | |||
| 97f6f71788 | |||
| 6a07ea62ea | |||
| ce2db38b92 | |||
| f75948b7e8 | |||
| 36a31b1048 | |||
| b25643483e | |||
| 3f997ca93a | |||
| 6273bd2e9a | |||
| 4d8a1849e8 | |||
| df246cd2fc | |||
| ea38821fd4 | |||
| bbe26640df | |||
| 54675f0e23 | |||
| 3ac02efd18 | |||
| b100922d91 | |||
| 87660c3682 | |||
| 635cd62196 | |||
| 336a5ea2e3 | |||
| 7fe0d207de | |||
| 3e708ca916 | |||
| de84339c80 | |||
| fb81a9c75e | |||
| 0e34905ddc | |||
| c1e7416644 | |||
| 53daa903ea | |||
| 9e2a5f1c78 |
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
#https://github.com/Microsoft/vscode/issues/23983#event-1087502108
|
||||
#trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
+9
-8
@@ -1,9 +1,10 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
*.nex
|
||||
src
|
||||
nexe.exe
|
||||
nexe
|
||||
npm-debug.log
|
||||
# test/**/src
|
||||
test/**/out.nex
|
||||
temp/
|
||||
coverage
|
||||
node_modules
|
||||
*.log
|
||||
*.exe
|
||||
.idea
|
||||
.nexe
|
||||
lib
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
script: npm run nexe-build
|
||||
env:
|
||||
global:
|
||||
secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"prettier.semi": false,
|
||||
"prettier.singleQuote": true,
|
||||
"prettier.typescriptEnable": ["typescript"],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"lib/**/*": true,
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true
|
||||
}
|
||||
}
|
||||
+40
-27
@@ -1,51 +1,64 @@
|
||||
# Nexe ChangeLog
|
||||
|
||||
## 2017-05-29, Version v2.0.0-beta.1, @calebboyd
|
||||
|
||||
Nexe 2.0 is a rewrite to enable some new features. These include:
|
||||
* Quick Builds!
|
||||
* Userland build patches
|
||||
* 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,4 +1,4 @@
|
||||
Copyright (c) 2013 Craig Condon
|
||||
Copyright (c) 2013-2017 Nexe Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@@ -1,179 +1,198 @@
|
||||
### Nexe
|
||||
<p align="center"><img src="https://cloud.githubusercontent.com/assets/2391349/23598327/a17bb68a-01ee-11e7-8f55-88a5fc96e997.png" /></p>
|
||||
|
||||
[](https://gitter.im/jaredallard/nexe?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://david-dm.org/crcn/nexe)
|
||||
[](http://waffle.io/jaredallard/nexe)
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/nexe/nexe"><img src="https://img.shields.io/circleci/project/nexe/nexe.svg" alt="Build Status"></a>
|
||||
<a href="https://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>
|
||||
|
||||
# NOTICE: Node 5.x.x is broken right now.
|
||||
<p align="center"><code>npm i nexe@beta -g</code></p>
|
||||
<p align="center">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
|
||||
|
||||
Nexe is a command-line utility that compiles your Node.js application into a single executable file.
|
||||
<p align="center">
|
||||
<img src="https://cloud.githubusercontent.com/assets/5818726/26533446/ce19ee5a-43de-11e7-9540-caf7ebd93370.gif"/>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
|
||||
### Motivation
|
||||
## Motivation and Features
|
||||
|
||||
- Supports production ready builds
|
||||
- Self contained applications
|
||||
- Ability to run multiple applications with *different* node.js runtimes.
|
||||
- Distributable binaries without needing node / npm.
|
||||
- Starts faster.
|
||||
- Distribute binaries without needing node / npm.
|
||||
- Idempotent builds
|
||||
- Start and deploy faster.
|
||||
- Lockdown specific application versions, and easily rollback.
|
||||
- Faster deployments.
|
||||
- Flexible build pipeline
|
||||
- Cross platform builds
|
||||
|
||||
## Building Requirements
|
||||
## Usage
|
||||
|
||||
- Linux / Mac / BSD / Windows
|
||||
- Python 2.6 or 2.7 (use --python if not in PATH)
|
||||
- Windows: Visual Studio 2010+
|
||||
- Existing application bundle:
|
||||
|
||||
## Caveats
|
||||
`nexe my-app-bundle.js -o my-app`
|
||||
|
||||
### Doesn't support native modules
|
||||
- stdin interface
|
||||
|
||||
- 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.
|
||||
`rollup -c | nexe --resource ./public/**/* -o my-app.exe`
|
||||
|
||||
### Doesn't support dynamic require statements
|
||||
For more CLI options see: `nexe --help`
|
||||
|
||||
## Including Additional Resources
|
||||
|
||||
Additional resources can be added to the binary by passing `-r glob/pattern/**/*`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`
|
||||
|
||||
## Compiling Node
|
||||
|
||||
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. To build node, use the `build` option/flag
|
||||
|
||||
Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
|
||||
|
||||
## Node.js API
|
||||
|
||||
Using Nexe Programatically
|
||||
|
||||
#### Example
|
||||
|
||||
Such As:
|
||||
|
||||
```javascript
|
||||
var x = require(someVar);
|
||||
const { compile } = require('nexe')
|
||||
|
||||
compile({
|
||||
input: './my-app.js',
|
||||
output: './my-app.exe',
|
||||
build: true, //builds node
|
||||
patches: [
|
||||
async (compiler, next) => {
|
||||
await compiler.setFileContentsAsync(
|
||||
'lib/new-native-module.js',
|
||||
'module.exports = 42'
|
||||
)
|
||||
return next()
|
||||
}
|
||||
]
|
||||
}).then(() => {
|
||||
console.log('success')
|
||||
})
|
||||
```
|
||||
|
||||
In this case nexe won't bundle the file
|
||||
### `options`
|
||||
|
||||
```javascript
|
||||
var x;
|
||||
if (someCheck) {
|
||||
x = require("./ver1.js");
|
||||
} else {
|
||||
x = require("./var2.js");
|
||||
}
|
||||
```
|
||||
- #### `build: boolean`
|
||||
- Build node from source (required in beta)
|
||||
- #### `input: string`
|
||||
- Input bundle file path
|
||||
- default: stdin or the current directory's main file (package.json)
|
||||
- #### `output: string`
|
||||
- Output executable file path
|
||||
- default: same as `name` with an OS specific extension.
|
||||
- #### `target: string`
|
||||
- Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'`
|
||||
- default: `[process.platform, process.arch, process.version.slice(1)].join('-')`
|
||||
- #### `bundle: string | 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 (
|
||||
filename: string,
|
||||
options: { name: string, minify: any, cwd: string }
|
||||
): Promise<string>`
|
||||
```
|
||||
- default: true, uses the internal fuse-box configuration
|
||||
- #### `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()
|
||||
- #### `version: string`
|
||||
- The Node version you're building for
|
||||
- default: `process.version.slice(1)`
|
||||
- #### `python: string`
|
||||
- On Linux this is the path pointing to your python2 executable
|
||||
- On Windows this is the directory where `python` can be accessed
|
||||
- default: `null`
|
||||
- #### `flags: Array<string>`
|
||||
- Array of node runtime flags to build node with.
|
||||
- Example: `['--expose-gc']`
|
||||
- default: `[]`
|
||||
- #### `configure: Array<string>`
|
||||
- Array of arguments for the node build configure step
|
||||
- Example: `['--with-dtrace', '--dest-cpu=x64']`
|
||||
- default: `[]`
|
||||
- #### `make: Array<string>`
|
||||
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
|
||||
- default: `[]` or `['nosign', 'release']` for non windows systems
|
||||
- #### `make: Array<string>`
|
||||
- Alias for `make` option
|
||||
- #### `snapshot: string`
|
||||
- path to a file to be used as the warmup snapshot for the build
|
||||
- default: `null`
|
||||
- #### `resources: Array<string>`
|
||||
- Array of globs with files to include in the build
|
||||
- Example: `['./public/**/*']`
|
||||
- default: `[]`
|
||||
- #### `temp: string`
|
||||
- Path to use for storing nexe's build files
|
||||
- Override in the env with `NEXE_TEMP`
|
||||
- default: `./.nexe` in the cwd
|
||||
- #### `ico: string`
|
||||
- Path to a user provided icon to be used (Windows only).
|
||||
- #### `rc: object`
|
||||
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
|
||||
- Example: `{ CompanyName: "ACME Corp" }`
|
||||
- default: `{}`
|
||||
- #### `clean: boolean`
|
||||
- If included, nexe will remove temporary files for accompanying configuration and exit
|
||||
- #### `enableNodeCli: boolean`
|
||||
- Enable the original Node CLI (will prevent application cli from working)
|
||||
- default: `false`
|
||||
- #### `sourceUrl: string`
|
||||
- Provide an alternate url for the node source. Should be a `.tar.gz`
|
||||
- #### `loglevel: string`
|
||||
- Set the loglevel, info, silent, or verbose
|
||||
- default: `'info'`
|
||||
- #### `patches: Array<NexePatch>`
|
||||
- Userland patches for patching or modifying node source
|
||||
- default: `[]`
|
||||
|
||||
In this case nexe will bundle both files.
|
||||
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
|
||||
|
||||
Workarounds:
|
||||
1) for dynamic requires that you want bundled add the following into your project
|
||||
A patch is just a middleware function that takes two arguments, the `compiler`, and `next`. The compiler is described below, and `next` ensures that the pipeline continues. Its invocation should always be awaited or returned to ensure correct behavior.
|
||||
|
||||
```javascript
|
||||
var dummyToForceIncludeForBundle = false;
|
||||
if (dummyToForceIncludeForBundle) {
|
||||
require("./loadedDynamicallyLater.js");
|
||||
// ...
|
||||
}
|
||||
```
|
||||
this will trick the bundler into including them.
|
||||
For examples, see the built in patches: [src/patches](src/patches)
|
||||
|
||||
2) for dynamic files getting included that you don't want to be
|
||||
### `NexeCompiler`
|
||||
|
||||
```javascript
|
||||
var moduleName = "./ver2.js";
|
||||
if (someCheck) {
|
||||
moduleName = "./ver1.js";
|
||||
}
|
||||
var x = require(moduleName);
|
||||
```
|
||||
Note: neither file will be bundled.
|
||||
- `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.
|
||||
- `files: Array<NexeFile>`
|
||||
- The cache of the currently read, modified, or created files within the downloaded Node.js source.
|
||||
|
||||
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.
|
||||
#### `NexeFile`
|
||||
- `contents: string`
|
||||
- `absPath: string`
|
||||
- `filename: string`
|
||||
|
||||
### __dirname
|
||||
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`.
|
||||
|
||||
Once the module is bundled it is part of the executable. __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.
|
||||
### Native Modules
|
||||
|
||||
If you had a data file at `/dev/myNodeApp/stateManager/handler/data/some.csv`
|
||||
and a file at `/dev/myNodeApp/stateManager/handler/loader.js`
|
||||
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
|
||||
Take a look at the [example](examples/native-build/build.js)
|
||||
|
||||
```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 __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: __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/crcn/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',
|
||||
output: 'path/to/bin',
|
||||
nodeVersion: '5.5.0',
|
||||
nodeTempDir: 'src',
|
||||
python: 'path/to/python',
|
||||
resourceFiles: [ 'path/to/a/file' ],
|
||||
flags: true,
|
||||
framework: "node" // node, nodejs, or iojs
|
||||
}, function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```
|
||||
"nexe": {
|
||||
"input": "./bin/nexe",
|
||||
"output": "nexe^$",
|
||||
"temp": "src",
|
||||
"runtime": {
|
||||
"framework": "node",
|
||||
"version": "5.5.0",
|
||||
"ignoreFlags": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
* output: can use ^$ for platform specific file extension
|
||||
- [ ] Implement support `node-pre-gyp#find`.
|
||||
|
||||
## Maintainers
|
||||
|
||||
* __Jared Allard__ ([@jaredallard](https://github.com/jaredallard)) <[jaredallard@outlook.com](mailto:jaredallard@outlook.com)> (Active)
|
||||
* __Christopher Karper__ ([@ckarper](https://github.com/CKarper)) <[Christopher.Karper@gmail.com](mailto:Christopher.Karper@gmail.com)> (Active)
|
||||
* __Craig Condon__ ([@crcn](https://github.com/crcn)) <[craig.j.condon@gmail.com](mailto:craig.j.condon@gmail.com)> (Old Project Owner)
|
||||
[](https://jaredallard.me/) | [](https://github.com/calebboyd) | [](https://github.com/ckarper) | [](https://github.com/dgreif) |
|
||||
---|---|---|---
|
||||
[Jared Allard](https://github.com/jaredallard) | [Caleb Boyd](http://github.com/calebboyd) | [Christopher Karper](https://github.com/ckarper) | [Dustin Greif](https://github.com/dgreif) |
|
||||
|
||||
### Former
|
||||
|
||||
- [Craig Condon](http://crcn.codes/)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
environment:
|
||||
CIRCLE_TOKEN:
|
||||
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
|
||||
TRAVIS_TOKEN:
|
||||
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
|
||||
GITHUB_TOKEN:
|
||||
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
|
||||
branches:
|
||||
except:
|
||||
- master
|
||||
install:
|
||||
- ps: Install-Product node 6
|
||||
- npm i
|
||||
build: off
|
||||
test: off
|
||||
build_script:
|
||||
- npm run nexe-build
|
||||
artifacts:
|
||||
- path: .nexe\**\node.exe
|
||||
cache:
|
||||
- node_modules
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
nexe = require('../lib'),
|
||||
_log = require("../lib/log"),
|
||||
cli = require('yargs').
|
||||
usage('Usage: $0 -i [sources] -o [binary]').
|
||||
options('i', {
|
||||
demand: true,
|
||||
alias: 'input',
|
||||
desc: 'The entry javascript files',
|
||||
default: process.cwd()
|
||||
}).
|
||||
options('o', {
|
||||
demand: true,
|
||||
alias: 'output',
|
||||
desc: 'The output binary',
|
||||
default: "out.nex"
|
||||
}).
|
||||
options('r', {
|
||||
alias: 'runtime',
|
||||
default: 'latest',
|
||||
description: 'The node.js runtime to use'
|
||||
}).
|
||||
options('t', {
|
||||
alias: 'temp',
|
||||
default: './tmp/nexe',
|
||||
description: 'The path to store node.js sources'
|
||||
}).
|
||||
options('f', {
|
||||
alias: 'flags',
|
||||
description: 'Don\'t parse node and v8 flags, pass through app flags',
|
||||
default: false
|
||||
}).
|
||||
options('v', {
|
||||
alias: 'version',
|
||||
description: 'Display version number'
|
||||
}).
|
||||
options('p', {
|
||||
alias: 'python',
|
||||
description: 'Set path of python to use.',
|
||||
default: 'python'
|
||||
}).
|
||||
options('F', {
|
||||
alias: 'framework',
|
||||
description: 'Set the framework to use',
|
||||
default: 'nodejs'
|
||||
});
|
||||
|
||||
var argv = cli.argv;
|
||||
if(argv.h || argv.help) {
|
||||
cli.showHelp();
|
||||
process.exit();
|
||||
} else if (argv.v || argv.version) {
|
||||
var pkginfo = require('../package.json');
|
||||
console.log(pkginfo.version);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Support network shares?
|
||||
**/
|
||||
function toAbsolute(pt) {
|
||||
if(pt.substr(0, 1) == "/") return pt;
|
||||
if(pt.substr(1, 3) == ":/") return pt; // for windows "c:/"
|
||||
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) {
|
||||
console.log(error.message);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* Call the core
|
||||
**/
|
||||
nexe.compile({
|
||||
input : require.resolve(toAbsolute(argv.i)),
|
||||
output : toAbsolute(argv.o),
|
||||
flags : argv.f,
|
||||
nodeVersion : argv.r,
|
||||
python : argv.python,
|
||||
nodeTempDir : toAbsolute(argv.t),
|
||||
framework : argv.F
|
||||
},
|
||||
function(error) {
|
||||
if(error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
test:
|
||||
override:
|
||||
- npm run nexe-build
|
||||
@@ -0,0 +1,22 @@
|
||||
const { FuseBox } = require('fuse-box')
|
||||
const { NativeModulePlugin } = require('../../lib/bundling')
|
||||
const fuse = FuseBox.init({
|
||||
homeDir: './',
|
||||
cache: false,
|
||||
log: true,
|
||||
debug: true,
|
||||
output: '$name.js',
|
||||
plugins: [
|
||||
new NativeModulePlugin({
|
||||
zmq: {
|
||||
additionalFiles: [
|
||||
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
fuse.bundle('app')
|
||||
.target('server')
|
||||
.instructions(`> index.js`)
|
||||
fuse.run()
|
||||
@@ -0,0 +1,19 @@
|
||||
var zmq = require('zmq')
|
||||
var pub = zmq.socket('pub')
|
||||
|
||||
pub.bindSync('tcp://127.0.0.1:3000')
|
||||
console.log('Publisher bound to port 3000')
|
||||
|
||||
setInterval(function(){
|
||||
console.log('sending a multipart message envelope')
|
||||
pub.send(['kitty cats', 'meow!'])
|
||||
}, 2500)
|
||||
console.log('global', global.require.main)
|
||||
var sub = zmq.socket('sub')
|
||||
sub.connect('tcp://127.0.0.1:3000')
|
||||
sub.subscribe('kitty cats')
|
||||
console.log('Subscriber connected to port 3000')
|
||||
|
||||
sub.on('message', function(topic, message) {
|
||||
console.log('received a message related to:', topic.toString(), 'containing message:', message.toString())
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "native-build",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"bundle": "node build",
|
||||
"build-windows": "nexe -i app.js -o app.exe"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"zmq": "^2.15.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fuse-box": "^2.2.1-beta.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const nexe = require('./lib/nexe')
|
||||
|
||||
module.exports = nexe
|
||||
|
||||
if (require.main === module) {
|
||||
nexe.compile(nexe.argv).catch((e) => {
|
||||
process.stderr.write(e.stack, () => process.exit(1))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
This is a
|
||||
- [ ] Bug Report
|
||||
- [ ] Feature Request
|
||||
- [ ] Other
|
||||
|
||||
<br/>
|
||||
If this is a bug report, What are the steps to reproduce?
|
||||
|
||||
```
|
||||
//provide configuration here
|
||||
```
|
||||
|
||||
<br/><br/>
|
||||
Please also provide:
|
||||
|
||||
- Platform(OS/Version):
|
||||
- Host Node Version:
|
||||
- Target Node Version:
|
||||
- Nexe version:
|
||||
- Python Version:
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013 Craig Condon
|
||||
* Copyright (c) 2015-2016 Jared Allard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
**/
|
||||
|
||||
var mdeps = require("module-deps"),
|
||||
path = require("path"),
|
||||
spawn = require('child_process').spawn,
|
||||
fs = require("fs"),
|
||||
through = require("through2"),
|
||||
async = require("async"),
|
||||
browserify = require('browserify'),
|
||||
builtins = require("builtins"),
|
||||
_log = require("./log");
|
||||
|
||||
/**
|
||||
* User browserify to create a "packed" file.
|
||||
*
|
||||
* @param {String} input - input file
|
||||
* @param {String} nc - node compiler dir
|
||||
* @param {Function} complete - next function to call (async)
|
||||
**/
|
||||
function bundle (input, nc, complete) {
|
||||
var bundlePath = path.join(nc, "lib", "nexe.js");
|
||||
var ws = fs.createWriteStream(bundlePath);
|
||||
var proc = spawn('node', [path.join(__dirname, '../', 'node_modules/browserify/bin/cmd.js'), '--node', input ]);
|
||||
|
||||
// TODO: Get path of nexe directory, and refer to it.
|
||||
|
||||
proc.stdout.pipe(ws);
|
||||
|
||||
proc.on('error', function(err) {
|
||||
console.error(err.toString('ascii'));
|
||||
_log('error', 'Failed to invoke browserify');
|
||||
process.exit(1);
|
||||
})
|
||||
|
||||
proc.stderr.on('data', function(data) {
|
||||
console.error(data.toString('ascii'));
|
||||
_log('error', 'Browserify failed to launch');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
proc.on('close', function(code) {
|
||||
_log("Browserify finished");
|
||||
})
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013 Craig Condon
|
||||
* Copyright (c) 2015-2016 Jared Allard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
**/
|
||||
|
||||
var path = require("path"),
|
||||
fs = require("fs");
|
||||
|
||||
/**
|
||||
* TODO: Optimize this code. (more like document it so I understand it better.)
|
||||
**/
|
||||
function embed(resourceFiles, resourceRoot, complete) {
|
||||
function encode(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");
|
||||
}
|
||||
|
||||
var buffer = "var embeddedFiles = {\n";
|
||||
for (var 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);
|
||||
}
|
||||
|
||||
var 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');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = embed;
|
||||
-713
@@ -1,713 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013 Craig Condon
|
||||
* Copyright (c) 2015-2016 Jared Allard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
**/
|
||||
var async = require("async"),
|
||||
outcome = require("outcome"),
|
||||
mkdirp = require("mkdirp"),
|
||||
request = require("request"),
|
||||
path = require("path"),
|
||||
fs = require("fs"),
|
||||
ncp = require("ncp").ncp,
|
||||
ProgressBar = require("progress"),
|
||||
child_process = require("child_process"),
|
||||
glob = require("glob"),
|
||||
bundle = require("./bundle"),
|
||||
embed = require("./embed"),
|
||||
os = require("os"),
|
||||
targz = require('tar.gz'),
|
||||
_log = require("./log"),
|
||||
_monkeypatch = require("./monkeypatch"),
|
||||
spawn = child_process.spawn;
|
||||
|
||||
var isWin = /^win/.test(process.platform);
|
||||
var isPy;
|
||||
var framework;
|
||||
var version;
|
||||
|
||||
var option_list = [
|
||||
"python",
|
||||
"input",
|
||||
"output",
|
||||
"nodeTempDir",
|
||||
"flags",
|
||||
"framework",
|
||||
"nodeVersion"
|
||||
]
|
||||
|
||||
/**
|
||||
* Compiliation process.
|
||||
*/
|
||||
|
||||
exports.compile = function (options, complete) {
|
||||
|
||||
var nodeCompiler, nexeEntryPath;
|
||||
|
||||
async.waterfall([
|
||||
/**
|
||||
*check relevant options
|
||||
*/
|
||||
function checkOpts (next) {
|
||||
/* failsafe */
|
||||
if(options === undefined) {
|
||||
_log("error", "no options given to .compile()");
|
||||
process.exit()
|
||||
}
|
||||
|
||||
option_list.forEach(function(v) {
|
||||
if(options[v] === undefined) {
|
||||
_log("error", "option "+v+" was empty")
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Have we been given a custom flag for python executable?
|
||||
**/
|
||||
if(options.python!=='python' && options.python!==""
|
||||
&& options.python!==undefined) {
|
||||
if(isWin) {
|
||||
isPy=options.python.replace(/\//gm, "\\"); // use windows file paths, batch is sensitive.
|
||||
} else {
|
||||
isPy=options.python;
|
||||
}
|
||||
|
||||
_log("set python as "+isPy);
|
||||
} else {
|
||||
isPy="python";
|
||||
}
|
||||
|
||||
// remove dots
|
||||
options.framework = options.framework.replace(/\./g, "");
|
||||
|
||||
// set outter-scope framework variable.
|
||||
framework = options.framework;
|
||||
_log("framework => "+framework);
|
||||
|
||||
version = options.nodeVersion; // better framework vc
|
||||
|
||||
// check iojs version
|
||||
if (framework === "iojs" && version === "latest" ) {
|
||||
_log("fetching iojs versions");
|
||||
mkdirp(options.nodeTempDir); // make temp dir, probably repetive.
|
||||
|
||||
// create write stream so we have control over events
|
||||
var output = fs.createWriteStream(path.join(options.nodeTempDir,
|
||||
"iojs-versions.json"));
|
||||
|
||||
request.get("https://iojs.org/dist/index.json")
|
||||
.pipe(output);
|
||||
|
||||
output.on('close', function() {
|
||||
_log("done");
|
||||
var f = fs.readFileSync(path.join(options.nodeTempDir,
|
||||
"iojs-versions.json"));
|
||||
f = JSON.parse(f);
|
||||
version = f[0].version.replace("v", "");
|
||||
|
||||
_log("iojs latest => "+version);
|
||||
|
||||
// continue down along the async road
|
||||
next();
|
||||
})
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* first download node
|
||||
*/
|
||||
function downloadNode (next) {
|
||||
_downloadNode(version, options.nodeTempDir, nodeConfigureArgs, nodeMakeArgs, next);
|
||||
},
|
||||
|
||||
/**
|
||||
* bundle the application into one script
|
||||
*/
|
||||
|
||||
function combineProject (nc, next) {
|
||||
nodeCompiler = nc;
|
||||
_log("bundle %s", options.input);
|
||||
bundle(options.input, nodeCompiler.dir, next);
|
||||
},
|
||||
|
||||
function embedResources(next) {
|
||||
_log("embedResources %s", options.resourceFiles);
|
||||
embed(options.resourceFiles, options.resourceRoot, next);
|
||||
},
|
||||
|
||||
function writeResources (resources, next) {
|
||||
resourcePath = path.join(nodeCompiler.dir, "lib", "nexeres.js");
|
||||
_log("resource -> %s", resourcePath);
|
||||
|
||||
fs.writeFile(resourcePath, resources, next);
|
||||
},
|
||||
|
||||
/**
|
||||
* monkeypatch some files so that the nexe.js file is loaded when the app runs
|
||||
*/
|
||||
|
||||
function monkeyPatchNodeConfig (next) {
|
||||
_monkeyPatchNodeConfig(nodeCompiler, next);
|
||||
},
|
||||
|
||||
/**
|
||||
* monkeypatch node.cc to prevent v8 and node from processing CLI flags
|
||||
*/
|
||||
function monkeyPatchNodeCc (next) {
|
||||
if (options.flags) {
|
||||
_monkeyPatchMainCc(nodeCompiler, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* monkeypatch child_process.js so nexejs knows when it is a forked process
|
||||
*/
|
||||
function monkeyPatchChildProc(next) {
|
||||
_monkeyPatchChildProcess(nodeCompiler, next);
|
||||
},
|
||||
|
||||
/**
|
||||
* If an old compiled executable exists in the Release directory, delete it.
|
||||
* This lets us see if the build failed by checking the existence of this file later.
|
||||
*/
|
||||
|
||||
function cleanUpOldExecutable (next) {
|
||||
fs.unlink(nodeCompiler.releasePath, function (err) {
|
||||
if (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
next();
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* compile the node application
|
||||
*/
|
||||
|
||||
function makeExecutable (next) {
|
||||
if(isWin) {
|
||||
_log("vcbuild [make stage]");
|
||||
} else {
|
||||
_log("make");
|
||||
}
|
||||
nodeCompiler.make(next);
|
||||
},
|
||||
|
||||
/**
|
||||
* we create the output directory if needed
|
||||
*/
|
||||
|
||||
function makeOutputDirectory (next) {
|
||||
mkdirp(path.dirname(options.output), function(){ next(); });
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify that the executable was compiled successfully
|
||||
*/
|
||||
|
||||
function checkThatExecutableExists (next) {
|
||||
fs.exists(nodeCompiler.releasePath, function (exists) {
|
||||
if (!exists) {
|
||||
_log("error",
|
||||
"The release executable has not been generated. " +
|
||||
"This indicates a failure in the build process. " +
|
||||
"There is likely additional information above."
|
||||
);
|
||||
throw new Error("The release executable was not built ('" + nodeCompiler.releasePath + "')");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the compilied binary to the output specified.
|
||||
*/
|
||||
|
||||
function copyBinaryToOutput (next) {
|
||||
_log("cp %s %s", nodeCompiler.releasePath, options.output);
|
||||
ncp(nodeCompiler.releasePath, options.output, function (err) {
|
||||
if (err) {
|
||||
_log("error", "Couldn't copy binary.");
|
||||
throw err; // dump raw error object
|
||||
}
|
||||
_log('copied');
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
], complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a version of node
|
||||
*
|
||||
* @param {string} version, version of node to download
|
||||
* @param {string} directory, where to store the downloaded src
|
||||
* @param {function} complete, callback
|
||||
*/
|
||||
|
||||
function _downloadNode(version, directory, nodeConfigureArgs, nodeMakeArgs, complete) {
|
||||
var nodeFileDir = path.resolve(path.join(directory, framework, version)), // fixes #107, was process.cwd(), + rest.
|
||||
nodeFilePath = path.resolve(path.join(nodeFileDir, framework + "-" + version + ".tar.gz"));
|
||||
|
||||
|
||||
// might already be downloaded, and unzipped
|
||||
if (_getNodeCompiler(nodeFileDir, nodeConfigureArgs, nodeMakeArgs, complete)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
async.waterfall([
|
||||
|
||||
/**
|
||||
* first make the directory where the zip file will live
|
||||
*/
|
||||
|
||||
function makeDirectory (next) {
|
||||
mkdirp.sync(path.dirname(nodeFilePath));
|
||||
next();
|
||||
},
|
||||
|
||||
/**
|
||||
* download node into the target directory
|
||||
*/
|
||||
|
||||
function downloadNode (next) {
|
||||
if (fs.existsSync(nodeFilePath)) return next();
|
||||
|
||||
var uri = framework;
|
||||
|
||||
if(framework==="node") {
|
||||
uri = 'nodejs'; // if node, use nodejs uri
|
||||
} else if(framework === 'nodejs') {
|
||||
framework = 'node'; // support nodejs, and node, as framework.
|
||||
}
|
||||
|
||||
var type = global.type;
|
||||
var url, prefix = "https://"+uri+".org/dist";
|
||||
|
||||
if (version === "latest") {
|
||||
url = prefix + "/" + framework + "-" + version + ".tar.gz";
|
||||
} else {
|
||||
url = prefix + "/v" + version + "/"+framework+"-v" + version + ".tar.gz";
|
||||
}
|
||||
|
||||
_log("downloading %s", url);
|
||||
|
||||
var output = fs.createWriteStream(nodeFilePath, { "flags": "w+" });
|
||||
|
||||
// need to set user-agent to bypass some corporate firewalls
|
||||
var requestOptions = {
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": "Node.js"
|
||||
}
|
||||
}
|
||||
|
||||
_logProgress(request(requestOptions)).pipe(output);
|
||||
|
||||
output.on("close", function () { next(); });
|
||||
},
|
||||
|
||||
/**
|
||||
* unzip in the same directory
|
||||
*/
|
||||
|
||||
function unzipNodeTarball (next) {
|
||||
if(isWin) {
|
||||
_log("Extracting the .tar.gz.");
|
||||
new targz().extract(nodeFilePath, nodeFileDir, next);
|
||||
} else {
|
||||
var cmd = ["tar", "-xf", nodeFilePath, "-C", nodeFileDir];
|
||||
_log(cmd.join(" "));
|
||||
|
||||
var tar = spawn(cmd.shift(), cmd);
|
||||
tar.stdout.pipe(process.stdout);
|
||||
tar.stderr.pipe(process.stderr);
|
||||
|
||||
tar.on("close", function () { next(); })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* return the compiler object for the node version
|
||||
*/
|
||||
|
||||
function (next, type) {
|
||||
_getNodeCompiler(nodeFileDir, nodeConfigureArgs, nodeMakeArgs, next, type)
|
||||
},
|
||||
|
||||
], complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compilier we will use for whatever platform we may be on and configure
|
||||
* it.
|
||||
*/
|
||||
|
||||
function _getNodeCompiler (nodeFileDir, nodeConfigureArgs, nodeMakeArgs, complete, type) {
|
||||
var dir = _getFirstDirectory(nodeFileDir);
|
||||
|
||||
// standard
|
||||
var executable = "node.exe";
|
||||
var binary = "node";
|
||||
|
||||
// iojs specifics.
|
||||
if(framework === "iojs") {
|
||||
executable = "iojs.exe";
|
||||
binary = "iojs";
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
if(isWin) {
|
||||
complete(null, {
|
||||
dir: dir,
|
||||
version: path.basename(nodeFileDir),
|
||||
releasePath: path.join(dir, "Release", executable),
|
||||
make: function(next) {
|
||||
// create a new env with minimal impact on old one
|
||||
var newEnv = process.env
|
||||
|
||||
if(isPy!=="python") {
|
||||
// add the dir of the suposed python exe to path
|
||||
newEnv.path = process.env.PATH+";"+path.dirname(isPy)
|
||||
}
|
||||
|
||||
// spawn a vcbuild process with our custom enviroment.
|
||||
var vcbuild = spawn("vcbuild.bat", ["nosign", "release"], {
|
||||
cwd: dir,
|
||||
env: newEnv
|
||||
});
|
||||
vcbuild.stdout.pipe(process.stdout);
|
||||
vcbuild.stderr.pipe(process.stderr);
|
||||
vcbuild.on("close", function() {
|
||||
next();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
complete(null, {
|
||||
dir: dir,
|
||||
version: path.basename(nodeFileDir),
|
||||
releasePath: path.join(dir, "out", "Release", binary),
|
||||
make: function (next) {
|
||||
var cfg = "./configure", configure;
|
||||
if(isPy !== "python") {
|
||||
configure = spawn(isPy, [cfg].concat(nodeConfigureArgs), { cwd: dir });
|
||||
} else {
|
||||
configure = spawn(cfg, nodeConfigureArgs, { cwd: dir });
|
||||
}
|
||||
|
||||
// local function, move to top eventually
|
||||
function _loop(dir) {
|
||||
/* eventually try every python file */
|
||||
var pdir = fs.readdirSync(dir);
|
||||
|
||||
pdir.forEach(function(v, i) {
|
||||
var stat = fs.statSync(dir+"/"+v);
|
||||
if(stat.isFile()) {
|
||||
// only process Makefiles and .mk targets.
|
||||
if(v !== "Makefile" && path.extname(v) !== ".mk") {
|
||||
return;
|
||||
}
|
||||
|
||||
_log("patching "+v);
|
||||
|
||||
/* patch the file */
|
||||
var py = fs.readFileSync(dir+"/"+v, {encoding: 'utf8'});
|
||||
py = py.replace(/([a-z]|\/)*python(\w|)/gm, isPy); // this is definently needed
|
||||
fs.writeFileSync(dir+"/"+v, py, {encoding: 'utf8'}); // write to file
|
||||
|
||||
delete pv;
|
||||
} else if(stat.isDirectory()) {
|
||||
// must be dir?
|
||||
// skip tests because we don't need them here
|
||||
if(v !== "test") {
|
||||
_loop(dir+"/"+v)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
configure.stdout.pipe(process.stdout);
|
||||
configure.stderr.pipe(process.stderr);
|
||||
configure.on("close", function () {
|
||||
if(isPy !== "python") {
|
||||
/**
|
||||
* Originally I thought this only applied to io.js,
|
||||
* however I soon found out this affects node.js,
|
||||
* so it is now mainstream.
|
||||
*/
|
||||
_log("preparing python");
|
||||
|
||||
// loop over depends
|
||||
_loop(dir);
|
||||
}
|
||||
var platformMake = "make";
|
||||
if (os.platform().match(/bsd$/) != null) {
|
||||
platformMake = "gmake";
|
||||
}
|
||||
var make = spawn(platformMake, nodeMakeArgs, { cwd: dir });
|
||||
make.stdout.pipe(process.stdout);
|
||||
make.stderr.pipe(process.stderr);
|
||||
make.on("close", function () {
|
||||
next();
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
function _monkeyPatchNodeConfig (compiler, complete) {
|
||||
async.waterfall([
|
||||
/**
|
||||
* monkeypatch the gyp file to include the nexe.js and nexeres.js files
|
||||
*/
|
||||
function (next) {
|
||||
_monkeyPatchGyp(compiler, next)
|
||||
},
|
||||
|
||||
/**
|
||||
* monkeypatch main entry point
|
||||
*/
|
||||
function (next) {
|
||||
_monkeyPatchMainJs(compiler, next)
|
||||
}
|
||||
], complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* patch the gyp file to allow our custom includes
|
||||
*/
|
||||
|
||||
function _monkeyPatchGyp (compiler, complete) {
|
||||
|
||||
var gypPath = path.join(compiler.dir, "node.gyp");
|
||||
|
||||
_monkeypatch(
|
||||
gypPath,
|
||||
function (content) {
|
||||
return ~content.indexOf("nexe.js");
|
||||
},
|
||||
function (content, next) {
|
||||
next(null, content.replace("'lib/fs.js',", "'lib/fs.js', 'lib/nexe.js', 'lib/nexeres.js', "))
|
||||
},
|
||||
complete
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
function _monkeyPatchMainJs (compiler, complete) {
|
||||
var mainPath = path.join(compiler.dir, "src", "node.js");
|
||||
|
||||
_monkeypatch(
|
||||
mainPath,
|
||||
function (content) {
|
||||
return ~content.indexOf("nexe");
|
||||
},
|
||||
function (content, next) {
|
||||
next(null, content.replace(/\(function\(process\) \{/,'\
|
||||
(function(process) {\n\
|
||||
process._eval = \'require("nexe");\';\n\
|
||||
if (process.argv[1] !== "nexe.js") {\n\
|
||||
process.argv.splice(1, 0, "nexe.js");\n\
|
||||
}\n\
|
||||
'))
|
||||
},
|
||||
complete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
function _monkeyPatchChildProcess(compiler, complete) {
|
||||
var childProcPath = path.join(compiler.dir, "lib", "child_process.js");
|
||||
|
||||
_monkeypatch(
|
||||
childProcPath,
|
||||
function (content) {
|
||||
return ~content.indexOf("--child_process");
|
||||
},
|
||||
function (content, next) {
|
||||
next(null, content.replace(/return spawn\(/, 'args.unshift("nexe.js", "--child_process");\n return spawn('));
|
||||
},
|
||||
complete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch node.cc to not check the internal arguments.
|
||||
*/
|
||||
|
||||
function _monkeyPatchMainCc(compiler, complete) {
|
||||
var mainPath = path.join(compiler.dir, "src", "node.cc");
|
||||
_monkeypatch(
|
||||
mainPath,
|
||||
function (content) {
|
||||
return ~content.indexOf('// // TODO use parse opts');
|
||||
},
|
||||
function (content, next) {
|
||||
var lines = content.split('\n');
|
||||
var endLine = lines.indexOf(' option_end_index = i;'); // pre node 0.11.6 compat
|
||||
|
||||
if(endLine !== -1) { // check if it succedded or not.
|
||||
lines[endLine] = ' option_end_index = 1;';
|
||||
_log("patched node.cc")
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the new method of passing the args. Tested on node.js 0.12.5
|
||||
* and iojs 2.3.1
|
||||
**/
|
||||
if(endLine === -1) { // only if the pre-0.12.5 failed.
|
||||
var startLine = lines.indexOf(' while (index < nargs && argv[index][0] == \'-\') {'); // beginning of the function
|
||||
endLine = lines.indexOf(' // Copy remaining arguments.');
|
||||
endLine = endLine-1; // space, then it's at the }
|
||||
|
||||
// remove the offending lines
|
||||
if(startLine !== -1) {
|
||||
for (var i = startLine; i < endLine; i++) {
|
||||
lines[i] = undefined; // set the value to undefined so it's skipped by the join
|
||||
}
|
||||
_log('patched node.cc');
|
||||
}
|
||||
}
|
||||
|
||||
lines = lines.join('\n');
|
||||
next(null, lines);
|
||||
},
|
||||
complete
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
function _getFirstDirectory (dir) {
|
||||
var files = glob.sync(dir + "/*");
|
||||
|
||||
for (var i = files.length; i--;) {
|
||||
var file = files[i];
|
||||
if (fs.statSync(file).isDirectory()) return file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
function _logProgress (req) {
|
||||
|
||||
req.on("response", function (resp) {
|
||||
|
||||
var len = parseInt(resp.headers["content-length"], 10),
|
||||
bar = new ProgressBar("[:bar]", {
|
||||
complete: "=",
|
||||
incomplete: " ",
|
||||
total: len,
|
||||
width: ((isWin) ? 100 : process.stdout.columns - 2) // windows doesn't output colums correctly
|
||||
});
|
||||
|
||||
req.on("data", function (chunk) {
|
||||
bar.tick(chunk.length);
|
||||
});
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse the package.json for nexe information.
|
||||
*
|
||||
* @param {string} path - path to package.json
|
||||
* @param {object} options - fallback options
|
||||
*
|
||||
* @todo implement options overriding package defaults.
|
||||
*
|
||||
* @return {object} nexe.compile options object
|
||||
**/
|
||||
exports.package = function(path, options) {
|
||||
var _package; // scope
|
||||
|
||||
// check if the file exists
|
||||
if(fs.existsSync(path)===false) {
|
||||
_log("warn", "no package.json found.");
|
||||
} else {
|
||||
_package = require(path);
|
||||
}
|
||||
|
||||
// replace ^$ w/ os specific extension on output
|
||||
if(isWin) {
|
||||
_package.nexe.output = _package.nexe.output.replace(/\^\$/, '.exe') // exe
|
||||
} else {
|
||||
_package.nexe.output = _package.nexe.output.replace(/\^\$/, '') // none
|
||||
}
|
||||
|
||||
// construct the object
|
||||
var obj = {
|
||||
input: (_package.nexe.input || options.i),
|
||||
output: (_package.nexe.output || options.o),
|
||||
flags: (_package.nexe.runtime.ignoreFlags || options.f),
|
||||
nodeConfigureArgs: (_package.nexe.runtime.nodeConfigureArgs || []),
|
||||
nodeMakeArgs: (_package.nexe.runtime.nodeMakeArgs || []),
|
||||
resourceFiles: (_package.nexe.resourceFiles),
|
||||
nodeVersion: (_package.nexe.runtime.version || options.r),
|
||||
nodeConfigureArgs: (_package.nexe.runtime.nodeConfigureArgs || []),
|
||||
nodeMakeArgs: (_package.nexe.runtime.nodeMakeArgs || []),
|
||||
python: (_package.nexe.python || options.p),
|
||||
nodeTempDir: (_package.nexe.temp || options.t),
|
||||
framework: (_package.nexe.runtime.framework || options.f)
|
||||
}
|
||||
|
||||
Object.keys(_package.nexe).forEach(function(v,i) {
|
||||
if(v!=="runtime") {
|
||||
_log("log", v+" => '"+_package.nexe[v]+"'");
|
||||
}
|
||||
});
|
||||
Object.keys(_package.nexe.runtime).forEach(function(v,i) {
|
||||
_log("log", "runtime."+v+" => '"+_package.nexe.runtime[v]+"'");
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("./exe");
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013 Craig Condon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
**/
|
||||
|
||||
/**
|
||||
* standard output, takes 3 different types.
|
||||
* log, error, and warn
|
||||
*
|
||||
* @param {any} arguments - Text to output.
|
||||
* @return undefined
|
||||
**/
|
||||
function _log () {
|
||||
|
||||
var colors = require('colors');
|
||||
|
||||
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;
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013 Craig Condon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
**/
|
||||
|
||||
var async = require("async"),
|
||||
fs = require("fs"),
|
||||
_log = require("./log");
|
||||
|
||||
/**
|
||||
* Monkey patch a file.
|
||||
*
|
||||
* @param {string} filePath - path to file.
|
||||
* @param {function} monkeyPatched - function to process contents
|
||||
* @param {function} processor - TODO: detail what this is
|
||||
* @param {function} complete - callback
|
||||
*
|
||||
* @return undefined
|
||||
*/
|
||||
function _monkeypatch (filePath, monkeyPatched, processor, complete) {
|
||||
|
||||
async.waterfall([
|
||||
|
||||
function read (next) {
|
||||
fs.readFile(filePath, "utf8", next);
|
||||
},
|
||||
|
||||
// TODO - need to parse gyp file - this is a bit hacker
|
||||
function monkeypatch (content, next) {
|
||||
|
||||
if (monkeyPatched(content)) return complete();
|
||||
|
||||
_log("monkey patch %s", filePath);
|
||||
processor(content, next);
|
||||
},
|
||||
|
||||
function write (content, next) {
|
||||
fs.writeFile(filePath, content, "utf8", next);
|
||||
}
|
||||
], complete);
|
||||
}
|
||||
|
||||
// export the function for require()
|
||||
module.exports = _monkeypatch;
|
||||
Generated
+3830
File diff suppressed because it is too large
Load Diff
+50
-42
@@ -1,55 +1,63 @@
|
||||
{
|
||||
"author": "Jared Allard <jaredallard@outlook.com>",
|
||||
"name": "nexe",
|
||||
"description": "create single executables out of your [node/io].js applications",
|
||||
"description": "Create a single executable out of your Node.js application",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0-rc.1",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Criag Condon",
|
||||
"email": "craig.j.condon@gmail.com",
|
||||
"url": "http://crcn.io/"
|
||||
}
|
||||
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
||||
"Jared Allard <jaredallard@outlook.com>",
|
||||
"Caleb Boyd <caleb.boyd@hotmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"nexe-build": "ts-node tasks/build",
|
||||
"prebuild": "rimraf lib && npm run lint",
|
||||
"prepublish": "npm run build",
|
||||
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
|
||||
"build": "tsc --declaration"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jaredallard/nexe.git"
|
||||
"url": "git://github.com/nexe/nexe.git"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"dependencies": {
|
||||
"async": "1.5.2",
|
||||
"browserify": "^13.0.0",
|
||||
"builtins": "1.0.3",
|
||||
"colors": "^1.1.2",
|
||||
"glob": "^6.0.4",
|
||||
"insert-module-globals": "^7.0.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"module-deps": "^4.0.5",
|
||||
"ncp": "^2.0.0",
|
||||
"outcome": "0.0.18",
|
||||
"progress": "^1.1.8",
|
||||
"request": "^2.67.0",
|
||||
"sprintf": "~0.1.5",
|
||||
"step": "0.0.x",
|
||||
"tar.gz": "1.0.2",
|
||||
"through": "^2.3.8",
|
||||
"yargs": "^3.32.0"
|
||||
},
|
||||
"nexe": {
|
||||
"input": "./bin/nexe",
|
||||
"output": "nexe^$",
|
||||
"temp": "src",
|
||||
"runtime": {
|
||||
"framework": "nodejs",
|
||||
"version": "5.5.0",
|
||||
"ignoreFlags": true
|
||||
}
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"files": [
|
||||
"lib",
|
||||
"source-map-support.js"
|
||||
],
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"nexe": "./bin/nexe"
|
||||
"nexe": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"app-builder": "^5.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
"download": "^6.2.0",
|
||||
"fuse-box": "^2.2.1",
|
||||
"globby": "^6.1.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"ora": "^1.2.0",
|
||||
"pifi": "1.0.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"uglify-js": "3.0.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodeunit": "^0.9.1"
|
||||
"@types/bluebird": "^3.5.8",
|
||||
"@types/chalk": "^0.4.31",
|
||||
"@types/execa": "0.7.0",
|
||||
"@types/globby": "^0.6.0",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/mkdirp": "^0.3.29",
|
||||
"@types/ora": "^0.3.31",
|
||||
"@types/pify": "0.0.28",
|
||||
"@types/rimraf": "0.0.28",
|
||||
"execa": "0.8.0",
|
||||
"got": "^7.1.0",
|
||||
"mocha": "^3.2.0",
|
||||
"prettier": "^1.6.1",
|
||||
"ts-node": "3.3.0",
|
||||
"typescript": "^2.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,162 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as child from 'child_process'
|
||||
import { createHash } from 'crypto'
|
||||
|
||||
export interface ExtractNodeModuleOptions {
|
||||
[key: string]: { additionalFiles: string[] } | true
|
||||
}
|
||||
|
||||
function hashName(name: string | Buffer) {
|
||||
return createHash('md5')
|
||||
.update(name)
|
||||
.digest('hex')
|
||||
.toString()
|
||||
.slice(0, 8)
|
||||
}
|
||||
|
||||
export function embedDotNode(
|
||||
options: ExtractNodeModuleOptions,
|
||||
file: { contents: string; absPath: string }
|
||||
) {
|
||||
const contents = fs.readFileSync(file.absPath)
|
||||
const module = Object.keys(options).find(x =>
|
||||
Boolean(~file.absPath.indexOf(path.join('node_modules', x)))
|
||||
)!
|
||||
const bindingName = path.basename(file.absPath)
|
||||
const settings = options[module]
|
||||
const moduleDir = hashName(contents)
|
||||
file.contents = `
|
||||
var fs=require('fs');var path=require('path');var binding='${contents.toString(
|
||||
'base64'
|
||||
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
|
||||
|
||||
if (settings === true) {
|
||||
file.contents += `
|
||||
mkdirp('${moduleDir}');
|
||||
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
|
||||
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
|
||||
process.dlopen(module, bindingPath)
|
||||
`.trim()
|
||||
return
|
||||
}
|
||||
|
||||
let depth = 0
|
||||
settings.additionalFiles.forEach(file => {
|
||||
let ownDepth = 0
|
||||
file.split('/').forEach(x => x === '..' && ownDepth++)
|
||||
depth = ownDepth > depth ? ownDepth : depth
|
||||
})
|
||||
let segments = [moduleDir]
|
||||
while (depth--) {
|
||||
segments.push(hashName(moduleDir + depth))
|
||||
}
|
||||
segments.push(bindingName)
|
||||
|
||||
file.contents += `
|
||||
var cwd = process.cwd()
|
||||
var bindingFileParts = ${JSON.stringify(segments)};
|
||||
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
|
||||
mkdirp(path.dirname(bindingFile));
|
||||
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
|
||||
${settings.additionalFiles.reduce((code, filename, i) => {
|
||||
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
|
||||
return (code += `
|
||||
var file${i} = '${contents.toString('base64')}';
|
||||
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
|
||||
mkdirp(path.dirname(filePath${i}));
|
||||
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
|
||||
`)
|
||||
}, '')};
|
||||
process.dlopen(module, bindingFile)
|
||||
`
|
||||
}
|
||||
|
||||
function findNativeModulePath(filePath: string, bindingsArg: string) {
|
||||
const dirname = path.dirname(filePath)
|
||||
const tempFile = Math.random() * 100 + '.js'
|
||||
const tempFilePath = path.join(dirname, tempFile)
|
||||
fs.writeFileSync(
|
||||
tempFilePath,
|
||||
`
|
||||
var bindings = require('bindings');
|
||||
var Module = require('module');
|
||||
var originalRequire = Module.prototype.require;
|
||||
Module.prototype.require = function(path) {
|
||||
const mod = originalRequire.apply(this, arguments);
|
||||
process.stdout.write(path)
|
||||
return mod
|
||||
};
|
||||
bindings('${bindingsArg}')
|
||||
`
|
||||
)
|
||||
//using exec because it can be done sync
|
||||
const nativeFileName = child.execSync('node ' + tempFile, { cwd: dirname }).toString()
|
||||
fs.unlinkSync(tempFilePath)
|
||||
const relativePath = './' + path.relative(dirname, nativeFileName).replace(/\\/g, '/')
|
||||
return relativePath
|
||||
}
|
||||
/**
|
||||
* Traverse all nodes in a file and evaluate usages of the bindings module
|
||||
* handles two common cases
|
||||
*/
|
||||
export class BindingsRewrite {
|
||||
private bindingsIdNodes: any[] = []
|
||||
public nativeModulePaths: string[] = []
|
||||
public rewrite = false
|
||||
|
||||
isRequireBindings(node: any) {
|
||||
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
|
||||
}
|
||||
|
||||
onNode(absolutePath: string, node: any, parent: any) {
|
||||
if (node.type === 'CallExpression') {
|
||||
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
|
||||
/**
|
||||
* const loadBindings = require('bindings');
|
||||
* -> const loadBindings = String('');
|
||||
*/
|
||||
this.bindingsIdNodes.push(parent.id)
|
||||
node.callee.name = 'String'
|
||||
node.arguments[0].value = ''
|
||||
this.rewrite = true
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
|
||||
/**
|
||||
*const bindings = require('bindings')('native-module')....
|
||||
* -> const bindings = require('./path/to/native/module.node').....
|
||||
*/
|
||||
const bindingsArgNode = parent.arguments[0]
|
||||
if (bindingsArgNode.type === 'Literal') {
|
||||
parent.callee = { type: 'Identifier', name: 'require' }
|
||||
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
|
||||
this.nativeModulePaths.push(bindingsArgNode.value)
|
||||
this.rewrite = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const bindingsInvocationIdx = this.bindingsIdNodes.findIndex(x => node.callee.name === x.name)
|
||||
|
||||
if (this.bindingsIdNodes[bindingsInvocationIdx]) {
|
||||
/**
|
||||
* const bindings = loadBindings('native-module')
|
||||
* -> const bindings = require('./path/to/native/module.node')
|
||||
*/
|
||||
const bindingsIdNode = this.bindingsIdNodes[bindingsInvocationIdx]
|
||||
const bindingsArgNode = node.arguments[0]
|
||||
this.bindingsIdNodes.splice(bindingsInvocationIdx, 1)
|
||||
|
||||
if (bindingsArgNode.type === 'Literal') {
|
||||
node.callee.name = 'require'
|
||||
bindingsArgNode.value = findNativeModulePath(absolutePath, bindingsArgNode.value)
|
||||
this.nativeModulePaths.push(bindingsArgNode.value)
|
||||
this.rewrite = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
|
||||
|
||||
export interface FuseBoxFile {
|
||||
info: { absPath: string }
|
||||
absPath: string
|
||||
contents: string
|
||||
analysis: {
|
||||
dependencies: string[]
|
||||
requiresRegeneration: boolean
|
||||
}
|
||||
loadContents(): void
|
||||
makeAnalysis(
|
||||
parsingOptions?: any,
|
||||
traversalPlugin?: {
|
||||
plugins: Array<{
|
||||
onNode(file: FuseBoxFile, node: any, parent: any): void
|
||||
onEnd(file: FuseBoxFile): void
|
||||
}>
|
||||
}
|
||||
): void
|
||||
}
|
||||
|
||||
export default function(options: ExtractNodeModuleOptions) {
|
||||
return new NativeModulePlugin(options)
|
||||
}
|
||||
|
||||
export class NativeModulePlugin {
|
||||
public test: RegExp
|
||||
public limit2Project = false
|
||||
private modules: (keyof ExtractNodeModuleOptions)[]
|
||||
|
||||
constructor(public options: ExtractNodeModuleOptions) {
|
||||
this.options = options
|
||||
this.test = new RegExp(`node_modules\/(${Object.keys(options).join('|')}).*\.js|\.node$`)
|
||||
}
|
||||
|
||||
init(context: any) {
|
||||
context.allowExtension('.node')
|
||||
}
|
||||
|
||||
transform(file: FuseBoxFile) {
|
||||
file.loadContents()
|
||||
|
||||
if (file.absPath.endsWith('.node')) {
|
||||
embedDotNode(this.options, file)
|
||||
return
|
||||
}
|
||||
|
||||
const bindingsRewrite = new BindingsRewrite()
|
||||
file.makeAnalysis(null, {
|
||||
plugins: [
|
||||
{
|
||||
onNode(file, node, parent) {
|
||||
bindingsRewrite.onNode(file.absPath, node, parent)
|
||||
},
|
||||
onEnd(file) {
|
||||
if (bindingsRewrite.rewrite) {
|
||||
const index = file.analysis.dependencies.indexOf('bindings')
|
||||
if (~index) {
|
||||
file.analysis.dependencies.splice(index, 1)
|
||||
}
|
||||
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
|
||||
file.analysis.requiresRegeneration = true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } from 'fuse-box'
|
||||
import { readFileAsync } from '../util'
|
||||
//import NativeModulePlugin from './fuse-native-module-plugin'
|
||||
|
||||
function createBundle(filename: string, options: { name: string; minify: any; cwd: string }) {
|
||||
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
|
||||
if (options.minify) {
|
||||
plugins.push(
|
||||
QuantumPlugin({
|
||||
target: 'server',
|
||||
uglify: true,
|
||||
bakeApiIntoBundle: options.name
|
||||
})
|
||||
)
|
||||
}
|
||||
const fuse = FuseBox.init({
|
||||
cache: false,
|
||||
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
|
||||
homeDir: options.cwd,
|
||||
sourceMaps: false,
|
||||
writeBundles: false,
|
||||
output: '$name.js',
|
||||
target: 'server',
|
||||
plugins
|
||||
})
|
||||
fuse.bundle(options.name).instructions(`> ${filename}`)
|
||||
return fuse.run().then(x => {
|
||||
let output = ''
|
||||
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
|
||||
return output
|
||||
})
|
||||
}
|
||||
|
||||
export default async function bundle(compiler: NexeCompiler, next: any) {
|
||||
if (!compiler.options.bundle) {
|
||||
compiler.input = await readFileAsync(compiler.options.input, 'utf-8')
|
||||
return next()
|
||||
}
|
||||
|
||||
if (compiler.options.empty || !compiler.options.input) {
|
||||
compiler.input = ''
|
||||
return next()
|
||||
}
|
||||
|
||||
let producer = createBundle
|
||||
if (typeof compiler.options.bundle === 'string') {
|
||||
producer = require(compiler.options.bundle).createBundle
|
||||
}
|
||||
|
||||
compiler.input = await producer(compiler.options.input, {
|
||||
cwd: compiler.options.cwd,
|
||||
name: compiler.options.name,
|
||||
minify: compiler.options.compress
|
||||
})
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './fuse-native-module-plugin'
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import { dirname, normalize, join } from 'path'
|
||||
import { Buffer } from 'buffer'
|
||||
import { createHash } from 'crypto'
|
||||
import { createReadStream } from 'fs'
|
||||
import { Readable } from 'stream'
|
||||
import { spawn } from 'child_process'
|
||||
import { Logger } from './logger'
|
||||
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
|
||||
import { NexeOptions, nexeVersion } from './options'
|
||||
import { NexeTarget } from './target'
|
||||
import { getLatestGitRelease, storeAsset } from './releases'
|
||||
|
||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||
const configure = isWindows ? 'configure' : './configure'
|
||||
|
||||
export interface NexeFile {
|
||||
filename: string
|
||||
absPath: string
|
||||
contents: string
|
||||
}
|
||||
|
||||
interface NexeHeader {
|
||||
resources: { [key: string]: number[] }
|
||||
version: string
|
||||
}
|
||||
|
||||
export class NexeCompiler {
|
||||
private start = Date.now()
|
||||
private env = { ...process.env }
|
||||
private compileStep: { modify: Function; log: Function }
|
||||
public log = new Logger(this.options.loglevel)
|
||||
public src = join(this.options.temp, this.options.version)
|
||||
public files: NexeFile[] = []
|
||||
public input: string
|
||||
public bundledInput?: string
|
||||
public output: string | null
|
||||
|
||||
public resources: { bundle: string; index: { [key: string]: number[] } } = {
|
||||
index: {},
|
||||
bundle: ''
|
||||
}
|
||||
public readFileAsync: (file: string) => Promise<NexeFile>
|
||||
public writeFileAsync: (file: string, contents: Buffer | string) => Promise<void>
|
||||
public replaceInFileAsync: (file: string, replacer: any, replaceValue: string) => Promise<void>
|
||||
public setFileContentsAsync: (file: string, contents: string | Buffer) => Promise<void>
|
||||
|
||||
private nodeSrcBinPath = isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
|
||||
constructor(public options: NexeOptions) {
|
||||
const { python } = (this.options = options)
|
||||
this.log.step('nexe ' + nexeVersion, 'info')
|
||||
if (python) {
|
||||
if (isWindows) {
|
||||
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
|
||||
} else {
|
||||
this.env.PYTHON = python
|
||||
}
|
||||
}
|
||||
|
||||
this.readFileAsync = async (file: string) => {
|
||||
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
|
||||
if (!cachedFile) {
|
||||
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
|
||||
}
|
||||
this.writeFileAsync = (file, contents) => writeFileAsync(join(this.src, file), contents)
|
||||
this.replaceInFileAsync = async (file, replace: string | RegExp, value: string) => {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = entry.contents.replace(replace, value)
|
||||
}
|
||||
this.setFileContentsAsync = async (file: string, contents: string) => {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = contents
|
||||
}
|
||||
}
|
||||
|
||||
quit() {
|
||||
const time = Date.now() - this.start
|
||||
this.log.write(`Finsihed in ${time / 1000}s`)
|
||||
return this.log.flush()
|
||||
}
|
||||
|
||||
private _getNodeExecutableLocation(target?: NexeTarget) {
|
||||
if (target) {
|
||||
return join(this.options.temp, target.toString())
|
||||
}
|
||||
return this.nodeSrcBinPath
|
||||
}
|
||||
|
||||
private _runBuildCommandAsync(command: string, args: string[]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
spawn(command, args, {
|
||||
cwd: this.src,
|
||||
env: this.env,
|
||||
stdio: 'ignore'
|
||||
})
|
||||
.once('error', reject)
|
||||
.once('close', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
private _configureAsync() {
|
||||
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
|
||||
configure,
|
||||
...this.options.configure
|
||||
])
|
||||
}
|
||||
|
||||
private async _buildAsync() {
|
||||
this.compileStep.log(
|
||||
`Configuring node build${this.options.configure.length
|
||||
? ': ' + this.options.configure
|
||||
: '...'}`
|
||||
)
|
||||
await this._configureAsync()
|
||||
const buildOptions = this.options.make
|
||||
this.compileStep.log(
|
||||
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
|
||||
)
|
||||
await this._runBuildCommandAsync(make, buildOptions)
|
||||
return createReadStream(this._getNodeExecutableLocation())
|
||||
}
|
||||
|
||||
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
|
||||
const githubRelease = await getLatestGitRelease()
|
||||
const assetName = target.toString()
|
||||
const asset = githubRelease.assets.find(x => x.name === assetName)
|
||||
|
||||
if (!asset) {
|
||||
throw new Error(`${assetName} not available, create one using --build`)
|
||||
}
|
||||
const filename = this._getNodeExecutableLocation(target)
|
||||
await storeAsset(asset, dirname(filename))
|
||||
return createReadStream(filename)
|
||||
}
|
||||
|
||||
private _generateHeader() {
|
||||
const version =
|
||||
['configure', 'vcBuild', 'make'].reduce((a, c) => {
|
||||
return (a += (this.options as any)[c]
|
||||
.slice()
|
||||
.sort()
|
||||
.join())
|
||||
}, '') + this.options.enableNodeCli
|
||||
const header = {
|
||||
resources: this.resources.index,
|
||||
version: createHash('md5')
|
||||
.update(version)
|
||||
.digest('hex')
|
||||
}
|
||||
const serializedHeader = this._serializeHeader(header)
|
||||
return header
|
||||
}
|
||||
|
||||
private _serializeHeader(header: NexeHeader) {
|
||||
return `process.__nexe=${JSON.stringify(header)};`
|
||||
}
|
||||
|
||||
async compileAsync(target: NexeTarget) {
|
||||
const step = (this.compileStep = this.log.step('Compiling result'))
|
||||
const build = this.options.build
|
||||
const location = this._getNodeExecutableLocation(target)
|
||||
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
|
||||
const header = this._generateHeader()
|
||||
|
||||
if (target && !build) {
|
||||
binary = await this._fetchPrebuiltBinaryAsync(target)
|
||||
}
|
||||
|
||||
if (!binary) {
|
||||
binary = await this._buildAsync()
|
||||
step.log('Node binary compiled')
|
||||
}
|
||||
return this._assembleDeliverable(header, binary)
|
||||
}
|
||||
|
||||
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
|
||||
if (this.options.empty) {
|
||||
return binary
|
||||
}
|
||||
const artifact = new Readable({ read() {} })
|
||||
binary.on('data', (chunk: Buffer) => {
|
||||
artifact.push(chunk)
|
||||
})
|
||||
binary.on('close', () => {
|
||||
const content = this._serializeHeader(header) + this.input
|
||||
artifact.push(content)
|
||||
artifact.push(this.resources.bundle)
|
||||
const lengths = Buffer.from(Array(16))
|
||||
lengths.writeDoubleLE(Buffer.byteLength(content), 0)
|
||||
lengths.writeDoubleLE(Buffer.byteLength(this.resources.bundle), 8)
|
||||
artifact.push(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
|
||||
artifact.push(null)
|
||||
})
|
||||
return artifact
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import colors = require('chalk')
|
||||
import ora = require('ora')
|
||||
|
||||
const frameLength = 120
|
||||
|
||||
export interface LogStep {
|
||||
modify(text: string, color?: string): void
|
||||
log(text: string, color?: string): void
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private verbose: boolean
|
||||
private silent: boolean
|
||||
private ora: any
|
||||
private modify: Function
|
||||
public write: (text: string, color?: string) => void
|
||||
|
||||
constructor(level: 'verbose' | 'silent' | 'info') {
|
||||
this.verbose = level === 'verbose'
|
||||
this.silent = level === 'silent'
|
||||
if (!this.silent) {
|
||||
this.ora = ora({
|
||||
text: 'Starting...',
|
||||
color: 'blue',
|
||||
spinner: 'dots'
|
||||
})
|
||||
this.ora.stop()
|
||||
}
|
||||
const noop = () => {}
|
||||
this.modify = this.silent ? noop : this._modify.bind(this)
|
||||
this.write = this.silent ? noop : this._write.bind(this)
|
||||
}
|
||||
|
||||
flush() {
|
||||
!this.silent && this.ora.succeed()
|
||||
return new Promise(resolve => setTimeout(resolve, frameLength))
|
||||
}
|
||||
|
||||
_write(update: string, color = 'green') {
|
||||
this.ora.succeed().text = (colors as any)[color](update)
|
||||
this.ora.start()
|
||||
}
|
||||
|
||||
_modify(update: string, color = this.ora.color) {
|
||||
this.ora.text = update
|
||||
this.ora.color = color
|
||||
}
|
||||
|
||||
step(text: string, method: string = 'succeed'): LogStep {
|
||||
if (this.silent) {
|
||||
return { modify() {}, log() {} }
|
||||
}
|
||||
if (!this.ora.id) {
|
||||
this.ora.start().text = text
|
||||
if (method !== 'succeed') {
|
||||
this.ora[method]()
|
||||
}
|
||||
} else {
|
||||
this.ora[method]().text = text
|
||||
this.ora.start()
|
||||
}
|
||||
|
||||
return {
|
||||
modify: this.modify,
|
||||
log: this.verbose ? this.write : this.modify
|
||||
} as LogStep
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { compose, PromiseConfig, Middleware } from 'app-builder'
|
||||
import resource from './steps/resource'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import {
|
||||
argv,
|
||||
nexeVersion as version,
|
||||
normalizeOptionsAsync,
|
||||
NexeOptions,
|
||||
NexePatch
|
||||
} from './options'
|
||||
import cli from './steps/cli'
|
||||
import bundle from './bundling/fuse'
|
||||
import download from './steps/download'
|
||||
import artifacts from './steps/artifacts'
|
||||
import patches from './patches'
|
||||
import { rimrafAsync } from './util'
|
||||
|
||||
async function compile(
|
||||
compilerOptions?: Partial<NexeOptions>,
|
||||
callback?: (err: Error | null) => void
|
||||
) {
|
||||
const options = await normalizeOptionsAsync(compilerOptions)
|
||||
const compiler = new NexeCompiler(options)
|
||||
const build = compiler.options.build
|
||||
|
||||
if (options.clean) {
|
||||
const step = compiler.log.step('Cleaning up nexe build artifacts...')
|
||||
step.log(`Deleting directory and contents at: ${compiler.src}`)
|
||||
await rimrafAsync(compiler.src)
|
||||
step.log(`Deleted directory and contents at: ${compiler.src}`)
|
||||
return compiler.quit()
|
||||
}
|
||||
|
||||
const buildSteps = build
|
||||
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
|
||||
: []
|
||||
const nexe = compose(resource, bundle, cli, buildSteps)
|
||||
return callback
|
||||
? void nexe(compiler).then(
|
||||
() => callback && callback(null),
|
||||
(e: Error) => {
|
||||
if (callback) callback(e)
|
||||
else throw e
|
||||
}
|
||||
)
|
||||
: nexe(compiler)
|
||||
}
|
||||
|
||||
export { argv, compile, version }
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
import * as parseArgv from 'minimist'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import { isWindows } from './util'
|
||||
import { basename, extname, join, isAbsolute, relative } from 'path'
|
||||
import { getTarget, NexeTarget } from './target'
|
||||
import { EOL } from 'os'
|
||||
|
||||
export const nexeVersion = '2.0.0-rc.1'
|
||||
|
||||
export interface NexePatch {
|
||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
||||
}
|
||||
|
||||
export interface NexeOptions {
|
||||
build: boolean
|
||||
input: string
|
||||
output: string
|
||||
targets: (string | NexeTarget)[]
|
||||
name: string
|
||||
cwd: string
|
||||
version: 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)[]
|
||||
empty: boolean
|
||||
sourceUrl?: string
|
||||
python?: string
|
||||
loglevel: 'info' | 'silent' | 'verbose'
|
||||
silent?: boolean
|
||||
verbose?: boolean
|
||||
info?: boolean
|
||||
ico?: string
|
||||
warmup?: string
|
||||
compress?: boolean
|
||||
clean?: boolean
|
||||
/**
|
||||
* Api Only
|
||||
*/
|
||||
downloadOptions: any
|
||||
}
|
||||
|
||||
function padRight(str: string, l: number) {
|
||||
return (str + ' '.repeat(l)).substr(0, l)
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
version: process.version.slice(1),
|
||||
flags: [],
|
||||
cwd: process.cwd(),
|
||||
configure: [],
|
||||
make: [],
|
||||
targets: [],
|
||||
vcBuild: isWindows ? ['nosign', 'release'] : [],
|
||||
enableNodeCli: false,
|
||||
compress: false,
|
||||
build: false,
|
||||
bundle: true,
|
||||
patches: []
|
||||
}
|
||||
const alias = {
|
||||
i: 'input',
|
||||
o: 'output',
|
||||
t: 'target',
|
||||
n: 'name',
|
||||
v: 'version',
|
||||
r: 'resource',
|
||||
a: 'resource',
|
||||
p: 'python',
|
||||
f: 'flag',
|
||||
c: 'configure',
|
||||
m: 'make',
|
||||
s: 'snapshot',
|
||||
h: 'help',
|
||||
l: 'loglevel'
|
||||
}
|
||||
const argv = parseArgv(process.argv, { alias, default: defaults })
|
||||
const help =
|
||||
`
|
||||
nexe --help CLI OPTIONS
|
||||
|
||||
-b --build -- build from source
|
||||
-i --input =index.js -- application entry point
|
||||
-o --output =my-app.exe -- path to output file
|
||||
-t --target =win32-x64-6.10.3 -- *target a prebuilt binary
|
||||
-n --name =my-app -- main app module name
|
||||
-v --version =${padRight(process.version.slice(1), 23)}-- node version
|
||||
-p --python =/path/to/python2 -- python executable
|
||||
-f --flag ="--expose-gc" -- *v8 flags to include during compilation
|
||||
-c --configure ="--with-dtrace" -- *pass arguments to the configure step
|
||||
-m --make ="--loglevel" -- *pass arguments to the make/build step
|
||||
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
|
||||
-r --resource =./paths/**/* -- *embed file bytes within the binary
|
||||
--bundle =./path/to/config -- pass a module path that exports nexeBundle
|
||||
--temp =./path/to/temp -- nexe temp files (for downloads and source builds)
|
||||
--no-bundle -- set when input is already bundled
|
||||
--cwd -- set the current working directory for the command
|
||||
--ico -- file name for alternate icon file (windows)
|
||||
--rc-* -- populate rc file options (windows)
|
||||
--clean -- force download of sources
|
||||
--enableNodeCli -- enable node cli enforcement (blocks app cli)
|
||||
--sourceUrl -- pass an alternate source (node.tar.gz) url
|
||||
--silent -- disable logging
|
||||
--verbose -- set logging to verbose
|
||||
|
||||
-* variable key name * option can be used more than once`.trim() + EOL
|
||||
|
||||
function flattenFilter(...args: any[]): string[] {
|
||||
return ([] as string[]).concat(...args).filter(x => x)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract keys such as { "rc-CompanyName": "Node.js" } to
|
||||
* { CompanyName: "Node.js" }
|
||||
* @param {*} match
|
||||
* @param {*} options
|
||||
*/
|
||||
function extractCliMap(match: RegExp, options: any) {
|
||||
return Object.keys(options)
|
||||
.filter(x => match.test(x))
|
||||
.reduce((map: { [key: string]: string }, option: keyof NexeOptions) => {
|
||||
const key = option.split('-')[1]
|
||||
map[key] = options[option]
|
||||
delete options[option]
|
||||
return map
|
||||
}, {})
|
||||
}
|
||||
|
||||
function tryResolveMainFileName(cwd: string) {
|
||||
let filename
|
||||
try {
|
||||
const file = require.resolve(cwd)
|
||||
filename = basename(file).replace(extname(file), '')
|
||||
} catch (_) {}
|
||||
|
||||
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
|
||||
}
|
||||
|
||||
function extractLogLevel(options: NexeOptions) {
|
||||
if (options.loglevel) return options.loglevel
|
||||
if (options.silent) return 'silent'
|
||||
if (options.verbose) return 'verbose'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function extractName(options: NexeOptions) {
|
||||
let name = options.name
|
||||
if (!name && typeof options.input === 'string') {
|
||||
name = basename(options.input).replace(extname(options.input), '')
|
||||
}
|
||||
name = name || tryResolveMainFileName(options.cwd)
|
||||
return name.replace(/\.exe$/, '')
|
||||
}
|
||||
|
||||
function isEntryFile(filename: string) {
|
||||
return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
|
||||
}
|
||||
|
||||
function findInput(input: string, cwd: string) {
|
||||
const maybeInput = argv._.slice().pop() || ''
|
||||
if (input) {
|
||||
return input
|
||||
}
|
||||
if (isEntryFile(maybeInput)) {
|
||||
return maybeInput
|
||||
}
|
||||
try {
|
||||
const main = require.resolve(cwd)
|
||||
return './' + relative(cwd, main)
|
||||
} catch (e) {
|
||||
void e
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions | never> {
|
||||
if (argv.help || argv._.some((x: string) => x === 'version') || argv.version === true) {
|
||||
return new Promise(() => {
|
||||
process.stderr.write(argv.help ? help : nexeVersion + EOL, () => process.exit(0))
|
||||
})
|
||||
}
|
||||
const options = Object.assign({}, defaults, input) as NexeOptions
|
||||
const opts = options as any
|
||||
|
||||
options.temp = process.env.NEXE_TEMP || join(options.cwd, '.nexe')
|
||||
options.name = extractName(options)
|
||||
options.input = findInput(options.input, options.cwd)
|
||||
options.loglevel = extractLogLevel(options)
|
||||
options.flags = flattenFilter(opts.flag, options.flags)
|
||||
options.targets = flattenFilter(opts.target, options.targets)
|
||||
options.make = flattenFilter(options.vcBuild, options.make)
|
||||
options.configure = flattenFilter(options.configure)
|
||||
options.resources = flattenFilter(opts.resource, options.resources)
|
||||
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
|
||||
|
||||
options.targets = options.targets.map(getTarget)
|
||||
|
||||
if (options.build && options.targets.length) {
|
||||
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}`])))
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.targets.length) {
|
||||
options.targets = [getTarget(options.version)]
|
||||
}
|
||||
|
||||
options.patches = options.patches.map(x => {
|
||||
if (typeof x === 'string') {
|
||||
return require(x).default
|
||||
}
|
||||
return x
|
||||
})
|
||||
|
||||
Object.keys(alias)
|
||||
.filter(k => k !== 'rc')
|
||||
.forEach(x => delete opts[x])
|
||||
|
||||
return Promise.resolve(options)
|
||||
}
|
||||
|
||||
export { argv, normalizeOptionsAsync }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
if (!compiler.options.version.startsWith('8.2')) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const file = await compiler.readFileAsync('./tools/msvs/find_python.cmd')
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'./tools/msvs/find_python.cmd',
|
||||
'%p%python.exe -V 2>&1',
|
||||
'"%p%python.exe" -V 2>&1'
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
if (compiler.options.enableNodeCli) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const nodeccMarker = "argv[index][0] == '-'"
|
||||
|
||||
await compiler.replaceInFileAsync('src/node.cc', nodeccMarker, nodeccMarker.replace('-', ']'))
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const nodeflags = compiler.options.flags
|
||||
if (!nodeflags.length) {
|
||||
return next()
|
||||
}
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'node.gyp',
|
||||
"'node_v8_options%': ''",
|
||||
`'node_v8_options%': '${nodeflags.join(' ')}'`
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { normalize } from 'path'
|
||||
import { readFileAsync } from '../util'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function ico(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const iconFile = compiler.options.ico
|
||||
if (!iconFile) {
|
||||
return next()
|
||||
}
|
||||
await compiler.setFileContentsAsync('src/res/node.ico', await readFileAsync(normalize(iconFile)))
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import gyp from './gyp'
|
||||
import nexePatches from './third-party-main'
|
||||
import buildFixes from './build-fixes'
|
||||
import cli from './disable-node-cli'
|
||||
import flags from './flags'
|
||||
import ico from './ico'
|
||||
import rc from './node-rc'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
const patches = [gyp, nexePatches, buildFixes, cli, flags, ico, rc]
|
||||
|
||||
export default patches
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function nodeRc(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const options = compiler.options.rc
|
||||
if (!options) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const file = await compiler.readFileAsync('src/res/node.rc')
|
||||
|
||||
Object.keys(options).forEach(key => {
|
||||
let value = options[key]
|
||||
const isVar = /^[A-Z_]+$/.test(value)
|
||||
value = isVar ? value : `"${value}"`
|
||||
file.contents.replace(new RegExp(`VALUE "${key}",*`), `VALUE "${key}", ${value}`)
|
||||
})
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function snapshot(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const snapshotFile = compiler.options.snapshot
|
||||
const warmupScript = compiler.options.warmup
|
||||
|
||||
if (!snapshotFile) {
|
||||
return next()
|
||||
}
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'configure',
|
||||
'def configure_v8(o):',
|
||||
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${warmupScript ||
|
||||
snapshotFile}'`
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await compiler.setFileContentsAsync(
|
||||
'lib/_third_party_main.js',
|
||||
`
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const Buffer = require('buffer').Buffer
|
||||
const isString = x => typeof x === 'string' || x instanceof String
|
||||
|
||||
const fd = fs.openSync(process.execPath, 'r')
|
||||
const size = fs.statSync(process.execPath).size
|
||||
const footer = Buffer.from(Array(32))
|
||||
fs.readSync(fd, footer, 0, 32, size - 32)
|
||||
|
||||
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
|
||||
throw 'Invalid Nexe binary'
|
||||
}
|
||||
|
||||
const contentSize = footer.readDoubleLE(16)
|
||||
const resourceSize = footer.readDoubleLE(24)
|
||||
const contentStart = size - 32 - resourceSize - contentSize
|
||||
const resourceStart = contentStart + contentSize
|
||||
|
||||
Object.defineProperty(process, '__nexe', (function () {
|
||||
let nexeHeader = null
|
||||
return {
|
||||
get: function () {
|
||||
return nexeHeader
|
||||
},
|
||||
set: function (value) {
|
||||
if (nexeHeader) {
|
||||
throw new Error('__nexe cannot be reconfigured')
|
||||
}
|
||||
nexeHeader = value
|
||||
Object.freeze(nexeHeader)
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
}
|
||||
})());
|
||||
|
||||
if (resourceSize) {
|
||||
const originalReadFile = fs.readFile
|
||||
const originalReadFileSync = fs.readFileSync
|
||||
let setupManifest = () => {
|
||||
const manifest = process.__nexe && process.__nexe.resources
|
||||
if (!manifest) {
|
||||
return
|
||||
}
|
||||
Object.keys(manifest).forEach((key) => {
|
||||
const absolutePath = path.resolve(key)
|
||||
if (!manifest[absolutePath]) {
|
||||
manifest[absolutePath] = manifest[key]
|
||||
}
|
||||
const normalizedPath = path.normalize(key)
|
||||
if (!manifest[normalizedPath]) {
|
||||
manifest[normalizedPath] = manifest[key]
|
||||
}
|
||||
})
|
||||
normalizeManifest = () => {}
|
||||
};
|
||||
fs.readFile = function readFile (file, options, callback) {
|
||||
setupManifest()
|
||||
const manifest = process.__nexe
|
||||
const entry = manifest && manifest.resources[file]
|
||||
if (!manifest || !entry || !isString(file)) {
|
||||
return originalReadFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = isString(options) ? options : null
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
fs.open(process.execPath, 'r', function (err, fd) {
|
||||
if (err) return callback(err, null)
|
||||
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function (error, bytesRead, buffer) {
|
||||
if (error) {
|
||||
return fs.close(fd, function () {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
fs.close(fd, function (err) {
|
||||
if (err) {
|
||||
return callback(err, buffer)
|
||||
}
|
||||
const result = Buffer.from(buffer.toString(), 'base64')
|
||||
callback(err, encoding ? result.toString(encoding) : result)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fs.readFileSync = function readFileSync (file, options) {
|
||||
setupManifest()
|
||||
const manifest = process.__nexe
|
||||
const entry = manifest && manifest.resources[file]
|
||||
if (!manifest || !entry || !isString(file)) {
|
||||
return originalReadFileSync.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = isString(options) ? options : null
|
||||
const fd = fs.openSync(process.execPath, 'r')
|
||||
const result = Buffer.alloc(length)
|
||||
fs.readSync(fd, result, 0, length, resourceOffset)
|
||||
fs.closeSync(fd)
|
||||
const contents = Buffer.from(result.toString(), 'base64')
|
||||
return encoding ? contents.toString(encoding) : contents
|
||||
}
|
||||
}
|
||||
|
||||
const contentBuffer = Buffer.from(Array(contentSize));
|
||||
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
|
||||
fs.closeSync(fd);
|
||||
const Module = require('module');
|
||||
process.mainModule = new Module(process.execPath, null);
|
||||
process.mainModule.loaded = true;
|
||||
process.mainModule._compile(contentBuffer.toString(), process.execPath);
|
||||
`.trim()
|
||||
)
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import got = require('got')
|
||||
import download = require('download')
|
||||
import {
|
||||
NodePlatform,
|
||||
NodeArch,
|
||||
platforms,
|
||||
architectures,
|
||||
NexeTarget,
|
||||
getTarget,
|
||||
targetsEqual
|
||||
} from './target'
|
||||
export { NexeTarget }
|
||||
|
||||
export interface GitAsset {
|
||||
name: string
|
||||
url: string
|
||||
browser_download_url: string
|
||||
}
|
||||
|
||||
export interface GitRelease {
|
||||
tag_name: string
|
||||
assets_url: string
|
||||
upload_url: string
|
||||
assets: GitAsset[]
|
||||
}
|
||||
|
||||
interface NodeRelease {
|
||||
version: string
|
||||
}
|
||||
|
||||
async function getJson<T>(url: string, options?: any) {
|
||||
return JSON.parse((await got(url, options)).body) as T
|
||||
}
|
||||
|
||||
//TODO only build the latest of each major...?
|
||||
function isBuildableVersion(version: string) {
|
||||
const major = +version.split('.')[0]
|
||||
return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4'
|
||||
}
|
||||
|
||||
export function getLatestGitRelease(options?: any) {
|
||||
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options)
|
||||
}
|
||||
|
||||
export async function storeAsset(asset: GitAsset, dest: string, headers?: any) {
|
||||
const options = headers ? { headers } : undefined
|
||||
await download(asset.browser_download_url, dest, options as any)
|
||||
}
|
||||
|
||||
export async function getUnBuiltReleases(options?: any) {
|
||||
const nodeReleases = await getJson<NodeRelease[]>(
|
||||
'https://nodejs.org/download/release/index.json'
|
||||
)
|
||||
const existingVersions = (await getLatestGitRelease(options)).assets.map(x => getTarget(x.name))
|
||||
|
||||
const versionMap: { [key: string]: true } = {}
|
||||
return nodeReleases
|
||||
.reduce((versions: NexeTarget[], { version }) => {
|
||||
version = version.replace('v', '').trim()
|
||||
if (!isBuildableVersion(version) || versionMap[version]) {
|
||||
return versions
|
||||
}
|
||||
versionMap[version] = true
|
||||
platforms.forEach(platform => {
|
||||
architectures.forEach(arch => {
|
||||
if (arch === 'x86' && platform === 'mac') return
|
||||
versions.push(getTarget({ platform, arch, version }))
|
||||
})
|
||||
})
|
||||
return versions
|
||||
}, [])
|
||||
.filter(x => !existingVersions.some(t => targetsEqual(t, x)))
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { normalize } from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import { createWriteStream, chmodSync } from 'fs'
|
||||
import { readFileAsync, dequote, isWindows } from '../util'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeTarget } from '../target'
|
||||
|
||||
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
|
||||
return new Promise(resolve => {
|
||||
let input = ''
|
||||
stream.setEncoding('utf-8')
|
||||
stream.on('data', (x: string) => {
|
||||
input += x
|
||||
})
|
||||
stream.once('end', () => resolve(dequote(input)))
|
||||
stream.resume && stream.resume()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* The "cli" step detects the appropriate input. If no input options are passed,
|
||||
* the package.json#main file is used.
|
||||
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
|
||||
*
|
||||
* Configuration:
|
||||
* - compiler.options.input - file path to the input bundle.
|
||||
* - fallbacks: stdin, package.json#main
|
||||
* - compiler.options.output - file path to the output executable.
|
||||
* - fallbacks: stdout, nexe_ + epoch + ext
|
||||
*
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { output } = compiler.options
|
||||
const { log, input: bundledInput } = compiler
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
log.step('Using stdin as input')
|
||||
compiler.input = await readStreamAsync(process.stdin)
|
||||
}
|
||||
|
||||
await next()
|
||||
|
||||
compiler.output = isWindows
|
||||
? `${(output || compiler.options.name).replace(/\.exe$/, '')}.exe`
|
||||
: `${output || compiler.options.name}`
|
||||
|
||||
const target = compiler.options.targets.shift() as NexeTarget
|
||||
const deliverable = await compiler.compileAsync(target)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const step = log.step('Writing result to file')
|
||||
deliverable
|
||||
.pipe(createWriteStream(normalize(compiler.output!)))
|
||||
.on('error', reject)
|
||||
.once('close', (e: Error) => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else if (compiler.output) {
|
||||
chmodSync(compiler.output, '755')
|
||||
step.log(`Executable written to: ${compiler.output}`)
|
||||
resolve(compiler.quit())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import download = require('download')
|
||||
import { pathExistsAsync } from '../util'
|
||||
import { LogStep } from '../logger'
|
||||
import { IncomingMessage } from 'http'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
|
||||
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
|
||||
return download(url, dest, Object.assign(options, { extract: true, strip: 1 }))
|
||||
.on('response', (res: IncomingMessage) => {
|
||||
const total = +res.headers['content-length']!
|
||||
let current = 0
|
||||
res.on('data', data => {
|
||||
current += data.length
|
||||
setText(current / total * 100)
|
||||
if (current === total) {
|
||||
step.log('Extracting Node...')
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(() => step.log(`Node source extracted to: ${dest}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the node source to the configured temporary directory
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { src, log } = compiler
|
||||
const { version, sourceUrl, downloadOptions } = compiler.options
|
||||
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
|
||||
const step = log.step(`Downloading Node.js source from: ${url}`)
|
||||
if (await pathExistsAsync(src)) {
|
||||
step.log('Source already downloaded')
|
||||
return next()
|
||||
}
|
||||
|
||||
if (compiler.options.build) {
|
||||
await fetchNodeSourceAsync(src, url, step, downloadOptions)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { readFileAsync, isDirectoryAsync, each } from '../util'
|
||||
import { Buffer } from 'buffer'
|
||||
import * as globs from 'globby'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const resources = compiler.resources
|
||||
|
||||
if (!compiler.options.resources.length) {
|
||||
return next()
|
||||
}
|
||||
const step = compiler.log.step('Bundling Resources...')
|
||||
let count = 0
|
||||
await each(globs(compiler.options.resources), async file => {
|
||||
if (await isDirectoryAsync(file)) {
|
||||
return
|
||||
}
|
||||
count++
|
||||
step.log(`Including file: ${file}`)
|
||||
const contents = await readFileAsync(file)
|
||||
const commentSafeContents = contents.toString('base64')
|
||||
resources.index[file] = [
|
||||
Buffer.byteLength(resources.bundle),
|
||||
Buffer.byteLength(commentSafeContents)
|
||||
]
|
||||
resources.bundle += commentSafeContents
|
||||
})
|
||||
step.log(
|
||||
`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`
|
||||
)
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
|
||||
export type NodeArch = 'x86' | 'x64'
|
||||
|
||||
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
|
||||
architectures: NodeArch[] = ['x86', 'x64']
|
||||
|
||||
export { platforms, architectures }
|
||||
|
||||
export interface NexeTarget {
|
||||
version: string
|
||||
platform: NodePlatform
|
||||
arch: NodeArch
|
||||
}
|
||||
|
||||
//TODO bsd
|
||||
const prettyPlatform: { [key: string]: NodePlatform } = {
|
||||
win32: 'windows',
|
||||
windows: 'windows',
|
||||
darwin: 'mac',
|
||||
macos: 'mac',
|
||||
mac: 'mac',
|
||||
linux: 'linux',
|
||||
static: 'alpine',
|
||||
alpine: 'alpine'
|
||||
}
|
||||
|
||||
//TODO arm
|
||||
const prettyArch: { [key: string]: NodeArch } = {
|
||||
x86: 'x86',
|
||||
ia32: 'x86',
|
||||
x32: 'x86',
|
||||
x64: 'x64'
|
||||
}
|
||||
|
||||
function isVersion(x: string) {
|
||||
if (!x) {
|
||||
return false
|
||||
}
|
||||
return /\d/.test(x.replace(/v|\./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 | NexeTarget = ''): NexeTarget {
|
||||
let 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
|
||||
}
|
||||
if (isPlatform(x)) {
|
||||
platform = prettyPlatform[x]
|
||||
}
|
||||
if (isArch(x)) {
|
||||
arch = prettyArch[x]
|
||||
}
|
||||
})
|
||||
|
||||
return new Target(arch, platform, version)
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
declare module 'got' {
|
||||
interface GotFn {
|
||||
(url: string, options?: any): Promise<{ body: string }>
|
||||
stream(url: string, optoins?: any): any
|
||||
}
|
||||
function got(url: string, options?: any): Promise<{ body: string }>
|
||||
export = got as GotFn
|
||||
}
|
||||
declare module 'download' {
|
||||
import { Duplex } from 'stream'
|
||||
interface DownloadOptions {
|
||||
extract?: boolean
|
||||
strip?: number
|
||||
filename?: string
|
||||
proxy?: string
|
||||
}
|
||||
function download(
|
||||
url: string,
|
||||
destination?: string | DownloadOptions,
|
||||
options?: DownloadOptions
|
||||
): PromiseLike<Buffer> & Duplex
|
||||
export = download
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { readFile, writeFile, stat } from 'fs'
|
||||
import { execFile } from 'child_process'
|
||||
import pify = require('pify')
|
||||
import rimraf = require('rimraf')
|
||||
|
||||
const rimrafAsync = pify(rimraf)
|
||||
|
||||
export async function each<T>(
|
||||
list: T[] | Promise<T[]>,
|
||||
action: (item: T, index: number, list: T[]) => Promise<any>
|
||||
) {
|
||||
const l = await list
|
||||
for (let i = 0; i < l.length; i++) {
|
||||
await action(l[i], i, l)
|
||||
}
|
||||
}
|
||||
|
||||
function falseOnEnoent(e: any) {
|
||||
if (e.code === 'ENOENT') {
|
||||
return false
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
function dequote(input: string) {
|
||||
input = input.trim()
|
||||
const singleQuote = input.startsWith("'") && input.endsWith("'")
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"')
|
||||
if (singleQuote || doubleQuote) {
|
||||
return input.slice(1).slice(0, -1)
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
export interface ReadFileAsync {
|
||||
(path: string): Promise<Buffer>
|
||||
(path: string, encoding: string): Promise<string>
|
||||
}
|
||||
|
||||
const readFileAsync = pify(readFile)
|
||||
const writeFileAsync = pify(writeFile)
|
||||
const statAsync = pify(stat)
|
||||
const execFileAsync = pify(execFile)
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function pathExistsAsync(path: string) {
|
||||
return statAsync(path)
|
||||
.then(x => true)
|
||||
.catch(falseOnEnoent)
|
||||
}
|
||||
|
||||
function isDirectoryAsync(path: string) {
|
||||
return statAsync(path)
|
||||
.then(x => x.isDirectory())
|
||||
.catch(falseOnEnoent)
|
||||
}
|
||||
|
||||
export {
|
||||
dequote,
|
||||
isWindows,
|
||||
rimrafAsync,
|
||||
statAsync,
|
||||
execFileAsync,
|
||||
readFileAsync,
|
||||
pathExistsAsync,
|
||||
isDirectoryAsync,
|
||||
writeFileAsync
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import * as nexe from '../src/nexe'
|
||||
import {
|
||||
getUnBuiltReleases,
|
||||
getLatestGitRelease
|
||||
} from '../src/releases'
|
||||
import * as ci from './ci'
|
||||
import { runAlpineBuild } from './docker'
|
||||
import { getTarget } from '../src/target'
|
||||
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src/util'
|
||||
import got = require('got')
|
||||
|
||||
const env = process.env,
|
||||
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
|
||||
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
|
||||
isLinux = Boolean(env.TRAVIS),
|
||||
isWindows = Boolean(env.APPVEYOR),
|
||||
isMac = Boolean(env.CIRCLECI),
|
||||
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER)
|
||||
|| Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
|
||||
|| Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
|
||||
headers = { 'Authorization': 'token ' + env.GITHUB_TOKEN }
|
||||
|
||||
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, //FIXME
|
||||
output
|
||||
}
|
||||
|
||||
const stop = keepalive()
|
||||
if (target.platform === 'alpine') {
|
||||
await runAlpineBuild(target, nexe.version.split('.')[0])
|
||||
} else {
|
||||
await nexe.compile(options)
|
||||
}
|
||||
stop()
|
||||
|
||||
|
||||
if (await pathExistsAsync(output)) {
|
||||
await assertNexeBinary(output)
|
||||
const gitRelease = await getLatestGitRelease({ headers })
|
||||
await got(gitRelease.upload_url.split('{')[0], {
|
||||
query: { name: target.toString() },
|
||||
body: await readFileAsync(output),
|
||||
headers: {
|
||||
'Authorization': 'token ' + env.GITHUB_TOKEN,
|
||||
'Content-Type': 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
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
|
||||
})
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { NexeTarget } from '../src/releases'
|
||||
import got = require('got')
|
||||
import * as assert from 'assert'
|
||||
const { env } = process
|
||||
|
||||
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: {
|
||||
NEXE_VERSION: release
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'Travis-API-Version': '3',
|
||||
'Authorization': `token ${env.TRAVIS_TOKEN}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 triggerWindowsBuild (release: NexeTarget) {
|
||||
const hasVersion = 'NEXE_VERSION' in env
|
||||
env.NEXE_VERSION = hasVersion
|
||||
? env.NEXE_VERSION!.trim()
|
||||
: release.toString()
|
||||
return Promise.resolve()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NexeTarget } from '../src/target'
|
||||
import { writeFileAsync } from '../src/util'
|
||||
import exec = require('execa')
|
||||
|
||||
function alpine (target: NexeTarget, nexeVersion: string) {
|
||||
const version = target.version
|
||||
return `
|
||||
FROM i386/alpine:3.6
|
||||
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
|
||||
WORKDIR /
|
||||
RUN curl -sSL https://nodejs.org/dist/v${version}/node-v${version}.tar.gz | tar -xz && \
|
||||
cd /node-v${version} && \
|
||||
./configure --prefix=/usr --fully-static && \
|
||||
make -j$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
|
||||
make install && \
|
||||
paxctl -cm /usr/bin/node
|
||||
|
||||
RUN mkdir nexe_temp && mv node-v${version} nexe_temp/${version}
|
||||
RUN npm i nexe@beta -g
|
||||
ENV NEXE_TEMP=/nexe_temp
|
||||
|
||||
RUN nexe --empty -c="--fully-static" -o /nexe-out`.trim()
|
||||
}
|
||||
|
||||
export async function runAlpineBuild (target: NexeTarget, nexeVersion: string) {
|
||||
await writeFileAsync('Dockerfile', alpine(target, nexeVersion))
|
||||
const option = { stido: 'inherit' } as any
|
||||
await exec('docker', ['build', '-t', 'nexe-alpine', '.'], option)
|
||||
await exec('docker', ['run', '--name', 'nexe', 'nexe-alpine'], option)
|
||||
await exec('docker', ['cp', 'nexe:/nexe-out', 'out'], option)
|
||||
await exec('docker', ['rm', 'nexe'], option)
|
||||
}
|
||||
Binary file not shown.
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Test Express with nexe
|
||||
**/
|
||||
|
||||
console.log('express init');
|
||||
|
||||
var express = require('express');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.get('/', function(req, res) {
|
||||
res.send('It works!');
|
||||
});
|
||||
|
||||
app.listen(8000, function() {
|
||||
console.log('Listening on port 8000');
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "express-test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"nexe": {
|
||||
"input": "./index.js",
|
||||
"output": "express-test^$",
|
||||
"temp": "src",
|
||||
"runtime": {
|
||||
"framework": "nodejs",
|
||||
"version": "5.5.0",
|
||||
"ignoreFlags": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.13.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target":"es5",
|
||||
"lib": ["es2017"],
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
"strict": true
|
||||
},
|
||||
"include":[
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user