Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34c51064f4 | |||
| be48e5a2f3 | |||
| 0340c01b36 | |||
| e62cd19b42 | |||
| fc99386199 | |||
| f949bf3040 | |||
| 0a1e747ca3 | |||
| 91aa014b7d |
@@ -4,8 +4,6 @@ coverage
|
||||
node_modules
|
||||
*.log
|
||||
*.exe
|
||||
*.bin
|
||||
!winsw.exe
|
||||
.idea
|
||||
.nexe
|
||||
lib
|
||||
|
||||
+7
-12
@@ -1,12 +1,7 @@
|
||||
language: node_js
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y g++-multilib
|
||||
node_js:
|
||||
- '6'
|
||||
script: npm run asset-compile
|
||||
env:
|
||||
global:
|
||||
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
|
||||
notifications:
|
||||
email: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
script: npm run nexe-build
|
||||
env:
|
||||
global:
|
||||
secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
|
||||
|
||||
Vendored
-1
@@ -3,7 +3,6 @@
|
||||
"prettier.semi": false,
|
||||
"prettier.singleQuote": true,
|
||||
"prettier.typescriptEnable": ["typescript"],
|
||||
"editor.trimAutoWhitespace": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
<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>
|
||||
<a href="https://gitter.im/nexe/nexe?utm_source=badge&utm_medium=badge"><img src="https://badges.gitter.im/nexe/nexe.svg"></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><code>npm i nexe -g</code></p>
|
||||
<p align="center"><code>npm i nexe@beta -g</code></p>
|
||||
<p align="center">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/5818726/30999006-df7e0ae0-a497-11e7-96db-9ce87ae67b34.gif"/>
|
||||
<img src="https://cloud.githubusercontent.com/assets/5818726/26533446/ce19ee5a-43de-11e7-9540-caf7ebd93370.gif"/>
|
||||
</p>
|
||||
|
||||
## Motivation and Features
|
||||
|
||||
- Supports production ready builds
|
||||
- Self contained applications
|
||||
- Ability to run multiple applications with *different* node.js runtimes.
|
||||
- Distribute binaries without needing node / npm.
|
||||
@@ -28,42 +28,40 @@
|
||||
|
||||
## Usage
|
||||
|
||||
- Application entrypoint:
|
||||
- Existing application bundle:
|
||||
|
||||
`nexe my-app.js`
|
||||
`nexe my-app-bundle.js -o my-app`
|
||||
|
||||
- stdin interface
|
||||
|
||||
`rollup -c | nexe --resource "./public/**/*" -o my-app.exe`
|
||||
`rollup -c | nexe --resource ./public/**/* -o my-app.exe`
|
||||
|
||||
For more CLI options see: `nexe --help`
|
||||
|
||||
### Examples
|
||||
## Including Additional Resources
|
||||
|
||||
- `nexe server.js -r public/**/*.html`
|
||||
- `nexe my-bundle.js --no-bundle -o app.exe`
|
||||
- `nexe --build`
|
||||
- `nexe -t x86-8.0.0`
|
||||
|
||||
## Resources
|
||||
|
||||
Additional files or resources can be added to the binary by passing `-r "glob/pattern/**/*"`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`.
|
||||
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, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
|
||||
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptbale version of python 2. eg. Systems that have python2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486)
|
||||
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
|
||||
|
||||
|
||||
```javascript
|
||||
const { compile } = require('nexe')
|
||||
|
||||
compile({
|
||||
input: './my-app.js',
|
||||
build: true, //required to use patches
|
||||
output: './my-app.exe',
|
||||
build: true, //builds node
|
||||
patches: [
|
||||
async (compiler, next) => {
|
||||
await compiler.setFileContentsAsync(
|
||||
@@ -77,65 +75,60 @@ compile({
|
||||
console.log('success')
|
||||
})
|
||||
```
|
||||
## NexeOptions
|
||||
|
||||
### `options: object`
|
||||
### `options`
|
||||
|
||||
- #### `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 | object`
|
||||
- An object or string describing platform-arch-version. e.g. `'windows-ia32-6.10.3'`
|
||||
- each segment is optional, and will be merged with the current environment
|
||||
- Examples: ([full list](https://github.com/nexe/nexe/releases))
|
||||
- `'win32-x86-8.6.0`
|
||||
- `{ platform: 'alpine' }`
|
||||
- `darwin-8.6.0`
|
||||
- `linux-x64`
|
||||
- `macos-8.4.0`
|
||||
|
||||
See [test/target.spec.ts](test/target.spec.ts)
|
||||
- If the [`build`](#build-boolean) flag is set, the platform portion of the target is ignored.
|
||||
- default: `process`
|
||||
- #### `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 (options: NexeOptions): Promise<string>
|
||||
export function createBundle (
|
||||
filename: string,
|
||||
options: { name: string, minify: any, cwd: string }
|
||||
): Promise<string>`
|
||||
```
|
||||
- default: true
|
||||
- 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()
|
||||
- #### `build: boolean`
|
||||
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
|
||||
- #### `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: string[]`
|
||||
- #### `flags: Array<string>`
|
||||
- Array of node runtime flags to build node with.
|
||||
- Example: `['--expose-gc']`
|
||||
- default: `[]`
|
||||
- #### `configure: string[]`
|
||||
- #### `configure: Array<string>`
|
||||
- Array of arguments for the node build configure step
|
||||
- Example: `['--with-dtrace', '--dest-cpu=x64']`
|
||||
- default: `[]`
|
||||
- #### `make: string[]`
|
||||
- #### `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
|
||||
- #### `vcBuild: string[]`
|
||||
- #### `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: string[]`
|
||||
- #### `resources: Array<string>`
|
||||
- Array of globs with files to include in the build
|
||||
- Example: `['./public/**/*']`
|
||||
- default: `[]`
|
||||
@@ -147,41 +140,27 @@ compile({
|
||||
- Path to a user provided icon to be used (Windows only).
|
||||
- #### `rc: object`
|
||||
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
|
||||
- Example:
|
||||
```javascript
|
||||
{
|
||||
CompanyName: "ACME Corp",
|
||||
PRODUCTVERSION: "17,3,0,0",
|
||||
FILEVERSION: "1,2,3,4"
|
||||
...
|
||||
}
|
||||
```
|
||||
- Example: `{ CompanyName: "ACME Corp" }`
|
||||
- default: `{}`
|
||||
- #### `clean: boolean`
|
||||
- If included, nexe will remove temporary files for the accompanying configuration and exit
|
||||
- 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`
|
||||
- #### `fakeArgv: boolean`
|
||||
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
|
||||
- #### `sourceUrl: string`
|
||||
- Provide an alternate url for the node source code
|
||||
- Note: temporary files will still be created for this under the specified version
|
||||
- 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: NexePatch[]`
|
||||
- #### `patches: Array<NexePatch>`
|
||||
- Userland patches for patching or modifying node source
|
||||
- default: `[]`
|
||||
- #### `plugins: NexePatch[]`
|
||||
- Userland plugins for modifying nexe executable behavior
|
||||
- default: `[]`
|
||||
|
||||
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
|
||||
|
||||
Patches and Plugins are just a middleware functions that take two arguments, the `compiler`, and `next`. The compiler is described below, and `next` ensures that the pipeline continues. Its invocation should always be awaited or returned to ensure correct behavior. Patches also require that [`--build`](#build-boolean) be set, while plugins do not.
|
||||
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.
|
||||
|
||||
For examples, see the built in patches: [src/patches](src/patches).
|
||||
For examples, see the built in patches: [src/patches](src/patches)
|
||||
|
||||
### `NexeCompiler`
|
||||
|
||||
@@ -191,9 +170,7 @@ For examples, see the built in patches: [src/patches](src/patches).
|
||||
- Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace`
|
||||
- `readFileAsync(filename: string): Promise<NexeFile>`
|
||||
- Access (or create) a file within the downloaded Node.js source.
|
||||
- `addResource(filename: string, contents: Buffer): void`
|
||||
- Add a resource to the nexe bundle
|
||||
- `files: NexeFile[]`
|
||||
- `files: Array<NexeFile>`
|
||||
- The cache of the currently read, modified, or created files within the downloaded Node.js source.
|
||||
|
||||
#### `NexeFile`
|
||||
@@ -203,31 +180,12 @@ For examples, see the built in patches: [src/patches](src/patches).
|
||||
|
||||
Any modifications made to `NexeFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
|
||||
|
||||
## Native Modules
|
||||
### Native Modules
|
||||
|
||||
Any `.node` binding can be used with nexe. These library files will be bundled and written alongside the resulting binary at runtime. Currently, nexe supports modules loaded directly (`.node` files) and those loaded with the `'bindings'` module. It does not yet support modules using `node-pre-gyp#find`.
|
||||
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)
|
||||
|
||||
Its important to note: unless your native module conditionally loads each platform binary. Nexe builds with native modules will be platform specific. Eg. You will no longer be able to use cross platform builds.
|
||||
|
||||
Nexe builds with native modules will need to target the same version, platform and architecture as the platform hosting the module. At least until [N-API](https://github.com/nodejs/abi-stable-node) is fully propegated
|
||||
|
||||
TODO
|
||||
|
||||
- [ ] Implement support for `node-pre-gyp#find`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Building
|
||||
```
|
||||
$ git clone git@github.com:nexe/nexe.git
|
||||
$ cd nexe
|
||||
$ yarn
|
||||
```
|
||||
|
||||
Testing
|
||||
```
|
||||
$ npm test
|
||||
```
|
||||
- [ ] Implement support `node-pre-gyp#find`.
|
||||
|
||||
## Maintainers
|
||||
|
||||
|
||||
+5
-2
@@ -5,13 +5,16 @@ environment:
|
||||
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
|
||||
GITHUB_TOKEN:
|
||||
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
|
||||
branches:
|
||||
except:
|
||||
- master
|
||||
install:
|
||||
- ps: Install-Product node 6
|
||||
- yarn
|
||||
- npm i
|
||||
build: off
|
||||
test: off
|
||||
build_script:
|
||||
- yarn run asset-compile
|
||||
- npm run nexe-build
|
||||
artifacts:
|
||||
- path: .nexe\**\node.exe
|
||||
cache:
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
test:
|
||||
override:
|
||||
- npm run asset-compile
|
||||
- npm run nexe-build
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
const nexe = require('../..')
|
||||
nexe.compile({
|
||||
output: 'native-build',
|
||||
silent: true,
|
||||
native: {
|
||||
zmq: {
|
||||
additionalFiles: [
|
||||
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
|
||||
]
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
@@ -8,6 +8,7 @@ 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')
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "node build"
|
||||
"bundle": "node build",
|
||||
"build-windows": "nexe -i app.js -o app.exe"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"sqlite3": "^3.1.10",
|
||||
"zmq": "^2.15.3"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"devDependencies": {
|
||||
"fuse-box": "^2.2.1-beta.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const options = require('./lib/options')
|
||||
const nexe = require('./lib/nexe')
|
||||
|
||||
module.exports = nexe
|
||||
|
||||
if (require.main === module) {
|
||||
//fast path for help/version
|
||||
const argv = options.argv
|
||||
const eol = require('os').EOL
|
||||
const showHelp = argv.help || argv._.some(x => x === 'help')
|
||||
const showVersion = argv.version || argv._.some(x => x === 'version')
|
||||
if (showHelp || showVersion) {
|
||||
process.stderr.write(showHelp ? options.help : options.version + eol)
|
||||
} else {
|
||||
const nexe = require('./lib/nexe')
|
||||
nexe.compile(argv).catch((e) => {
|
||||
process.stderr.write(eol + e.stack)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
module.exports = require('./lib/nexe')
|
||||
nexe.compile(nexe.argv).catch((e) => {
|
||||
process.stderr.write(e.stack, () => process.exit(1))
|
||||
})
|
||||
}
|
||||
|
||||
Generated
+3830
File diff suppressed because it is too large
Load Diff
+11
-16
@@ -2,20 +2,18 @@
|
||||
"name": "nexe",
|
||||
"description": "Create a single executable out of your Node.js application",
|
||||
"license": "MIT",
|
||||
"version": "2.0.0-rc.15",
|
||||
"version": "2.0.0-rc.1",
|
||||
"contributors": [
|
||||
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
||||
"Jared Allard <jaredallard@outlook.com>",
|
||||
"Caleb Boyd <caleb.boyd@hotmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"asset-compile": "ts-node tasks/asset-compile",
|
||||
"test": "mocha",
|
||||
"fmt": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"{src,plugins,tasks}/**/*.ts\"",
|
||||
"prepublish": "npm test && npm run build",
|
||||
"prebuild": "rimraf lib && npm run fmt",
|
||||
"build": "tsc --declaration",
|
||||
"postbuild": "ts-node tasks/post-build"
|
||||
"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",
|
||||
@@ -25,7 +23,6 @@
|
||||
"lib",
|
||||
"source-map-support.js"
|
||||
],
|
||||
"typings": "lib/nexe.d.ts",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"nexe": "index.js"
|
||||
@@ -37,7 +34,7 @@
|
||||
"app-builder": "^5.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
"download": "^6.2.0",
|
||||
"fuse-box": "^2.3.4-beta.5",
|
||||
"fuse-box": "^2.2.1",
|
||||
"globby": "^6.1.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
@@ -47,20 +44,18 @@
|
||||
"uglify-js": "3.0.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.0.4",
|
||||
"@types/bluebird": "^3.5.8",
|
||||
"@types/chalk": "^0.4.31",
|
||||
"@types/execa": "^0.7.0",
|
||||
"@types/execa": "0.7.0",
|
||||
"@types/globby": "^0.6.0",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/mkdirp": "^0.3.29",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/ora": "^0.3.31",
|
||||
"@types/pify": "0.0.28",
|
||||
"@types/rimraf": "0.0.28",
|
||||
"chai": "^4.1.2",
|
||||
"execa": "^0.8.0",
|
||||
"execa": "0.8.0",
|
||||
"got": "^7.1.0",
|
||||
"mocha": "^3.5.0",
|
||||
"mocha": "^3.2.0",
|
||||
"prettier": "^1.6.1",
|
||||
"ts-node": "3.3.0",
|
||||
"typescript": "^2.4.1"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "nexe-daemon",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin for nexe to create installable services",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf *.js",
|
||||
"build": "tsc",
|
||||
"postpublish": "npm run clean"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"winsw.exe"
|
||||
],
|
||||
"author": "Caleb Boyd",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"rimraf": "^2.6.2",
|
||||
"typescript": "^2.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"nexe": "^2.0.0-rc.11"
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { NexeCompiler, NexeOptions } from 'nexe'
|
||||
import { readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
interface NexeDaemonOptions {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
executable: string
|
||||
}
|
||||
|
||||
type DaemonOptions = NexeOptions & { daemon: { windows: NexeDaemonOptions } }
|
||||
|
||||
function renderWinswConfig(options: any) {
|
||||
return (
|
||||
'<configuration>\r\n' +
|
||||
`${Object.keys(options).reduce((config: string, element: string) => {
|
||||
return (config += `<${element}>${options[element]}</${element}>\r\n`)
|
||||
}, '')}</configuration>\r\n`
|
||||
)
|
||||
}
|
||||
|
||||
export default function daemon(compiler: NexeCompiler<DaemonOptions>, next: () => Promise<void>) {
|
||||
if (compiler.target.platform !== 'windows') {
|
||||
return next()
|
||||
}
|
||||
compiler.addResource(
|
||||
'./nexe/plugin/daemon/winsw.exe',
|
||||
readFileSync(require.resolve('../winsw.exe'))
|
||||
)
|
||||
const name = compiler.options.name,
|
||||
options = (compiler.options.daemon && compiler.options.daemon.windows) || {},
|
||||
defaults: NexeDaemonOptions = {
|
||||
id: name,
|
||||
name,
|
||||
description: name,
|
||||
executable: '%BASE%\\' + compiler.output
|
||||
}
|
||||
|
||||
compiler.addResource(
|
||||
'./nexe/plugin/daemon/winsw.xml',
|
||||
Buffer.from(renderWinswConfig(Object.assign(defaults, options)))
|
||||
)
|
||||
compiler.addResource('./nexe/plugin/daemon/app.js', Buffer.from(compiler.input))
|
||||
compiler.input = '{{replace:plugins/nexe-daemon/lib/nexe-daemon.js}}'
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
const installCommands = ['-i', '--install']
|
||||
const installIndex = process.argv.findIndex(x => installCommands.includes(x))
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as cp from 'child_process'
|
||||
|
||||
function mkdirp(r: string, t?: any): any {
|
||||
;(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 (~installIndex) {
|
||||
console.log('DIRECTORY FOUND')
|
||||
const directory = process.argv[installIndex + 1]
|
||||
if (directory) {
|
||||
mkdirp(path.dirname(directory))
|
||||
const filename = path.join(directory, path.basename(process.execPath))
|
||||
const readStream = fs.createReadStream(process.execPath)
|
||||
const writeStream = fs.createWriteStream(filename)
|
||||
|
||||
const onError = function() {
|
||||
readStream.removeAllListeners()
|
||||
writeStream.removeAllListeners()
|
||||
//TODO
|
||||
}
|
||||
|
||||
readStream
|
||||
.on('error', onError)
|
||||
.pipe(writeStream)
|
||||
.on('error', onError)
|
||||
.on('close', () => {
|
||||
const winsw = filename.replace('.exe', '-service.exe')
|
||||
fs.writeFileSync(winsw, fs.readFileSync('./nexe/plugin/daemon/winsw.exe'))
|
||||
fs.writeFileSync(
|
||||
filename.replace('.exe', '-service.xml'),
|
||||
fs.readFileSync('./nexe/plugin/daemon/winsw.xml')
|
||||
)
|
||||
installService(winsw)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log('DIRECTORY NOT FOUND')
|
||||
require('./nexe/plugin/daemon/app.js')
|
||||
}
|
||||
|
||||
function installService(filename: string) {
|
||||
cp.exec(filename + ' install', error => {
|
||||
if (error && !error.message.includes('already exists')) {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"lib": [ "es2016" ],
|
||||
"outDir": "lib",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,75 @@ 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'
|
||||
@@ -36,13 +105,13 @@ export class BindingsRewrite {
|
||||
public nativeModulePaths: string[] = []
|
||||
public rewrite = false
|
||||
|
||||
isRequire(node: any, moduleName: string) {
|
||||
return node.callee.name === 'require' && node.arguments[0].value === moduleName
|
||||
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.isRequire(node, 'bindings') && parent.type === 'VariableDeclarator') {
|
||||
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
|
||||
/**
|
||||
* const loadBindings = require('bindings');
|
||||
* -> const loadBindings = String('');
|
||||
@@ -54,7 +123,7 @@ export class BindingsRewrite {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isRequire(node, 'bindings') && parent.type === 'CallExpression') {
|
||||
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
|
||||
/**
|
||||
*const bindings = require('bindings')('native-module')....
|
||||
* -> const bindings = require('./path/to/native/module.node').....
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { createHash } from 'crypto'
|
||||
import * as path from 'path'
|
||||
|
||||
export interface EmbedNodeModuleOptions {
|
||||
[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: EmbedNodeModuleOptions,
|
||||
file: { contents: string; absPath: string }
|
||||
) {
|
||||
const contents = fs.readFileSync(file.absPath)
|
||||
const modulePathParts = file.absPath.split(path.sep).reverse()
|
||||
const module = modulePathParts[modulePathParts.findIndex(x => x === 'node_modules') - 1]
|
||||
const bindingName = path.basename(file.absPath)
|
||||
const settings = options[module]
|
||||
const moduleDir = hashName(contents)
|
||||
file.contents = `
|
||||
var fs=require('fs');var path=require('path');var binding='${contents.toString(
|
||||
'base64'
|
||||
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
|
||||
|
||||
if (!settings || settings === true) {
|
||||
file.contents += `
|
||||
mkdirp('${moduleDir}');
|
||||
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
|
||||
|
||||
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
|
||||
process.dlopen(module, bindingPath)
|
||||
`.trim()
|
||||
return {}
|
||||
}
|
||||
|
||||
let depth = 0
|
||||
settings.additionalFiles.forEach(file => {
|
||||
let ownDepth = 0
|
||||
file.split('/').forEach(x => x === '..' && ownDepth++)
|
||||
depth = ownDepth > depth ? ownDepth : depth
|
||||
})
|
||||
let segments = [moduleDir]
|
||||
while (depth--) {
|
||||
segments.push(hashName(moduleDir + depth))
|
||||
}
|
||||
segments.push(bindingName)
|
||||
|
||||
file.contents += `
|
||||
var cwd = process.cwd()
|
||||
var bindingFileParts = ${JSON.stringify(segments)};
|
||||
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
|
||||
mkdirp(path.dirname(bindingFile));
|
||||
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
|
||||
${settings.additionalFiles.reduce((code, filename, i) => {
|
||||
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
|
||||
return (code += `
|
||||
var file${i} = '${contents.toString('base64')}';
|
||||
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
|
||||
mkdirp(path.dirname(filePath${i}));
|
||||
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
|
||||
`)
|
||||
}, '')};
|
||||
process.dlopen(module, bindingFile)
|
||||
`
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BindingsRewrite } from './bindings-rewrite'
|
||||
import { embedDotNode, EmbedNodeModuleOptions } from './embed-node'
|
||||
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
|
||||
|
||||
export interface FuseBoxFile {
|
||||
info: { absPath: string }
|
||||
@@ -9,7 +8,6 @@ export interface FuseBoxFile {
|
||||
dependencies: string[]
|
||||
requiresRegeneration: boolean
|
||||
}
|
||||
consume(): void
|
||||
loadContents(): void
|
||||
makeAnalysis(
|
||||
parsingOptions?: any,
|
||||
@@ -22,16 +20,19 @@ export interface FuseBoxFile {
|
||||
): void
|
||||
}
|
||||
|
||||
export default function(options: EmbedNodeModuleOptions = {}) {
|
||||
export default function(options: ExtractNodeModuleOptions) {
|
||||
return new NativeModulePlugin(options)
|
||||
}
|
||||
|
||||
export class NativeModulePlugin {
|
||||
public test = /node_modules.*(\.js|\.node)$|\.node$/
|
||||
public test: RegExp
|
||||
public limit2Project = false
|
||||
private modules: (keyof EmbedNodeModuleOptions)[]
|
||||
private modules: (keyof ExtractNodeModuleOptions)[]
|
||||
|
||||
constructor(public options = {}) {}
|
||||
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')
|
||||
@@ -46,7 +47,6 @@ export class NativeModulePlugin {
|
||||
}
|
||||
|
||||
const bindingsRewrite = new BindingsRewrite()
|
||||
|
||||
file.makeAnalysis(null, {
|
||||
plugins: [
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
+70
-123
@@ -4,13 +4,11 @@ import { createHash } from 'crypto'
|
||||
import { createReadStream } from 'fs'
|
||||
import { Readable } from 'stream'
|
||||
import { spawn } from 'child_process'
|
||||
import { Logger, LogStep } from './logger'
|
||||
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util'
|
||||
import { NexeOptions, version } from './options'
|
||||
import { Logger } from './logger'
|
||||
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
|
||||
import { NexeOptions, nexeVersion } from './options'
|
||||
import { NexeTarget } from './target'
|
||||
import download = require('download')
|
||||
import { getLatestGitRelease } from './releases'
|
||||
import { IncomingMessage } from 'http'
|
||||
import { getLatestGitRelease, storeAsset } from './releases'
|
||||
|
||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||
@@ -22,40 +20,38 @@ export interface NexeFile {
|
||||
contents: string
|
||||
}
|
||||
|
||||
export { NexeOptions }
|
||||
|
||||
interface NexeHeader {
|
||||
resources: { [key: string]: number[] }
|
||||
version: string
|
||||
}
|
||||
|
||||
export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
export class NexeCompiler {
|
||||
private start = Date.now()
|
||||
private env = { ...process.env }
|
||||
private compileStep: LogStep
|
||||
private compileStep: { modify: Function; log: Function }
|
||||
public log = new Logger(this.options.loglevel)
|
||||
public src: string
|
||||
public src = join(this.options.temp, this.options.version)
|
||||
public files: NexeFile[] = []
|
||||
public shims: string[] = []
|
||||
public input: string
|
||||
public bundledInput?: string
|
||||
public targets: NexeTarget[]
|
||||
public target: NexeTarget
|
||||
public resources: { bundle: Buffer; index: { [key: string]: number[] } } = {
|
||||
public output: string | null
|
||||
|
||||
public resources: { bundle: string; index: { [key: string]: number[] } } = {
|
||||
index: {},
|
||||
bundle: Buffer.from('')
|
||||
bundle: ''
|
||||
}
|
||||
public output = this.options.output
|
||||
private nodeSrcBinPath: string
|
||||
constructor(public options: T) {
|
||||
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.targets = options.targets as NexeTarget[]
|
||||
this.target = this.targets[0]
|
||||
this.src = join(this.options.temp, this.target.version)
|
||||
this.nodeSrcBinPath = isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
this.log.step('nexe ' + version, 'info')
|
||||
this.log.step('nexe ' + nexeVersion, 'info')
|
||||
if (python) {
|
||||
if (isWindows) {
|
||||
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
|
||||
@@ -63,65 +59,41 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
this.env.PYTHON = python
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bound
|
||||
addResource(file: string, contents: Buffer) {
|
||||
const { resources } = this
|
||||
resources.index[file] = [resources.bundle.byteLength, contents.byteLength]
|
||||
resources.bundle = Buffer.concat([resources.bundle, contents])
|
||||
}
|
||||
|
||||
@bound
|
||||
async readFileAsync(file: string) {
|
||||
this.assertBuild()
|
||||
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
|
||||
if (!cachedFile) {
|
||||
const absPath = join(this.src, file)
|
||||
cachedFile = {
|
||||
absPath,
|
||||
filename: file,
|
||||
contents: await readFileAsync(absPath, 'utf-8').catch(x => {
|
||||
if (x.code === 'ENOENT') return ''
|
||||
throw x
|
||||
})
|
||||
this.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)
|
||||
}
|
||||
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
|
||||
}
|
||||
return cachedFile
|
||||
}
|
||||
|
||||
@bound
|
||||
writeFileAsync(file: string, contents: string | Buffer) {
|
||||
this.assertBuild()
|
||||
return writeFileAsync(join(this.src, file), contents)
|
||||
}
|
||||
|
||||
@bound
|
||||
async replaceInFileAsync(file: string, replace: string | RegExp, value: string) {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = entry.contents.replace(replace, value)
|
||||
}
|
||||
|
||||
@bound
|
||||
async setFileContentsAsync(file: string, contents: string) {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = contents
|
||||
}
|
||||
|
||||
quit() {
|
||||
const time = Date.now() - this.start
|
||||
this.log.write(`Finished in ${time / 1000}s`)
|
||||
this.log.write(`Finsihed in ${time / 1000}s`)
|
||||
return this.log.flush()
|
||||
}
|
||||
|
||||
assertBuild() {
|
||||
if (!this.options.build) {
|
||||
throw new Error('This feature is only available with `--build`')
|
||||
}
|
||||
}
|
||||
|
||||
public getNodeExecutableLocation(target?: NexeTarget) {
|
||||
private _getNodeExecutableLocation(target?: NexeTarget) {
|
||||
if (target) {
|
||||
return join(this.options.temp, target.toString())
|
||||
}
|
||||
@@ -129,31 +101,14 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
}
|
||||
|
||||
private _runBuildCommandAsync(command: string, args: string[]) {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep.pause()
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
spawn(command, args, {
|
||||
cwd: this.src,
|
||||
env: this.env,
|
||||
stdio: this.log.verbose ? 'inherit' : 'ignore'
|
||||
stdio: 'ignore'
|
||||
})
|
||||
.once('error', (e: Error) => {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep.resume()
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
.once('close', (code: number) => {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep.resume()
|
||||
}
|
||||
if (code != 0) {
|
||||
const error = `${command} ${args.join(' ')} exited with code: ${code}`
|
||||
reject(new Error(error))
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
.once('error', reject)
|
||||
.once('close', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -176,38 +131,23 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
|
||||
)
|
||||
await this._runBuildCommandAsync(make, buildOptions)
|
||||
return createReadStream(this.getNodeExecutableLocation())
|
||||
return createReadStream(this._getNodeExecutableLocation())
|
||||
}
|
||||
|
||||
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
|
||||
const downloadOptions: any = {
|
||||
headers: {
|
||||
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
|
||||
}
|
||||
}
|
||||
const githubRelease = await getLatestGitRelease(downloadOptions)
|
||||
const 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 it using the --build flag`)
|
||||
throw new Error(`${assetName} not available, create one using --build`)
|
||||
}
|
||||
const filename = this.getNodeExecutableLocation(target)
|
||||
await download(asset.browser_download_url, dirname(filename), downloadOptions).on(
|
||||
'response',
|
||||
(res: IncomingMessage) => {
|
||||
const total = +res.headers['content-length']!
|
||||
let current = 0
|
||||
res.on('data', data => {
|
||||
current += data.length
|
||||
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
|
||||
})
|
||||
}
|
||||
)
|
||||
const filename = this._getNodeExecutableLocation(target)
|
||||
await storeAsset(asset, dirname(filename))
|
||||
return createReadStream(filename)
|
||||
}
|
||||
|
||||
getHeader() {
|
||||
private _generateHeader() {
|
||||
const version =
|
||||
['configure', 'vcBuild', 'make'].reduce((a, c) => {
|
||||
return (a += (this.options as any)[c]
|
||||
@@ -221,26 +161,33 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
.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(build ? undefined : target)
|
||||
const location = this._getNodeExecutableLocation(target)
|
||||
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
|
||||
if (!build && !binary) {
|
||||
step.modify('Fetching prebuilt binary')
|
||||
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(binary)
|
||||
return this._assembleDeliverable(header, binary)
|
||||
}
|
||||
|
||||
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
|
||||
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
|
||||
if (this.options.empty) {
|
||||
return binary
|
||||
}
|
||||
@@ -249,12 +196,12 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
|
||||
artifact.push(chunk)
|
||||
})
|
||||
binary.on('close', () => {
|
||||
const content = [this.shims.join(''), this.input].join(';')
|
||||
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(this.resources.bundle.byteLength, 8)
|
||||
lengths.writeDoubleLE(Buffer.byteLength(this.resources.bundle), 8)
|
||||
artifact.push(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
|
||||
artifact.push(null)
|
||||
})
|
||||
|
||||
+3
-7
@@ -6,12 +6,10 @@ const frameLength = 120
|
||||
export interface LogStep {
|
||||
modify(text: string, color?: string): void
|
||||
log(text: string, color?: string): void
|
||||
pause(): void
|
||||
resume(): void
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
public verbose: boolean
|
||||
private verbose: boolean
|
||||
private silent: boolean
|
||||
private ora: any
|
||||
private modify: Function
|
||||
@@ -50,7 +48,7 @@ export class Logger {
|
||||
|
||||
step(text: string, method: string = 'succeed'): LogStep {
|
||||
if (this.silent) {
|
||||
return { modify() {}, log() {}, pause() {}, resume() {} }
|
||||
return { modify() {}, log() {} }
|
||||
}
|
||||
if (!this.ora.id) {
|
||||
this.ora.start().text = text
|
||||
@@ -64,9 +62,7 @@ export class Logger {
|
||||
|
||||
return {
|
||||
modify: this.modify,
|
||||
log: this.verbose ? this.write : this.modify,
|
||||
pause: () => this.ora.stopAndPersist(),
|
||||
resume: () => this.ora.start()
|
||||
log: this.verbose ? this.write : this.modify
|
||||
} as LogStep
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,40 +1,40 @@
|
||||
import { compose, Middleware } from 'app-builder'
|
||||
import { compose, PromiseConfig, Middleware } from 'app-builder'
|
||||
import resource from './steps/resource'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import { argv, version, help, normalizeOptions, NexeOptions, NexePatch } from './options'
|
||||
import {
|
||||
argv,
|
||||
nexeVersion as version,
|
||||
normalizeOptionsAsync,
|
||||
NexeOptions,
|
||||
NexePatch
|
||||
} from './options'
|
||||
import cli from './steps/cli'
|
||||
import bundle from './steps/bundle'
|
||||
import bundle from './bundling/fuse'
|
||||
import download from './steps/download'
|
||||
import shim from './steps/shim'
|
||||
import artifacts from './steps/artifacts'
|
||||
import patches from './patches'
|
||||
import { rimrafAsync } from './util'
|
||||
import { NexeTarget } from './target'
|
||||
|
||||
async function compile(
|
||||
compilerOptions?: Partial<NexeOptions>,
|
||||
callback?: (err: Error | null) => void
|
||||
) {
|
||||
const options = normalizeOptions(compilerOptions)
|
||||
const options = await normalizeOptionsAsync(compilerOptions)
|
||||
const compiler = new NexeCompiler(options)
|
||||
const build = compiler.options.build
|
||||
|
||||
if (options.clean) {
|
||||
let path = compiler.src
|
||||
if (!options.build) {
|
||||
path = compiler.getNodeExecutableLocation(compiler.options.targets[0] as NexeTarget)
|
||||
}
|
||||
const step = compiler.log.step('Cleaning up nexe build artifacts...')
|
||||
step.log(`Deleting contents at: ${path}`)
|
||||
await rimrafAsync(path)
|
||||
step.log(`Deleted contents at: ${path}`)
|
||||
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, shim, options.plugins as NexePatch[])
|
||||
const nexe = compose(resource, bundle, cli, buildSteps)
|
||||
return callback
|
||||
? void nexe(compiler).then(
|
||||
() => callback && callback(null),
|
||||
@@ -46,4 +46,4 @@ async function compile(
|
||||
: nexe(compiler)
|
||||
}
|
||||
|
||||
export { argv, compile, version, NexeCompiler, NexeOptions, help }
|
||||
export { argv, compile, version }
|
||||
|
||||
+83
-110
@@ -1,12 +1,11 @@
|
||||
import * as parseArgv from 'minimist'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import { isWindows, padRight } from './util'
|
||||
import { basename, extname, join, isAbsolute, relative, dirname, resolve } from 'path'
|
||||
import { isWindows } from './util'
|
||||
import { basename, extname, join, isAbsolute, relative } from 'path'
|
||||
import { getTarget, NexeTarget } from './target'
|
||||
import { EOL, homedir } from 'os'
|
||||
import * as c from 'chalk'
|
||||
import { EOL } from 'os'
|
||||
|
||||
export const version = '{{replace:0}}'
|
||||
export const nexeVersion = '2.0.0-rc.1'
|
||||
|
||||
export interface NexePatch {
|
||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
||||
@@ -19,6 +18,7 @@ export interface NexeOptions {
|
||||
targets: (string | NexeTarget)[]
|
||||
name: string
|
||||
cwd: string
|
||||
version: string
|
||||
flags: string[]
|
||||
configure: string[]
|
||||
vcBuild: string[]
|
||||
@@ -30,19 +30,14 @@ export interface NexeOptions {
|
||||
enableNodeCli: boolean
|
||||
bundle: boolean | string
|
||||
patches: (string | NexePatch)[]
|
||||
plugins: (string | NexePatch)[]
|
||||
native: any
|
||||
empty: boolean
|
||||
sourceUrl?: string
|
||||
enableStdIn?: boolean
|
||||
python?: string
|
||||
loglevel: 'info' | 'silent' | 'verbose'
|
||||
silent?: boolean
|
||||
fakeArgv?: boolean
|
||||
verbose?: boolean
|
||||
info?: boolean
|
||||
ico?: string
|
||||
debugBundle?: boolean
|
||||
warmup?: string
|
||||
compress?: boolean
|
||||
clean?: boolean
|
||||
@@ -52,7 +47,12 @@ export interface NexeOptions {
|
||||
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: [],
|
||||
@@ -63,67 +63,56 @@ const defaults = {
|
||||
compress: false,
|
||||
build: false,
|
||||
bundle: true,
|
||||
patches: [],
|
||||
plugins: []
|
||||
patches: []
|
||||
}
|
||||
const alias = {
|
||||
i: 'input',
|
||||
o: 'output',
|
||||
v: 'version',
|
||||
t: 'target',
|
||||
b: 'build',
|
||||
n: 'name',
|
||||
v: 'version',
|
||||
r: 'resource',
|
||||
a: 'resource',
|
||||
p: 'python',
|
||||
f: 'flag',
|
||||
c: 'configure',
|
||||
m: 'make',
|
||||
s: 'snapshot',
|
||||
h: 'help',
|
||||
l: 'loglevel',
|
||||
'fake-argv': 'fakeArgv'
|
||||
l: 'loglevel'
|
||||
}
|
||||
const argv = parseArgv(process.argv, { alias, default: { ...defaults, enableStdIn: true } })
|
||||
const g = c.gray
|
||||
let help = `
|
||||
${c.bold('nexe <entry-file> [options]')}
|
||||
const argv = parseArgv(process.argv, { alias, default: defaults })
|
||||
const help =
|
||||
`
|
||||
nexe --help CLI OPTIONS
|
||||
|
||||
${c.underline.bold('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
|
||||
|
||||
-i --input -- application entry point
|
||||
-o --output -- path to output file
|
||||
-t --target -- node version description
|
||||
-n --name -- main app module name
|
||||
-r --resource -- *embed files (glob) within the binary
|
||||
--plugin -- extend nexe runtime behavior
|
||||
-* variable key name * option can be used more than once`.trim() + EOL
|
||||
|
||||
${c.underline.bold('Building from source:')}
|
||||
|
||||
-b --build -- build from source
|
||||
-p --python -- python2 (as python) executable path
|
||||
-f --flag -- *v8 flags to include during compilation
|
||||
-c --configure -- *arguments to the configure step
|
||||
-m --make -- *arguments to the make/build step
|
||||
--snapshot -- path to a warmup snapshot
|
||||
--ico -- file name for alternate icon file (windows)
|
||||
--rc-* -- populate rc file options (windows)
|
||||
--sourceUrl -- pass an alternate source (node.tar.gz) url
|
||||
--enableNodeCli -- enable node cli enforcement (blocks app cli)
|
||||
|
||||
${c.underline.bold('Other options:')}
|
||||
|
||||
--bundle -- custom bundling module with 'createBundle' export
|
||||
--temp -- temp file storage default '~/.nexe'
|
||||
--cwd -- set the current working directory for the command
|
||||
--fake-argv -- fake argv[1] with entry file
|
||||
--clean -- force download of sources
|
||||
--silent -- disable logging
|
||||
--verbose -- set logging to verbose
|
||||
|
||||
-* variable key name * option can be used more than once`.trim()
|
||||
help = EOL + help + EOL
|
||||
|
||||
function flatten(...args: any[]): string[] {
|
||||
function flattenFilter(...args: any[]): string[] {
|
||||
return ([] as string[]).concat(...args).filter(x => x)
|
||||
}
|
||||
|
||||
@@ -151,7 +140,7 @@ function tryResolveMainFileName(cwd: string) {
|
||||
filename = basename(file).replace(extname(file), '')
|
||||
} catch (_) {}
|
||||
|
||||
return filename ? filename : 'nexe_' + Date.now()
|
||||
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
|
||||
}
|
||||
|
||||
function extractLogLevel(options: NexeOptions) {
|
||||
@@ -161,100 +150,84 @@ function extractLogLevel(options: NexeOptions) {
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function isName(name: string) {
|
||||
return name && name !== 'index'
|
||||
}
|
||||
|
||||
function extractName(options: NexeOptions) {
|
||||
let name = options.name
|
||||
if (!isName(name) && typeof options.input === 'string') {
|
||||
if (!name && typeof options.input === 'string') {
|
||||
name = basename(options.input).replace(extname(options.input), '')
|
||||
}
|
||||
|
||||
if (!isName(name)) {
|
||||
name = tryResolveMainFileName(options.cwd)
|
||||
}
|
||||
|
||||
if (!isName(name) && basename(options.cwd)) {
|
||||
name = basename(options.cwd)
|
||||
}
|
||||
|
||||
name = name || tryResolveMainFileName(options.cwd)
|
||||
return name.replace(/\.exe$/, '')
|
||||
}
|
||||
|
||||
function isEntryFile(filename?: string): filename is string {
|
||||
return Boolean(
|
||||
filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
|
||||
)
|
||||
function isEntryFile(filename: string) {
|
||||
return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
|
||||
}
|
||||
|
||||
export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
|
||||
function findInput(input: string, cwd: string) {
|
||||
const maybeInput = argv._.slice().pop() || ''
|
||||
if (input) {
|
||||
return resolve(cwd, input)
|
||||
return input
|
||||
}
|
||||
if (isEntryFile(maybeEntry)) {
|
||||
return resolve(cwd, maybeEntry)
|
||||
}
|
||||
if (!process.stdin.isTTY) {
|
||||
return ''
|
||||
if (isEntryFile(maybeInput)) {
|
||||
return maybeInput
|
||||
}
|
||||
try {
|
||||
return require.resolve(cwd)
|
||||
const main = require.resolve(cwd)
|
||||
return './' + relative(cwd, main)
|
||||
} catch (e) {
|
||||
throw new Error('No entry file found')
|
||||
void e
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
|
||||
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
|
||||
const cwd = (options.cwd = resolve(options.cwd))
|
||||
options.temp = options.temp
|
||||
? resolve(cwd, options.temp)
|
||||
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
|
||||
const maybeEntry = input === argv ? argv._[argv._.length - 1] : undefined
|
||||
options.input = resolveEntry(options.input, cwd, maybeEntry)
|
||||
|
||||
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 = flatten(opts.flag, options.flags)
|
||||
options.targets = flatten(opts.target, options.targets).map(getTarget)
|
||||
options.make = flatten(options.vcBuild, options.make)
|
||||
options.configure = flatten(options.configure)
|
||||
options.resources = flatten(opts.resource, options.resources)
|
||||
options.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.output = isWindows
|
||||
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
|
||||
: `${options.output || options.name}`
|
||||
options.output = resolve(cwd, options.output)
|
||||
|
||||
if (!options.targets.length) {
|
||||
options.targets.push(getTarget())
|
||||
}
|
||||
options.targets = options.targets.map(getTarget)
|
||||
|
||||
if (options.build) {
|
||||
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)))
|
||||
options.make = Array.from(new Set(options.make.concat([arch])))
|
||||
} else {
|
||||
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
|
||||
}
|
||||
}
|
||||
|
||||
const requireDefault = (x: string) => {
|
||||
if (!options.targets.length) {
|
||||
options.targets = [getTarget(options.version)]
|
||||
}
|
||||
|
||||
options.patches = options.patches.map(x => {
|
||||
if (typeof x === 'string') {
|
||||
return require(x).default
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
|
||||
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
|
||||
})
|
||||
|
||||
Object.keys(alias)
|
||||
.filter(k => k !== 'rc')
|
||||
.forEach(x => delete opts[x])
|
||||
|
||||
return options
|
||||
return Promise.resolve(options)
|
||||
}
|
||||
|
||||
export { argv, normalizeOptions, help }
|
||||
export { argv, normalizeOptionsAsync }
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const fd = fs.openSync(process.execPath, 'r')
|
||||
const stat = fs.statSync(process.execPath)
|
||||
const footer = Buffer.from(Array(32))
|
||||
|
||||
fs.readSync(fd, footer, 0, 32, stat.size - 32)
|
||||
|
||||
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
|
||||
throw 'Invalid Nexe binary'
|
||||
}
|
||||
|
||||
const contentSize = footer.readDoubleLE(16)
|
||||
const resourceSize = footer.readDoubleLE(24)
|
||||
const contentStart = stat.size - 32 - resourceSize - contentSize
|
||||
const resourceStart = contentStart + contentSize
|
||||
|
||||
Object.defineProperty(
|
||||
process,
|
||||
'__nexe',
|
||||
(function() {
|
||||
let nexeHeader: any = null
|
||||
return {
|
||||
get: function() {
|
||||
return nexeHeader
|
||||
},
|
||||
set: function(value: any) {
|
||||
if (nexeHeader) {
|
||||
throw new Error('__nexe cannot be reconfigured')
|
||||
}
|
||||
nexeHeader = Object.assign({}, value, {
|
||||
layout: {
|
||||
stat,
|
||||
contentSize,
|
||||
contentStart,
|
||||
resourceSize,
|
||||
resourceStart
|
||||
}
|
||||
})
|
||||
Object.freeze(nexeHeader)
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
}
|
||||
})()
|
||||
)
|
||||
|
||||
const contentBuffer = Buffer.from(Array(contentSize))
|
||||
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart)
|
||||
fs.closeSync(fd)
|
||||
const Module = require('module')
|
||||
process.mainModule = new Module(process.execPath, null)
|
||||
process.mainModule!.loaded = true
|
||||
;(process.mainModule as any)._compile(contentBuffer.toString(), process.execPath)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
if (!compiler.target.version.startsWith('8.2')) {
|
||||
if (!compiler.options.version.startsWith('8.2')) {
|
||||
return next()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import cli from './disable-node-cli'
|
||||
import flags from './flags'
|
||||
import ico from './ico'
|
||||
import rc from './node-rc'
|
||||
import { NexeCompiler, NexeOptions } from '../compiler'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
const patches = [gyp, nexePatches, buildFixes, cli, flags, ico, rc]
|
||||
|
||||
|
||||
@@ -12,15 +12,7 @@ export default async function nodeRc(compiler: NexeCompiler, next: () => Promise
|
||||
let value = options[key]
|
||||
const isVar = /^[A-Z_]+$/.test(value)
|
||||
value = isVar ? value : `"${value}"`
|
||||
file.contents = file.contents.replace(
|
||||
new RegExp(`VALUE "${key}",*`),
|
||||
`VALUE "${key}", ${value}`
|
||||
)
|
||||
})
|
||||
;['PRODUCTVERSION', 'FILEVERSION'].forEach(x => {
|
||||
if (options[x]) {
|
||||
file.contents = file.contents.replace(new RegExp(x + ' .*$', 'm'), `${x} ${options[x]}`)
|
||||
}
|
||||
file.contents.replace(new RegExp(`VALUE "${key}",*`), `VALUE "${key}", ${value}`)
|
||||
})
|
||||
|
||||
return next()
|
||||
|
||||
@@ -5,7 +5,122 @@ import { join } from 'path'
|
||||
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await compiler.setFileContentsAsync(
|
||||
'lib/_third_party_main.js',
|
||||
'{{replace:lib/patches/boot-nexe.js}}'
|
||||
`
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ 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'
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { readFileAsync, writeFileAsync } from '../util'
|
||||
import { resolve, relative } from 'path'
|
||||
import NativeModulePlugin from '../bundling/fuse-native-module-plugin'
|
||||
import { NexeOptions } from '../options'
|
||||
|
||||
function createBundle(options: NexeOptions) {
|
||||
const { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } = require('fuse-box')
|
||||
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin(), NativeModulePlugin(options.native)]
|
||||
if (options.compress) {
|
||||
plugins.push(
|
||||
QuantumPlugin({
|
||||
target: 'server',
|
||||
uglify: true,
|
||||
bakeApiIntoBundle: options.name
|
||||
})
|
||||
)
|
||||
}
|
||||
const cwd = options.cwd
|
||||
const fuse = FuseBox.init({
|
||||
cache: false,
|
||||
log: Boolean(process.env.NEXE_BUNDLE_LOG) || false,
|
||||
homeDir: cwd,
|
||||
sourceMaps: false,
|
||||
writeBundles: false,
|
||||
output: '$name.js',
|
||||
target: 'server',
|
||||
plugins
|
||||
})
|
||||
const input = relative(cwd, resolve(cwd, options.input)).replace(/\\/g, '/')
|
||||
fuse.bundle(options.name).instructions(`> ${input}`)
|
||||
return fuse.run().then((x: any) => {
|
||||
let output = ''
|
||||
x.bundles.forEach((y: any) => (output = y.context.output.lastPrimaryOutput.content.toString()))
|
||||
return output
|
||||
})
|
||||
}
|
||||
|
||||
export default async function bundle(compiler: NexeCompiler, next: any) {
|
||||
const { bundle, cwd, empty, input } = compiler.options
|
||||
if (!bundle) {
|
||||
compiler.input = await readFileAsync(resolve(cwd, input), 'utf-8')
|
||||
return next()
|
||||
}
|
||||
|
||||
if (compiler.options.empty || !compiler.options.input) {
|
||||
compiler.input = ''
|
||||
return next()
|
||||
}
|
||||
|
||||
let producer = createBundle
|
||||
if (typeof compiler.options.bundle === 'string') {
|
||||
producer = require(resolve(cwd, bundle)).createBundle
|
||||
}
|
||||
|
||||
compiler.input = await producer(compiler.options)
|
||||
|
||||
if ('string' === typeof compiler.options.debugBundle) {
|
||||
await writeFileAsync(resolve(cwd, compiler.options.debugBundle), compiler.input)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
+22
-28
@@ -1,22 +1,19 @@
|
||||
import { normalize, relative } from 'path'
|
||||
import { normalize } from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import { createWriteStream, chmodSync, statSync } from 'fs'
|
||||
import { createWriteStream, chmodSync } from 'fs'
|
||||
import { readFileAsync, dequote, isWindows } from '../util'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeTarget } from '../target'
|
||||
|
||||
function getStdIn(stdin: NodeJS.ReadStream): Promise<string> {
|
||||
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
|
||||
return new Promise(resolve => {
|
||||
let out = ''
|
||||
stdin
|
||||
.setEncoding('utf8')
|
||||
.on('readable', () => {
|
||||
let current
|
||||
while ((current = stdin.read())) {
|
||||
out += current
|
||||
}
|
||||
})
|
||||
.on('end', () => resolve(out))
|
||||
let input = ''
|
||||
stream.setEncoding('utf-8')
|
||||
stream.on('data', (x: string) => {
|
||||
input += x
|
||||
})
|
||||
stream.once('end', () => resolve(dequote(input)))
|
||||
stream.resume && stream.resume()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,15 +32,20 @@ function getStdIn(stdin: NodeJS.ReadStream): Promise<string> {
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { log } = compiler
|
||||
let stdInUsed = false
|
||||
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
|
||||
stdInUsed = true
|
||||
compiler.input = dequote(await getStdIn(process.stdin))
|
||||
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)
|
||||
|
||||
@@ -56,16 +58,8 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else if (compiler.output) {
|
||||
const output = compiler.output
|
||||
const mode = statSync(output).mode | 0o111
|
||||
chmodSync(output, mode.toString(8).slice(-3))
|
||||
const inputFile = relative(process.cwd(), compiler.options.input)
|
||||
const outputFile = relative(process.cwd(), output)
|
||||
step.log(
|
||||
`Entry: '${stdInUsed
|
||||
? compiler.options.empty ? '[empty]' : '[stdin]'
|
||||
: inputFile}' written to: ${outputFile}`
|
||||
)
|
||||
chmodSync(compiler.output, '755')
|
||||
step.log(`Executable written to: ${compiler.output}`)
|
||||
resolve(compiler.quit())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import { pathExistsAsync } from '../util'
|
||||
import { LogStep } from '../logger'
|
||||
import { IncomingMessage } from 'http'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeTarget } from '../target'
|
||||
|
||||
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
|
||||
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
|
||||
@@ -28,8 +27,8 @@ function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { src, log, targets: [{ version }] } = compiler
|
||||
const { sourceUrl, downloadOptions } = compiler.options
|
||||
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)) {
|
||||
|
||||
@@ -18,8 +18,15 @@ export default async function resource(compiler: NexeCompiler, next: () => Promi
|
||||
count++
|
||||
step.log(`Including file: ${file}`)
|
||||
const contents = await readFileAsync(file)
|
||||
compiler.addResource(file, contents)
|
||||
const commentSafeContents = contents.toString('base64')
|
||||
resources.index[file] = [
|
||||
Buffer.byteLength(resources.bundle),
|
||||
Buffer.byteLength(commentSafeContents)
|
||||
]
|
||||
resources.bundle += commentSafeContents
|
||||
})
|
||||
step.log(`Included ${count} file(s). ${(resources.bundle.byteLength / 1e6).toFixed(3)} MB`)
|
||||
step.log(
|
||||
`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`
|
||||
)
|
||||
return next()
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
import { Stats } from 'fs'
|
||||
import { ok } from 'assert'
|
||||
import { dirname, resolve, normalize, basename } from 'path'
|
||||
|
||||
const binary = (process as any).__nexe as NexeBinary
|
||||
ok(binary)
|
||||
const manifest = binary.resources
|
||||
const directories: { [key: string]: { [key: string]: boolean } } = {}
|
||||
const isString = (x: any): x is string => typeof x === 'string' || x instanceof String
|
||||
const isNotFile = () => false
|
||||
const isNotDirectory = isNotFile
|
||||
const isFile = () => true
|
||||
const isDirectory = isFile
|
||||
|
||||
if (Object.keys(manifest).length) {
|
||||
const fs = require('fs')
|
||||
const originalReadFile = fs.readFile
|
||||
const originalReadFileSync = fs.readFileSync
|
||||
const originalReaddir = fs.readdir
|
||||
const originalReaddirSync = fs.readdirSync
|
||||
const originalStatSync = fs.statSync
|
||||
const originalStat = fs.stat
|
||||
const resourceStart = binary.layout.resourceStart
|
||||
|
||||
const statTime = function() {
|
||||
const stat = binary.layout.stat
|
||||
return {
|
||||
dev: 0,
|
||||
ino: 0,
|
||||
nlink: 0,
|
||||
rdev: 0,
|
||||
uid: 123,
|
||||
gid: 500,
|
||||
blksize: 4096,
|
||||
blocks: 0,
|
||||
atime: new Date(stat.atime),
|
||||
atimeMs: stat.atime.getTime(),
|
||||
mtime: new Date(stat.mtime),
|
||||
mtimeMs: stat.mtime.getTime(),
|
||||
ctime: new Date(stat.ctime),
|
||||
ctimMs: stat.ctime.getTime(),
|
||||
birthtime: new Date(stat.birthtime),
|
||||
birthtimeMs: stat.birthtime.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
const createStat = function(directoryExtensions: any, fileExtensions?: any) {
|
||||
if (!fileExtensions) {
|
||||
return Object.assign({}, binary.layout.stat, directoryExtensions, { size: 0 }, statTime())
|
||||
}
|
||||
const size = directoryExtensions[1]
|
||||
return Object.assign({}, binary.layout.stat, fileExtensions, { size }, statTime())
|
||||
}
|
||||
|
||||
const ownStat = function(path: string | Buffer) {
|
||||
const key = resolve(path)
|
||||
if (directories[key]) {
|
||||
return createStat({ isDirectory, isFile: isNotFile })
|
||||
}
|
||||
if (manifest[key]) {
|
||||
return createStat(manifest[key], { isFile, isDirectory: isNotDirectory })
|
||||
}
|
||||
}
|
||||
|
||||
let setupManifest = () => {
|
||||
Object.keys(manifest).forEach(key => {
|
||||
const absolutePath = resolve(key)
|
||||
const dirPath = dirname(absolutePath)
|
||||
directories[dirPath] = directories[dirPath] || {}
|
||||
directories[dirPath][basename(absolutePath)] = true
|
||||
if (!manifest[absolutePath]) {
|
||||
manifest[absolutePath] = manifest[key]
|
||||
}
|
||||
const normalizedPath = normalize(key)
|
||||
if (!manifest[normalizedPath]) {
|
||||
manifest[normalizedPath] = manifest[key]
|
||||
}
|
||||
})
|
||||
setupManifest = () => {}
|
||||
}
|
||||
//naive patches intended to work for most use cases
|
||||
var nfs = {
|
||||
readdir: function readdir(path: string | Buffer, options: any, callback: any) {
|
||||
setupManifest()
|
||||
path = path.toString()
|
||||
if ('function' === typeof options) {
|
||||
callback = options
|
||||
options = { encoding: 'utf8' }
|
||||
}
|
||||
const dir = directories[resolve(path)]
|
||||
if (dir) {
|
||||
process.nextTick(() => {
|
||||
callback(null, Object.keys(dir))
|
||||
})
|
||||
} else {
|
||||
return originalReaddir.apply(fs, arguments)
|
||||
}
|
||||
},
|
||||
|
||||
readdirSync: function readdirSync(path: string | Buffer, options: any) {
|
||||
setupManifest()
|
||||
path = path.toString()
|
||||
const dir = directories[resolve(path)]
|
||||
if (dir) {
|
||||
return Object.keys(dir)
|
||||
}
|
||||
return originalReaddirSync.apply(fs, arguments)
|
||||
},
|
||||
|
||||
readFile: function readFile(file: any, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[file]
|
||||
if (!entry || !isString(file)) {
|
||||
return originalReadFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = isString(options) ? options : null
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
fs.open(process.execPath, 'r', function(err: Error, fd: number) {
|
||||
if (err) return callback(err, null)
|
||||
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function(
|
||||
error: Error,
|
||||
bytesRead: number,
|
||||
result: Buffer
|
||||
) {
|
||||
if (error) {
|
||||
return fs.close(fd, function() {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
fs.close(fd, function(err: Error) {
|
||||
if (err) {
|
||||
return callback(err, result)
|
||||
}
|
||||
callback(err, encoding ? result.toString(encoding) : result)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
readFileSync: function readFileSync(file: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[file]
|
||||
if (!entry || !isString(file)) {
|
||||
return originalReadFileSync.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = isString(options) ? options : null
|
||||
const fd = fs.openSync(process.execPath, 'r')
|
||||
const result = Buffer.alloc(length)
|
||||
fs.readSync(fd, result, 0, length, resourceOffset)
|
||||
fs.closeSync(fd)
|
||||
return encoding ? result.toString(encoding) : result
|
||||
},
|
||||
statSync: function statSync(path: string | Buffer) {
|
||||
const stat = ownStat(path)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalStatSync.apply(fs, arguments)
|
||||
},
|
||||
stat: function stat(path: string | Buffer, callback: any) {
|
||||
const stat = ownStat(path)
|
||||
if (stat) {
|
||||
process.nextTick(() => {
|
||||
callback(null, stat)
|
||||
})
|
||||
} else {
|
||||
return originalStat.apply(fs, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(fs, nfs)
|
||||
}
|
||||
|
||||
interface NexeBinary {
|
||||
resources: { [key: string]: number[] }
|
||||
version: string
|
||||
layout: {
|
||||
stat: Stats
|
||||
contentSize: number
|
||||
contentStart: number
|
||||
resourceSize: number
|
||||
resourceStart: number
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
function wrap(code: string) {
|
||||
return '!(function () {' + code + '})();'
|
||||
}
|
||||
|
||||
export default function(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
compiler.shims.push(wrap(compiler.getHeader()))
|
||||
|
||||
compiler.shims.push(
|
||||
wrap(`
|
||||
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
|
||||
const cluster = require('cluster')
|
||||
cluster._setupWorker()
|
||||
delete process.env.NODE_UNIQUE_ID
|
||||
}
|
||||
`)
|
||||
)
|
||||
|
||||
if (compiler.options.resources.length) {
|
||||
compiler.shims.push(wrap('{{replace:lib/steps/shim-fs.js}}'))
|
||||
}
|
||||
|
||||
//compiler.shims.push(wrap('{/{replace:lib/steps/shim-require.js}}'))
|
||||
|
||||
if (compiler.options.fakeArgv) {
|
||||
const nty = !process.stdin.isTTY
|
||||
const input = nty ? '[stdin]' : compiler.options.input
|
||||
compiler.shims.push(
|
||||
wrap(`
|
||||
var r = require('path').resolve;
|
||||
process.argv.splice(1,0, ${nty ? `'${input}'` : `r("${input}")`});`)
|
||||
)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
+6
-9
@@ -8,15 +8,14 @@ export { platforms, architectures }
|
||||
|
||||
export interface NexeTarget {
|
||||
version: string
|
||||
platform: NodePlatform | string
|
||||
arch: NodeArch | string
|
||||
platform: NodePlatform
|
||||
arch: NodeArch
|
||||
}
|
||||
|
||||
//TODO bsd
|
||||
const prettyPlatform: { [key: string]: NodePlatform } = {
|
||||
win32: 'windows',
|
||||
windows: 'windows',
|
||||
win: 'windows',
|
||||
darwin: 'mac',
|
||||
macos: 'mac',
|
||||
mac: 'mac',
|
||||
@@ -28,7 +27,6 @@ const prettyPlatform: { [key: string]: NodePlatform } = {
|
||||
//TODO arm
|
||||
const prettyArch: { [key: string]: NodeArch } = {
|
||||
x86: 'x86',
|
||||
amd64: 'x64',
|
||||
ia32: 'x86',
|
||||
x32: 'x86',
|
||||
x64: 'x64'
|
||||
@@ -38,7 +36,7 @@ function isVersion(x: string) {
|
||||
if (!x) {
|
||||
return false
|
||||
}
|
||||
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ''))
|
||||
return /\d/.test(x.replace(/v|\./g, ''))
|
||||
}
|
||||
|
||||
function isPlatform(x: string): x is NodePlatform {
|
||||
@@ -63,9 +61,8 @@ export function targetsEqual(a: NexeTarget, b: NexeTarget) {
|
||||
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
|
||||
}
|
||||
|
||||
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
|
||||
const currentArch = process.arch
|
||||
let arch = currentArch in prettyArch ? prettyArch[process.arch] : process.arch as NodeArch,
|
||||
export function getTarget(target: string | NexeTarget = ''): NexeTarget {
|
||||
let arch = process.arch as NodeArch,
|
||||
platform = prettyPlatform[process.platform],
|
||||
version = process.version.slice(1)
|
||||
|
||||
@@ -78,7 +75,7 @@ export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget
|
||||
.split('-')
|
||||
.forEach(x => {
|
||||
if (isVersion(x)) {
|
||||
version = x.replace(/v/g, '')
|
||||
version = x
|
||||
}
|
||||
if (isPlatform(x)) {
|
||||
platform = prettyPlatform[x]
|
||||
|
||||
-22
@@ -22,26 +22,6 @@ function falseOnEnoent(e: any) {
|
||||
throw e
|
||||
}
|
||||
|
||||
function padRight(str: string, l: number) {
|
||||
return (str + ' '.repeat(l)).substr(0, l)
|
||||
}
|
||||
|
||||
const bound: MethodDecorator = function bound<T>(
|
||||
target: Object,
|
||||
propertyKey: string,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
) {
|
||||
const configurable = true
|
||||
return {
|
||||
configurable,
|
||||
get(this: T) {
|
||||
const value = (descriptor.value as any).bind(this)
|
||||
Object.defineProperty(this, propertyKey, { configurable, value, writable: true })
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dequote(input: string) {
|
||||
input = input.trim()
|
||||
const singleQuote = input.startsWith("'") && input.endsWith("'")
|
||||
@@ -77,8 +57,6 @@ function isDirectoryAsync(path: string) {
|
||||
|
||||
export {
|
||||
dequote,
|
||||
padRight,
|
||||
bound,
|
||||
isWindows,
|
||||
rimrafAsync,
|
||||
statAsync,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as nexe from '../lib/nexe'
|
||||
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
|
||||
import * as nexe from '../src/nexe'
|
||||
import {
|
||||
getUnBuiltReleases,
|
||||
getLatestGitRelease
|
||||
} from '../src/releases'
|
||||
import * as ci from './ci'
|
||||
import { runAlpineBuild } from './docker'
|
||||
import { getTarget } from '../lib/target'
|
||||
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../lib/util'
|
||||
import { getTarget } from '../src/target'
|
||||
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src/util'
|
||||
import got = require('got')
|
||||
|
||||
const env = process.env,
|
||||
@@ -12,14 +15,10 @@ const env = process.env,
|
||||
isLinux = Boolean(env.TRAVIS),
|
||||
isWindows = Boolean(env.APPVEYOR),
|
||||
isMac = Boolean(env.CIRCLECI),
|
||||
isPullRequest =
|
||||
Boolean(env.CIRCLE_PR_NUMBER) ||
|
||||
Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER) ||
|
||||
Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
|
||||
headers = {
|
||||
Authorization: 'token ' + env.GITHUB_TOKEN,
|
||||
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
|
||||
}
|
||||
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) {
|
||||
@@ -30,7 +29,7 @@ if (require.main === module) {
|
||||
}
|
||||
}
|
||||
|
||||
async function build() {
|
||||
async function build () {
|
||||
if (isScheduled) {
|
||||
const releases = await getUnBuiltReleases({ headers })
|
||||
if (!releases.length) {
|
||||
@@ -52,23 +51,25 @@ async function build() {
|
||||
}
|
||||
|
||||
if (env.NEXE_VERSION) {
|
||||
const target = getTarget(env.NEXE_VERSION),
|
||||
const
|
||||
target = getTarget(env.NEXE_VERSION),
|
||||
output = isWindows ? './out.exe' : './out',
|
||||
options = {
|
||||
empty: true,
|
||||
build: true,
|
||||
target,
|
||||
target, //FIXME
|
||||
output
|
||||
}
|
||||
|
||||
|
||||
const stop = keepalive()
|
||||
if (target.platform === 'alpine') {
|
||||
await runAlpineBuild(target)
|
||||
await runAlpineBuild(target, nexe.version.split('.')[0])
|
||||
} else {
|
||||
await nexe.compile(options)
|
||||
await nexe.compile(options)
|
||||
}
|
||||
stop()
|
||||
|
||||
|
||||
|
||||
if (await pathExistsAsync(output)) {
|
||||
await assertNexeBinary(output)
|
||||
const gitRelease = await getLatestGitRelease({ headers })
|
||||
@@ -76,9 +77,8 @@ async function build() {
|
||||
query: { name: target.toString() },
|
||||
body: await readFileAsync(output),
|
||||
headers: {
|
||||
Authorization: 'token ' + env.GITHUB_TOKEN,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
|
||||
'Authorization': 'token ' + env.GITHUB_TOKEN,
|
||||
'Content-Type': 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
console.log(target + ' uploaded.')
|
||||
@@ -86,12 +86,12 @@ async function build() {
|
||||
}
|
||||
}
|
||||
|
||||
function keepalive() {
|
||||
function keepalive () {
|
||||
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
|
||||
return () => clearInterval(keepalive)
|
||||
}
|
||||
|
||||
function assertNexeBinary(file: string) {
|
||||
function assertNexeBinary (file: string) {
|
||||
return execFileAsync(file).catch(e => {
|
||||
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
|
||||
return
|
||||
+20
-21
@@ -1,21 +1,8 @@
|
||||
import { NexeTarget } from '../lib/releases'
|
||||
import { NexeTarget } from '../src/releases'
|
||||
import got = require('got')
|
||||
import * as assert from 'assert'
|
||||
const { env } = process
|
||||
|
||||
export function triggerMacBuild(release: NexeTarget, branch: string) {
|
||||
assert.ok(env.CIRCLE_TOKEN)
|
||||
const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/${branch}?circle-token=${env.CIRCLE_TOKEN}`
|
||||
return got(circle, {
|
||||
json: true,
|
||||
body: {
|
||||
build_parameters: {
|
||||
NEXE_VERSION: release.toString()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function triggerDockerBuild(release: NexeTarget, branch: string) {
|
||||
assert.ok(env.TRAVIS_TOKEN)
|
||||
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
|
||||
@@ -27,23 +14,35 @@ export function triggerDockerBuild(release: NexeTarget, branch: string) {
|
||||
config: {
|
||||
merge_mode: 'deep_merge',
|
||||
env: {
|
||||
//use matrix so that secure global variable is merged
|
||||
matrix: {
|
||||
NEXE_VERSION: release.toString()
|
||||
}
|
||||
NEXE_VERSION: release
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'Travis-API-Version': '3',
|
||||
Authorization: `token ${env.TRAVIS_TOKEN}`
|
||||
'Authorization': `token ${env.TRAVIS_TOKEN}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function triggerWindowsBuild(release: NexeTarget) {
|
||||
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()
|
||||
env.NEXE_VERSION = hasVersion
|
||||
? env.NEXE_VERSION!.trim()
|
||||
: release.toString()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
+22
-49
@@ -1,59 +1,32 @@
|
||||
import { NexeTarget } from '../lib/target'
|
||||
import { writeFileAsync, readFileAsync } from '../lib/util'
|
||||
import { spawn } from 'child_process'
|
||||
import got = require('got')
|
||||
import execa = require('execa')
|
||||
import { appendFileSync } from 'fs'
|
||||
import { NexeTarget } from '../src/target'
|
||||
import { writeFileAsync } from '../src/util'
|
||||
import exec = require('execa')
|
||||
|
||||
function alpine(target: NexeTarget) {
|
||||
function alpine (target: NexeTarget, nexeVersion: string) {
|
||||
const version = target.version
|
||||
return `
|
||||
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
|
||||
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
|
||||
ENV NODE_VERSION=${target.version}
|
||||
ENV NEXE_VERSION=beta
|
||||
WORKDIR /
|
||||
|
||||
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
|
||||
mkdir /nexe_temp && mv /node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
|
||||
cd /nexe_temp/\${NODE_VERSION} && \
|
||||
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 && make install && \
|
||||
make -j$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
|
||||
make install && \
|
||||
paxctl -cm /usr/bin/node
|
||||
|
||||
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
|
||||
npm install -g nexe@\${NEXE_VERSION} && \
|
||||
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
|
||||
`.trim()
|
||||
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) {
|
||||
await writeFileAsync('Dockerfile', alpine(target))
|
||||
const outFilename = 'nexe-alpine-build-log.txt'
|
||||
await writeFileAsync(outFilename, '')
|
||||
let output: any = []
|
||||
|
||||
try {
|
||||
output.push(await execa.shell(`docker build -t nexe-alpine .`))
|
||||
output.push(await execa.shell(`docker run -d --name nexe nexe-alpine sh`))
|
||||
output.push(await execa.shell(`docker cp nexe:/out out`))
|
||||
output.push(await execa.shell(`docker rm nexe`))
|
||||
} catch (e) {
|
||||
console.log('Error running docker', e)
|
||||
} finally {
|
||||
output.forEach((x: any) => {
|
||||
appendFileSync(outFilename, x.stderr)
|
||||
appendFileSync(outFilename, x.stdout)
|
||||
})
|
||||
await got(
|
||||
`https://transfer.sh/${Math.random()
|
||||
.toString(36)
|
||||
.substring(2)}.txt`,
|
||||
{
|
||||
body: await readFileAsync(outFilename),
|
||||
method: 'PUT'
|
||||
}
|
||||
)
|
||||
.then(x => console.log('Posted docker log: ', x.body))
|
||||
.catch(e => console.log('Error posting log', e))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { writeFileSync, readFileSync } from 'fs'
|
||||
|
||||
/**
|
||||
* post build step to insert code files into code files.
|
||||
* '{{replace:path/to/file}}' => "file contents"
|
||||
* And the package.json version.
|
||||
*/
|
||||
//inject('plugins/nexe-daemon/lib/index.js')
|
||||
inject('lib/patches/third-party-main.js')
|
||||
inject('lib/steps/shim.js')
|
||||
inject('lib/options.js', JSON.stringify(require('../package.json').version))
|
||||
|
||||
function inject(filename: string, ...replacements: string[]) {
|
||||
let contents = readFileSync(filename, 'utf8')
|
||||
contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => {
|
||||
if (!matches || !matches[1]) {
|
||||
return substring
|
||||
}
|
||||
const [replace, file] = matches[1].split(':')
|
||||
if (replace !== 'replace') {
|
||||
return substring
|
||||
}
|
||||
console.log('Replacing: ', substring)
|
||||
return replacements[+file] ? replacements[+file] : JSON.stringify(readFileSync(file, 'utf8'))
|
||||
})
|
||||
writeFileSync(filename, contents)
|
||||
console.log(`Wrote: ${filename}`)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"main": "entry-file.js"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
./test/**/*.spec.ts
|
||||
--check-leaks
|
||||
--require ts-node/register
|
||||
--recursive
|
||||
@@ -1,48 +0,0 @@
|
||||
import { normalizeOptions } from '../src/options'
|
||||
import { expect } from 'chai'
|
||||
import * as path from 'path'
|
||||
|
||||
const ext = process.platform === 'win32' ? '.exe' : ''
|
||||
|
||||
describe('options', () => {
|
||||
describe('cwd', () => {
|
||||
it('should use process.cwd() if nothing is provided', () => {
|
||||
const options = normalizeOptions()
|
||||
expect(options.cwd).to.equal(process.cwd())
|
||||
})
|
||||
it('should use the main module in a package directory', () => {
|
||||
const options = normalizeOptions()
|
||||
expect(options.input).to.equal(path.resolve('./index.js'))
|
||||
})
|
||||
it('should resolve pathed options against cwd', () => {
|
||||
const cwd = '/a/b/c'
|
||||
const options = normalizeOptions({
|
||||
cwd,
|
||||
input: '123.js',
|
||||
output: 'abc',
|
||||
temp: './d'
|
||||
})
|
||||
expect(options.temp).to.equal(path.resolve(cwd, './d'))
|
||||
expect(options.input).to.equal(path.resolve(cwd, '123.js'))
|
||||
expect(options.output).to.equal(path.resolve(cwd, `abc${ext}`))
|
||||
})
|
||||
})
|
||||
describe('output', () => {
|
||||
it('should work', () => {
|
||||
const options = normalizeOptions({
|
||||
output: './some-output'
|
||||
})
|
||||
expect(options.output).to.equal(path.resolve(`./some-output${ext}`))
|
||||
})
|
||||
it('should default to the input file name if not index', () => {
|
||||
const options = normalizeOptions({
|
||||
input: 'src/folder/app.js'
|
||||
})
|
||||
expect(options.output).to.equal(path.resolve(`./app${ext}`))
|
||||
})
|
||||
it('should default to the folder/project name if filename is index', () => {
|
||||
const options = normalizeOptions()
|
||||
expect(options.output).to.equal(path.resolve(`./nexe${ext}`))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
import { padRight, isWindows } from '../src/util'
|
||||
import { expect } from 'chai'
|
||||
import { blue as b } from 'chalk'
|
||||
import { getTarget, NexeTarget } from '../src/target'
|
||||
|
||||
const arch = process.arch === 'ia32' ? 'x86' : process.arch
|
||||
|
||||
describe('Targets', () => {
|
||||
[
|
||||
['win-ia32-6.11.2', 'windows-x86-6.11.2'],
|
||||
[{ version: '6.11.2', platform: 'win', arch: 'ia32' }, 'windows-x86-6.11.2'],
|
||||
['win32-x64-6.11.2', 'windows-x64-6.11.2'],
|
||||
['win-amd64-6.11.2', 'windows-x64-6.11.2'],
|
||||
['darwin-x64-v8.4.0', 'mac-x64-8.4.0'],
|
||||
['macos-x64-v8.4.0', 'mac-x64-8.4.0'],
|
||||
['static-x86-6.10.3', 'alpine-x86-6.10.3'],
|
||||
['linux-x32', `linux-x86-${process.version.slice(1)}`],
|
||||
['alpine-notsupported-6.10.3', `alpine-${arch}-6.10.3`],
|
||||
['not-a-thing', getTarget(process).toString()]
|
||||
].forEach(([input, expected]) => {
|
||||
it(`should accept: ${padRight(JSON.stringify(input), 53)} ${b('->')} ${expected}`, () => {
|
||||
expect(getTarget(input).toString()).to.equal(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it ('should stringify and toString', () => {
|
||||
expect(JSON.stringify(getTarget(process))).to.equal(`"${getTarget(process)}"`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target":"es5",
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2017"],
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
|
||||
Reference in New Issue
Block a user