Compare commits

..

52 Commits

Author SHA1 Message Date
calebboyd 66afccc2e2 fix: order of operations #448 2018-02-16 22:26:42 -06:00
David Goemans 71e77052e5 docs: add note about --build required for --ico option 2018-02-07 11:54:26 -06:00
calebboyd eef430afb1 chore: update fuse-box 2018-02-05 20:22:23 -06:00
bruce-one 133d1919fa feat: allow setting NODE_OPTIONS. (#448) 2018-02-01 16:29:49 -06:00
calebboyd 9a81cc2fa2 fix: don't rely on got as a transient dependency
closes #439
2018-01-22 18:37:50 -06:00
Bryce Gibson a40d697542 feat: add createReadStream support for resources 2018-01-22 19:24:13 -05:00
calebboyd 02481f069c fix re-order shim 2018-01-04 13:44:01 -06:00
Andrew Razumovsky cb26021296 fix(rc): regex works to the end of the line (#437) 2017-12-21 22:47:30 -06:00
calebboyd 719804eb2f chore: bump dependencies 2017-12-02 12:52:59 -06:00
calebboyd 721b06f5d2 chore: bump rc 23 2017-11-29 15:52:53 -06:00
calebboyd c1896e13a3 feat: add proxy support (set environment variable HTTPS_PROXY) 2017-11-29 15:49:33 -06:00
calebboyd 931af404ae chore: bump rc 2017-11-26 09:10:04 -06:00
Tim f83298a0af fix(download): remove the ghToken for binary download
The github token is only needed for the API access and will confuse amazon S3 when it is passed to the download:

```
<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>InvalidArgument</Code><Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message><ArgumentName>Authorization</ArgumentName><ArgumentValue>token herebesecrettoken</ArgumentValue><RequestId>7CCDB87CF1347C29</RequestId><HostId>xyyeVP5ImwDePFMXAZheNYLnXQBsVCka4U6KLWKCjynHNpQZxzfBdl4sRfUbORSul8c8YNwux8w=</HostId></Error>
```

Refs #425
2017-11-26 08:08:28 -07:00
calebboyd 717e825b17 chore: lint 2017-11-25 21:53:16 -06:00
calebboyd 5ac0afb0dd feat(options): add ability to pass a github token 2017-11-25 21:51:41 -06:00
Ishai Jaffe 23f550818b fix(shim): properly serialize path 2017-11-14 14:57:39 -05:00
calebboyd 3d18eae18a chore: bump rc 2017-10-27 21:21:47 -05:00
Artem Derevnjuk 6f553d0fba fix(embed-node): import missing module to work with fs (#416) 2017-10-27 21:17:26 -05:00
calebboyd aba3047191 fix: only add extension when targeting windows 2017-10-25 23:35:26 -05:00
calebboyd 52b58a107d feat: enable fake-argv by default 2017-10-21 22:48:42 -05:00
calebboyd ec734e4fb1 chore: use yarn in circle-ci 2017-10-20 17:38:20 -05:00
calebboyd 7c755e666f chore: bump fuse-box 2017-10-17 16:56:44 -05:00
calebboyd 90ae7de7ac chore: bump fuse-box 2017-10-16 13:02:08 -05:00
Alexis Tyler 96f67ecba4 chore(docs): fix typo & add missing period 2017-10-15 19:32:12 -05:00
calebboyd 6526a611a8 chore: bump deps 2017-10-10 09:56:22 -04:00
calebboyd f9bf4ca464 chore: add chat badge 2017-10-07 09:27:30 -05:00
calebboyd 4ec8a43d60 fix: consistent cwd usage 2017-10-05 20:54:37 -05:00
calebboyd 77109235c4 chore: separate bindings and .node embedding 2017-10-05 18:58:41 -05:00
Bryce Gibson c1531cbf23 Pause logger before and resume after spawn. 2017-10-04 22:22:53 -05:00
Bryce Gibson c5f3116c18 Show process output when loglevel is verbose. 2017-10-04 22:22:53 -05:00
calebboyd 03e2985c7a feat: stat and statSync 2017-10-03 01:54:06 -05:00
calebboyd a4b48611d9 feat: readdir and readdirSync 2017-10-03 00:39:33 -05:00
calebboyd 83d3ccf1c5 feat: support the cluster module 2017-10-02 22:28:04 -05:00
calebboyd 356d4c1886 fix(nix): only make output executable 2017-10-02 21:49:05 -05:00
calebboyd b74bdfc53b chore: fusebox beta 2017-10-02 18:04:32 -05:00
calebboyd 927820a5ec chore: service progress 2017-10-02 18:02:32 -05:00
calebboyd 4ef98bdc8a chore: re-order docs 2017-09-30 15:44:57 -05:00
calebboyd 90d7beb4f3 docs: add native module note 2017-09-30 15:28:03 -05:00
calebboyd 55c95b4663 docs: elaborate on target 2017-09-30 15:06:34 -05:00
calebboyd fe5aa0be09 fix: dequote 2017-09-29 23:16:09 -05:00
calebboyd 2a2526d3bc fix: disable stdin for programatic usage 2017-09-29 17:49:17 -05:00
calebboyd c8310ce3bc docs: update readme 2017-09-28 22:02:27 -05:00
calebboyd bb356a967b chore: multi-arch builds on travis 2017-09-28 16:06:55 -05:00
calebboyd 0d62d0100d chore: bump rc.9 2017-09-28 00:15:44 -05:00
calebboyd bfd07cd10a chore: move mac build to circle 2017-09-27 23:36:52 -05:00
calebboyd 4390568654 fix: ia32 target normalization 2017-09-27 01:34:07 -05:00
calebboyd 5c9cf3580c chore: update build 2017-09-27 00:47:16 -05:00
calebboyd fcb54d9085 fix: check error code on build commands 2017-09-26 23:54:18 -05:00
calebboyd 11fa087e9d refactor: move startup scripts to shims, add plugins 2017-09-26 20:16:03 -05:00
calebboyd b822984776 fix: input path for bundle 2017-09-26 00:10:09 -05:00
calebboyd 4f2b1c51d5 refactor: addResource compiler method 2017-09-26 00:10:09 -05:00
Brian McCallister dbcff4c7d2 fix: minor spelling problem 2017-09-21 18:04:07 -05:00
47 changed files with 6477 additions and 3564 deletions
+2
View File
@@ -4,6 +4,8 @@ coverage
node_modules
*.log
*.exe
*.bin
!winsw.exe
.idea
.nexe
lib
+1
View File
@@ -0,0 +1 @@
package-lock=false
+12 -9
View File
@@ -1,9 +1,12 @@
os: osx
language: node_js
node_js:
- '6'
script: npm run nexe-build
env:
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
notifications:
email: false
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
+68 -15
View File
@@ -5,13 +5,14 @@
<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">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
<p align="center">
<img src="https://cloud.githubusercontent.com/assets/5818726/26533446/ce19ee5a-43de-11e7-9540-caf7ebd93370.gif"/>
<img src="https://user-images.githubusercontent.com/5818726/30999006-df7e0ae0-a497-11e7-96db-9ce87ae67b34.gif"/>
</p>
## Motivation and Features
@@ -37,16 +38,21 @@
For more CLI options see: `nexe --help`
# Advanced
### Examples
- `nexe server.js -r public/**/*.html`
- `nexe my-bundle.js --no-bundle -o app.exe`
- `nexe --build`
- `nexe -t x86-8.0.0`
## Resources
Additional files or resources can be added to the binary by passing `-r "glob/pattern/**/*"`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`
Additional files or resources can be added to the binary by passing `-r "glob/pattern/**/*"`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`.
## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the `--build` option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md)
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptable version of python 2. eg. Systems that have python2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486).
## Node.js API
@@ -82,8 +88,17 @@ compile({
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string | object`
- Combination of platform-arch-version. e.g. `'win32-ia32-6.10.3'`
- 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`
- #### `bundle: string | boolean`
- If a string is provided it must be a valid relative module path
@@ -99,7 +114,7 @@ compile({
- Directory nexe will operate on as though it is the cwd
- default: process.cwd()
- #### `build: boolean`
- Build node from source
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
- #### `python: string`
- On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed
@@ -115,7 +130,7 @@ compile({
- #### `make: string[]`
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- #### `make: string[]`
- #### `vcBuild: string[]`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
@@ -129,18 +144,32 @@ compile({
- Override in the env with `NEXE_TEMP`
- default: `./.nexe` in the cwd
- #### `ico: string`
- Path to a user provided icon to be used (Windows only).
- Path to a user provided icon to be used (Windows only). Requires `--build` to be set.
- #### `rc: object`
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
- Example: `{ CompanyName: "ACME Corp" }`
- Example:
```javascript
{
CompanyName: "ACME Corp",
PRODUCTVERSION: "17,3,0,0",
FILEVERSION: "1,2,3,4"
...
}
```
- default: `{}`
- #### `clean: boolean`
- If included, nexe will remove temporary files for the accompanying configuration and exit
- #### `enableNodeCli: boolean`
- Enable the original Node CLI (will prevent application cli from working)
- Enable the original Node CLI (will prevent application cli from working).
- Node CLI arguments passed via the [NODE_OPTIONS](https://nodejs.org/api/cli.html#cli_node_options_options) environment
variable will still be processed. NODE_OPTIONS support can be disabled with the `--without-node-options` configure flag.
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `ghToken: string`
- Provide a Github Token for accessing nexe releases
- This is usually needed in CI environments
- default: `process.env.GITHUB_TOKEN`
- #### `sourceUrl: string`
- Provide an alternate url for the node source code
- Note: temporary files will still be created for this under the specified version
@@ -150,12 +179,15 @@ compile({
- #### `patches: NexePatch[]`
- Userland patches for patching or modifying node source
- default: `[]`
- #### `plugins: NexePatch[]`
- Userland plugins for modifying nexe executable behavior
- default: `[]`
### `NexePatch: (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>`
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.
Patches and Plugins are just a middleware functions that take two arguments, the `compiler`, and `next`. The compiler is described below, and `next` ensures that the pipeline continues. Its invocation should always be awaited or returned to ensure correct behavior. Patches also require that [`--build`](#build-boolean) be set, while plugins do not.
For examples, see the built in patches: [src/patches](src/patches)
For examples, see the built in patches: [src/patches](src/patches).
### `NexeCompiler`
@@ -165,6 +197,8 @@ 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[]`
- The cache of the currently read, modified, or created files within the downloaded Node.js source.
@@ -177,10 +211,29 @@ Any modifications made to `NexeFile#contents` will be maintained in the cache _w
## Native Modules
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
Take a look at the (windows) [example](examples/native-build/build.js)
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`.
- [ ] Implement support `node-pre-gyp#find`.
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
```
## Maintainers
+2 -5
View File
@@ -5,16 +5,13 @@ environment:
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
branches:
except:
- master
install:
- ps: Install-Product node 6
- npm i
- yarn
build: off
test: off
build_script:
- npm run nexe-build
- yarn run asset-compile
artifacts:
- path: .nexe\**\node.exe
cache:
+7 -6
View File
@@ -1,8 +1,9 @@
machine:
node:
version: 6.11.2
services:
- docker
dependencies:
override:
- export NVS_HOME="$HOME/.nvs" && git clone https://github.com/jasongin/nvs "$NVS_HOME" && . "$NVS_HOME/nvs.sh" install
- nvs add lts && nvs link lts && nvs use default
- npm i yarn -g
- yarn
test:
override:
- npm run nexe-build
- yarn run asset-compile
+14 -8
View File
@@ -1,11 +1,17 @@
#!/usr/bin/env node
const nexe = require('./lib/nexe')
const eol = require('os').EOL
module.exports = nexe
const options = require('./lib/options')
if (require.main === module) {
nexe.compile(nexe.argv).catch((e) => {
process.stderr.write(eol + e.stack, () => process.exit(1))
})
//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)
}
} else {
module.exports = require('./lib/nexe')
}
-2995
View File
File diff suppressed because it is too large Load Diff
+17 -11
View File
@@ -2,19 +2,21 @@
"name": "nexe",
"description": "Create a single executable out of your Node.js application",
"license": "MIT",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.25",
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>"
],
"scripts": {
"nexe-build": "ts-node tasks/build",
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm test && npm run build",
"test": "mocha test/**/*.spec.ts",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
"build": "tsc --declaration"
"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\"",
"prepare": "npm run build",
"prepublishOnly": "npm test",
"prebuild": "rimraf lib && npm run fmt",
"build": "tsc --declaration",
"postbuild": "ts-node tasks/post-build"
},
"repository": {
"type": "git",
@@ -24,6 +26,7 @@
"lib",
"source-map-support.js"
],
"typings": "lib/nexe.d.ts",
"main": "index.js",
"bin": {
"nexe": "index.js"
@@ -33,10 +36,13 @@
},
"dependencies": {
"app-builder": "^5.1.0",
"typescript": "2.5.3",
"caw": "^2.0.1",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "2.2.31",
"fuse-box": "^3.1.0",
"globby": "^6.1.0",
"got": "^8.0.3",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"ora": "^1.2.0",
@@ -47,6 +53,7 @@
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/chalk": "^0.4.31",
"@types/execa": "^0.7.0",
"@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
@@ -55,10 +62,9 @@
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28",
"chai": "^4.1.2",
"got": "^7.1.0",
"execa": "^0.8.0",
"mocha": "^3.5.0",
"prettier": "^1.6.1",
"ts-node": "3.3.0",
"typescript": "^2.4.1"
"ts-node": "3.3.0"
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"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"
}
}
+48
View File
@@ -0,0 +1,48 @@
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()
}
+66
View File
@@ -0,0 +1,66 @@
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
}
})
}
+13
View File
@@ -0,0 +1,13 @@
{
"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
-68
View File
@@ -3,74 +3,6 @@ import * as path from 'path'
import * as child from 'child_process'
import { createHash } from 'crypto'
export interface ExtractNodeModuleOptions {
[key: string]: { additionalFiles: string[] } | true
}
function hashName(name: string | Buffer) {
return createHash('md5')
.update(name)
.digest('hex')
.toString()
.slice(0, 8)
}
export function embedDotNode(
options: ExtractNodeModuleOptions,
file: { contents: string; absPath: string }
) {
const contents = fs.readFileSync(file.absPath)
const modulePathParts = file.absPath.split(path.sep).reverse()
const module = modulePathParts[modulePathParts.findIndex(x => x === 'node_modules') - 1]
const bindingName = path.basename(file.absPath)
const settings = options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (!settings || settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
})
let segments = [moduleDir]
while (depth--) {
segments.push(hashName(moduleDir + depth))
}
segments.push(bindingName)
file.contents += `
var cwd = process.cwd()
var bindingFileParts = ${JSON.stringify(segments)};
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
mkdirp(path.dirname(bindingFile));
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
${settings.additionalFiles.reduce((code, filename, i) => {
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
return (code += `
var file${i} = '${contents.toString('base64')}';
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
}
function findNativeModulePath(filePath: string, bindingsArg: string) {
const dirname = path.dirname(filePath)
const tempFile = Math.random() * 100 + '.js'
+72
View File
@@ -0,0 +1,72 @@
import { createHash } from 'crypto'
import * as path from 'path'
import * as fs from 'fs'
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)
`
}
+4 -3
View File
@@ -1,4 +1,5 @@
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
import { BindingsRewrite } from './bindings-rewrite'
import { embedDotNode, EmbedNodeModuleOptions } from './embed-node'
export interface FuseBoxFile {
info: { absPath: string }
@@ -21,14 +22,14 @@ export interface FuseBoxFile {
): void
}
export default function(options: ExtractNodeModuleOptions = {}) {
export default function(options: EmbedNodeModuleOptions = {}) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test = /node_modules.*(\.js|\.node)$|\.node$/
public limit2Project = false
private modules: (keyof ExtractNodeModuleOptions)[]
private modules: (keyof EmbedNodeModuleOptions)[]
constructor(public options = {}) {}
+1
View File
@@ -0,0 +1 @@
//TODO
+113 -72
View File
@@ -4,9 +4,9 @@ import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { Readable } from 'stream'
import { spawn } from 'child_process'
import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
import { NexeOptions, nexeVersion } from './options'
import { Logger, LogStep } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util'
import { NexeOptions, version } from './options'
import { NexeTarget } from './target'
import download = require('download')
import { getLatestGitRelease } from './releases'
@@ -22,34 +22,32 @@ export interface NexeFile {
contents: string
}
export { NexeOptions }
interface NexeHeader {
resources: { [key: string]: number[] }
version: string
}
export class NexeCompiler {
export class NexeCompiler<T extends NexeOptions = NexeOptions> {
private start = Date.now()
private env = { ...process.env }
private compileStep: { modify: Function; log: Function }
private compileStep: LogStep
public log = new Logger(this.options.loglevel)
public src: string
public files: NexeFile[] = []
public shims: string[] = []
public input: string
public bundledInput?: string
public output: string | null
public targets: NexeTarget[]
public target: NexeTarget
public resources: { bundle: string; index: { [key: string]: number[] } } = {
public resources: { bundle: Buffer; index: { [key: string]: number[] } } = {
index: {},
bundle: ''
bundle: Buffer.from('')
}
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>
public output = this.options.output
private nodeSrcBinPath: string
constructor(public options: NexeOptions) {
constructor(public options: T) {
const { python } = (this.options = options)
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
@@ -57,7 +55,7 @@ export class NexeCompiler {
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + nexeVersion, 'info')
this.log.step('nexe ' + version, 'info')
if (python) {
if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
@@ -65,40 +63,64 @@ export class NexeCompiler {
this.env.PYTHON = python
}
}
}
this.readFileAsync = async (file: string) => {
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
if (!cachedFile) {
const absPath = join(this.src, file)
cachedFile = {
absPath,
filename: file,
contents: await readFileAsync(absPath, 'utf-8').catch(x => {
if (x.code === 'ENOENT') return ''
throw x
})
}
this.files.push(cachedFile)
@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
})
}
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
this.files.push(cachedFile)
}
return cachedFile
}
@bound
writeFileAsync(file: string, contents: string | Buffer) {
this.assertBuild()
return writeFileAsync(join(this.src, file), contents)
}
@bound
async replaceInFileAsync(file: string, replace: string | RegExp, value: string) {
const entry = await this.readFileAsync(file)
entry.contents = entry.contents.replace(replace, value)
}
@bound
async setFileContentsAsync(file: string, contents: string) {
const entry = await this.readFileAsync(file)
entry.contents = contents
}
quit() {
const time = Date.now() - this.start
this.log.write(`Finsihed in ${time / 1000}s`)
this.log.write(`Finished in ${time / 1000}s`)
return this.log.flush()
}
assertBuild() {
if (!this.options.build) {
throw new Error('This feature is only available with `--build`')
}
}
public getNodeExecutableLocation(target?: NexeTarget) {
if (target) {
return join(this.options.temp, target.toString())
@@ -107,14 +129,31 @@ export class NexeCompiler {
}
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: 'ignore'
stdio: this.log.verbose ? 'inherit' : 'ignore'
})
.once('error', reject)
.once('close', resolve)
.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()
})
})
}
@@ -127,9 +166,9 @@ export class NexeCompiler {
private async _buildAsync() {
this.compileStep.log(
`Configuring node build${this.options.configure.length
? ': ' + this.options.configure
: '...'}`
`Configuring node build${
this.options.configure.length ? ': ' + this.options.configure : '...'
}`
)
await this._configureAsync()
const buildOptions = this.options.make
@@ -141,11 +180,15 @@ export class NexeCompiler {
}
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
const downloadOptions: any = {
headers: {
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
let downloadOptions = this.options.downloadOptions
if (this.options.ghToken) {
downloadOptions = Object.assign({}, downloadOptions)
downloadOptions.headers = Object.assign({}, downloadOptions.headers, {
Authorization: 'token ' + this.options.ghToken
})
}
const githubRelease = await getLatestGitRelease(downloadOptions)
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
@@ -154,22 +197,22 @@ export class NexeCompiler {
throw new Error(`${assetName} not available, create it using the --build flag`)
}
const filename = this.getNodeExecutableLocation(target)
await download(
asset.browser_download_url,
dirname(filename),
downloadOptions
).on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
})
await download(asset.browser_download_url, dirname(filename), this.options.downloadOptions).on(
'response',
(res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
}
)
return createReadStream(filename)
}
private _generateHeader() {
getHeader() {
const version =
['configure', 'vcBuild', 'make'].reduce((a, c) => {
return (a += (this.options as any)[c]
@@ -183,11 +226,6 @@ export class NexeCompiler {
.update(version)
.digest('hex')
}
const serializedHeader = this._serializeHeader(header)
return header
}
private _serializeHeader(header: NexeHeader) {
return `process.__nexe=${JSON.stringify(header)};`
}
@@ -196,7 +234,6 @@ export class NexeCompiler {
const build = this.options.build
const location = this.getNodeExecutableLocation(build ? undefined : target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
const header = this._generateHeader()
if (!build && !binary) {
step.modify('Fetching prebuilt binary')
binary = await this._fetchPrebuiltBinaryAsync(target)
@@ -205,10 +242,14 @@ export class NexeCompiler {
binary = await this._buildAsync()
step.log('Node binary compiled')
}
return this._assembleDeliverable(header, binary)
return this._assembleDeliverable(binary)
}
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
code() {
return [this.shims.join(''), this.input].join(';')
}
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
if (this.options.empty) {
return binary
}
@@ -217,12 +258,12 @@ export class NexeCompiler {
artifact.push(chunk)
})
binary.on('close', () => {
const content = this._serializeHeader(header) + this.input
const content = this.code()
artifact.push(content)
artifact.push(this.resources.bundle)
const lengths = Buffer.from(Array(16))
lengths.writeDoubleLE(Buffer.byteLength(content), 0)
lengths.writeDoubleLE(Buffer.byteLength(this.resources.bundle), 8)
lengths.writeDoubleLE(this.resources.bundle.byteLength, 8)
artifact.push(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
artifact.push(null)
})
+7 -3
View File
@@ -6,10 +6,12 @@ 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 {
private verbose: boolean
public verbose: boolean
private silent: boolean
private ora: any
private modify: Function
@@ -48,7 +50,7 @@ export class Logger {
step(text: string, method: string = 'succeed'): LogStep {
if (this.silent) {
return { modify() {}, log() {} }
return { modify() {}, log() {}, pause() {}, resume() {} }
}
if (!this.ora.id) {
this.ora.start().text = text
@@ -62,7 +64,9 @@ export class Logger {
return {
modify: this.modify,
log: this.verbose ? this.write : this.modify
log: this.verbose ? this.write : this.modify,
pause: () => this.ora.stopAndPersist(),
resume: () => this.ora.start()
} as LogStep
}
}
+27 -23
View File
@@ -1,27 +1,22 @@
import { compose, PromiseConfig, Middleware } from 'app-builder'
import resource from './steps/resource'
import { EOL } from 'os'
import { compose, Middleware } from 'app-builder'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
import { NexeCompiler } from './compiler'
import {
argv,
nexeVersion as version,
normalizeOptionsAsync,
NexeOptions,
NexePatch
} from './options'
import { normalizeOptions, NexeOptions, NexePatch } from './options'
import resource from './steps/resource'
import cli from './steps/cli'
import bundle from './steps/bundle'
import download from './steps/download'
import shim from './steps/shim'
import artifacts from './steps/artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = await normalizeOptionsAsync(compilerOptions)
const options = normalizeOptions(compilerOptions)
const compiler = new NexeCompiler(options)
const build = compiler.options.build
@@ -40,16 +35,25 @@ async function compile(
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
const nexe = compose(resource, bundle, cli, buildSteps, shim)
return callback
? void nexe(compiler).then(
() => callback && callback(null),
(e: Error) => {
if (callback) callback(e)
else throw e
}
)
: nexe(compiler)
const nexe = compose(resource, bundle, shim, cli, buildSteps, options.plugins as NexePatch[])
let error = null
try {
await nexe(compiler)
} catch (e) {
error = e
}
if (error) {
if (compiler.options.loglevel !== 'silent' && error) {
process.stderr.write(EOL + error.stack + EOL)
}
compiler.quit()
if (callback) return callback(error)
return Promise.reject(error)
}
if (callback) callback(null)
}
export { argv, compile, version }
export { compile, NexeCompiler }
export { argv, version, NexeOptions, help } from './options'
+93 -65
View File
@@ -1,12 +1,14 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { isWindows, padRight } from './util'
import { basename, extname, join, isAbsolute, relative, dirname } from 'path'
import { basename, extname, join, isAbsolute, relative, dirname, resolve } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os'
import { EOL, homedir } from 'os'
import * as c from 'chalk'
export const nexeVersion = '2.0.0-rc.6'
const caw = require('caw')
export const version = '{{replace:0}}'
export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
@@ -30,9 +32,12 @@ export interface NexeOptions {
enableNodeCli: boolean
bundle: boolean | string
patches: (string | NexePatch)[]
plugins: (string | NexePatch)[]
native: any
empty: boolean
ghToken: string
sourceUrl?: string
enableStdIn?: boolean
python?: string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
@@ -61,7 +66,8 @@ const defaults = {
compress: false,
build: false,
bundle: true,
patches: []
patches: [],
plugins: []
}
const alias = {
i: 'input',
@@ -78,46 +84,47 @@ const alias = {
m: 'make',
h: 'help',
l: 'loglevel',
'fake-argv': 'fakeArgv'
'fake-argv': 'fakeArgv',
'gh-token': 'ghToken'
}
const argv = parseArgv(process.argv, { alias, default: defaults })
const argv = parseArgv(process.argv, { alias, default: { ...defaults, enableStdIn: true } })
const g = c.gray
let help = `
${c.bold('nexe <entry-file> [options]')}
${c.underline.bold('Options:')}
-i --input ${g('=index.js')} -- application entry point
-o --output ${g('=my-app.exe')} -- path to output file
-t --target ${g('=8.4.0-x64')} -- node version description
-n --name ${g('=my-app')} -- main app module name
-r --resource -- *embed files (glob) within the binary
-i --input -- application entry point
-o --output -- path to output file
-t --target -- node version description
-n --name -- main app module name
-r --resource -- *embed files (glob) within the binary
--plugin -- extend nexe runtime behavior
${c.underline.bold('Building from source:')}
-b --build -- build from source
-p --python -- python2 (as python) executable path
-f --flag -- *v8 flags to include during compilation
-c --configure -- *arguments to the configure step
-m --make -- *arguments to the make/build step
--snapshot -- path to a warmup snapshot
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--enableNodeCli -- enable node cli enforcement (blocks app cli)
-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 TODO -- fake argv[1] with entry file
--clean -- force download of sources
--silent -- disable logging
--verbose -- set logging to verbose
--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()
-* variable key name * option can be used more than once`.trim()
help = EOL + help + EOL
function flatten(...args: any[]): string[] {
@@ -148,10 +155,6 @@ function tryResolveMainFileName(cwd: string) {
filename = basename(file).replace(extname(file), '')
} catch (_) {}
if (filename === 'index' && basename(cwd)) {
return basename(cwd)
}
return filename ? filename : 'nexe_' + Date.now()
}
@@ -162,63 +165,85 @@ function extractLogLevel(options: NexeOptions) {
return 'info'
}
function isName(name: string) {
return name && name !== 'index'
}
function extractName(options: NexeOptions) {
let name = options.name
if (!name && typeof options.input === 'string') {
if (!isName(name) && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '')
}
name = name === 'index' ? tryResolveMainFileName(options.cwd) : name
if (!isName(name)) {
name = tryResolveMainFileName(options.cwd)
}
if (!isName(name) && basename(options.cwd)) {
name = basename(options.cwd)
}
return name.replace(/\.exe$/, '')
}
function isEntryFile(filename: string) {
return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
function isEntryFile(filename?: string): filename is string {
return Boolean(
filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
)
}
function findInput(input: string, cwd: string) {
const maybeInput = argv._.slice().pop() || ''
export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
if (input) {
return input
return resolve(cwd, input)
}
if (isEntryFile(maybeInput)) {
return maybeInput
if (isEntryFile(maybeEntry)) {
return resolve(cwd, maybeEntry)
}
if (!process.stdin.isTTY) {
return ''
}
try {
const main = require.resolve(cwd)
return './' + relative(cwd, main)
return require.resolve(cwd)
} catch (e) {
void e
throw new Error('No entry file found')
}
return ''
}
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions | never> {
if (argv.help || argv._.some((x: string) => x === 'version') || argv.version === true) {
return new Promise(() => {
process.stderr.write(argv.help ? help : nexeVersion + EOL, () => process.exit(0))
})
}
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
options.temp = process.env.NEXE_TEMP || join(options.cwd, '.nexe')
options.input = findInput(options.input, options.cwd)
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.name = extractName(options)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget)
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (!options.targets.length) {
options.targets.push(getTarget())
}
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.downloadOptions = options.downloadOptions || {}
options.downloadOptions.headers = options.downloadOptions.headers || {}
options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)'
options.downloadOptions.agent = process.env.HTTPS_PROXY
? caw(process.env.HTTPS_PROXY, { protocol: 'https' })
: options.downloadOptions.agent || require('https').globalAgent
options.downloadOptions.rejectUnauthorized = process.env.HTTPS_PROXY ? false : true
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
options.output =
(options.targets[0] as NexeTarget).platform === 'windows'
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
: `${options.output || options.name}`
options.output = resolve(cwd, options.output)
if (options.build) {
const { arch } = options.targets[0] as NexeTarget
@@ -229,18 +254,21 @@ function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOption
}
}
options.patches = options.patches.map(x => {
const requireDefault = (x: string) => {
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 Promise.resolve(options)
return options
}
export { argv, normalizeOptionsAsync }
export { argv, normalizeOptions, help }
+53
View File
@@ -0,0 +1,53 @@
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)
+10 -2
View File
@@ -5,8 +5,16 @@ export default async function disableNodeCli(compiler: NexeCompiler, next: () =>
return next()
}
const nodeccMarker = "argv[index][0] == '-'"
const nodeccMarker = 'argv[index][0] =='
await compiler.replaceInFileAsync(
'src/node.cc',
`${nodeccMarker} '-'`,
// allow NODE_OPTIONS, introduced in 8.0
parseInt(compiler.target.version.split('.')[0]) >= 8
? `(${nodeccMarker} (is_env ? '-' : ']'))`
: `(${nodeccMarker} ']')`
)
await compiler.replaceInFileAsync('src/node.cc', nodeccMarker, nodeccMarker.replace('-', ']'))
return next()
}
+3 -3
View File
@@ -1,12 +1,12 @@
import gyp from './gyp'
import nexePatches from './third-party-main'
import bootNexe from './third-party-main'
import buildFixes from './build-fixes'
import cli from './disable-node-cli'
import flags from './flags'
import ico from './ico'
import rc from './node-rc'
import { NexeCompiler } from '../compiler'
import { NexeCompiler, NexeOptions } from '../compiler'
const patches = [gyp, nexePatches, buildFixes, cli, flags, ico, rc]
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc]
export default patches
+1 -1
View File
@@ -13,7 +13,7 @@ export default async function nodeRc(compiler: NexeCompiler, next: () => Promise
const isVar = /^[A-Z_]+$/.test(value)
value = isVar ? value : `"${value}"`
file.contents = file.contents.replace(
new RegExp(`VALUE "${key}",*`),
new RegExp(`VALUE "${key}",.*`),
`VALUE "${key}", ${value}`
)
})
+1 -116
View File
@@ -5,122 +5,7 @@ import { join } from 'path'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
`
const fs = require('fs')
const path = require('path')
const Buffer = require('buffer').Buffer
const isString = x => typeof x === 'string' || x instanceof String
const fd = fs.openSync(process.execPath, 'r')
const size = fs.statSync(process.execPath).size
const footer = Buffer.from(Array(32))
fs.readSync(fd, footer, 0, 32, size - 32)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
throw 'Invalid Nexe binary'
}
const contentSize = footer.readDoubleLE(16)
const resourceSize = footer.readDoubleLE(24)
const contentStart = size - 32 - resourceSize - contentSize
const resourceStart = contentStart + contentSize
Object.defineProperty(process, '__nexe', (function () {
let nexeHeader = null
return {
get: function () {
return nexeHeader
},
set: function (value) {
if (nexeHeader) {
throw new Error('__nexe cannot be reconfigured')
}
nexeHeader = value
Object.freeze(nexeHeader)
},
enumerable: false,
configurable: false
}
})());
if (resourceSize) {
const originalReadFile = fs.readFile
const originalReadFileSync = fs.readFileSync
let setupManifest = () => {
const manifest = process.__nexe && process.__nexe.resources
if (!manifest) {
return
}
Object.keys(manifest).forEach((key) => {
const absolutePath = path.resolve(key)
if (!manifest[absolutePath]) {
manifest[absolutePath] = manifest[key]
}
const normalizedPath = path.normalize(key)
if (!manifest[normalizedPath]) {
manifest[normalizedPath] = manifest[key]
}
})
normalizeManifest = () => {}
};
fs.readFile = function readFile (file, options, callback) {
setupManifest()
const manifest = process.__nexe
const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) {
return originalReadFile.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const encoding = isString(options) ? options : null
callback = typeof options === 'function' ? options : callback
fs.open(process.execPath, 'r', function (err, fd) {
if (err) return callback(err, null)
fs.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function (error, bytesRead, buffer) {
if (error) {
return fs.close(fd, function () {
callback(error, null)
})
}
fs.close(fd, function (err) {
if (err) {
return callback(err, buffer)
}
const result = Buffer.from(buffer.toString(), 'base64')
callback(err, encoding ? result.toString(encoding) : result)
})
})
})
}
fs.readFileSync = function readFileSync (file, options) {
setupManifest()
const manifest = process.__nexe
const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) {
return originalReadFileSync.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const encoding = isString(options) ? options : null
const fd = fs.openSync(process.execPath, 'r')
const result = Buffer.alloc(length)
fs.readSync(fd, result, 0, length, resourceOffset)
fs.closeSync(fd)
const contents = Buffer.from(result.toString(), 'base64')
return encoding ? contents.toString(encoding) : contents
}
}
const contentBuffer = Buffer.from(Array(contentSize));
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
fs.closeSync(fd);
const Module = require('module');
process.mainModule = new Module(process.execPath, null);
process.mainModule.loaded = true;
process.mainModule._compile(contentBuffer.toString(), process.execPath);
`.trim()
'{{replace:lib/patches/boot-nexe.js}}'
)
return next()
}
-1
View File
@@ -32,7 +32,6 @@ async function getJson<T>(url: string, options?: any) {
return JSON.parse((await got(url, options)).body) as T
}
//TODO only build the latest of each major...?
function isBuildableVersion(version: string) {
const major = +version.split('.')[0]
return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4'
+1 -1
View File
@@ -14,7 +14,7 @@ function readDirAsync(dir: string): Promise<string[]> {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : path as any))
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : (path as any)))
})
).then(result => {
return [].concat(...(result as any))
+16 -13
View File
@@ -1,42 +1,45 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } from 'fuse-box'
import { readFileAsync, writeFileAsync } from '../util'
import { resolve } from 'path'
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',
target: 'server@esnext',
uglify: true,
bakeApiIntoBundle: options.name
})
)
}
const cwd = options.cwd
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_LOG) || false,
homeDir: options.cwd,
homeDir: cwd,
sourceMaps: false,
writeBundles: false,
output: '$name.js',
target: 'server',
target: 'server@esnext', //try not to transpile
plugins
})
fuse.bundle(options.name).instructions(`> ${options.input}`)
return fuse.run().then(x => {
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 => (output = y.context.output.lastPrimaryOutput.content!.toString()))
x.bundles.forEach((y: any) => (output = y.context.output.lastPrimaryOutput.content.toString()))
return output
})
}
export default async function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
compiler.input = await readFileAsync(compiler.options.input, 'utf-8')
const { bundle, cwd, empty, input } = compiler.options
if (!bundle) {
compiler.input = await readFileAsync(resolve(cwd, input), 'utf-8')
return next()
}
@@ -46,14 +49,14 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
}
let producer = createBundle
if (typeof compiler.options.bundle === 'string') {
producer = require(resolve(compiler.options.cwd, compiler.options.bundle)).createBundle
if (typeof bundle === 'string') {
producer = require(resolve(cwd, bundle)).createBundle
}
compiler.input = await producer(compiler.options)
if ('string' === typeof compiler.options.debugBundle) {
await writeFileAsync(compiler.options.debugBundle, compiler.input)
await writeFileAsync(resolve(cwd, compiler.options.debugBundle), compiler.input)
}
return next()
+28 -22
View File
@@ -1,19 +1,22 @@
import { normalize } from 'path'
import { normalize, relative } from 'path'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { createWriteStream, chmodSync, statSync } from 'fs'
import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
function getStdIn(stdin: NodeJS.ReadStream): Promise<string> {
return new Promise(resolve => {
let input = ''
stream.setEncoding('utf-8')
stream.on('data', (x: string) => {
input += x
})
stream.once('end', () => resolve(dequote(input)))
stream.resume && stream.resume()
let out = ''
stdin
.setEncoding('utf8')
.on('readable', () => {
let current
while ((current = stdin.read())) {
out += current
}
})
.on('end', () => resolve(out))
})
}
@@ -32,20 +35,15 @@ function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { output } = compiler.options
const { log, input: bundledInput } = compiler
if (!process.stdin.isTTY) {
log.step('Using stdin as input')
compiler.input = await readStreamAsync(process.stdin)
const { log } = compiler
let stdInUsed = false
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
stdInUsed = true
compiler.input = dequote(await getStdIn(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)
@@ -58,8 +56,16 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
if (e) {
reject(e)
} else if (compiler.output) {
chmodSync(compiler.output, '755')
step.log(`Executable written to: ${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}`
)
resolve(compiler.quit())
}
})
+2 -9
View File
@@ -18,15 +18,8 @@ export default async function resource(compiler: NexeCompiler, next: () => Promi
count++
step.log(`Including file: ${file}`)
const contents = await readFileAsync(file)
const commentSafeContents = contents.toString('base64')
resources.index[file] = [
Buffer.byteLength(resources.bundle),
Buffer.byteLength(commentSafeContents)
]
resources.bundle += commentSafeContents
compiler.addResource(file, contents)
})
step.log(
`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`
)
step.log(`Included ${count} file(s). ${(resources.bundle.byteLength / 1e6).toFixed(3)} MB`)
return next()
}
+207
View File
@@ -0,0 +1,207 @@
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 originalCreateReadStream = fs.createReadStream
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 as string)
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)
})
})
})
},
createReadStream: function createReadStream(file: any, options: any) {
setupManifest()
const entry = manifest[file]
if (!entry || !isString(file)) {
return originalCreateReadStream.apply(fs, arguments)
}
const [offset, length] = entry
const resourceOffset = resourceStart + offset
const opts = !options ? {} : isString(options) ? { encoding: options } : options
return fs.createReadStream(
process.execPath,
Object.assign({}, opts, {
start: resourceOffset,
end: resourceOffset + length
})
)
},
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
}
}
+28 -9
View File
@@ -1,18 +1,37 @@
import { NexeCompiler } from '../compiler'
function wrap(code: string) {
return '!(function () {' + code + '})();'
}
export default function(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.options.fakeArgv) {
return next()
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}}'))
}
const nty = !process.stdin.isTTY
const input = nty ? '[stdin]' : compiler.options.input
//compiler.shims.push(wrap('{/{replace:lib/steps/shim-require.js}}'))
compiler.input =
`!(() => {
var r = require('path').resolve;
process.argv.splice(1,0, ${nty ? `'${input}'` : `r("${input}")`});
})();` + compiler.input
if (compiler.options.fakeArgv !== false) {
const nty = !process.stdin.isTTY
const input = nty ? '[stdin]' : JSON.stringify(compiler.options.input)
compiler.shims.push(
wrap(`
var r = require('path').resolve;
process.argv.splice(1,0, ${nty ? `'${input}'` : `r(${input})`});`)
)
}
return next()
}
+3 -1
View File
@@ -28,6 +28,7 @@ const prettyPlatform: { [key: string]: NodePlatform } = {
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
amd64: 'x64',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
@@ -63,7 +64,8 @@ export function targetsEqual(a: NexeTarget, b: NexeTarget) {
}
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
let arch = process.arch as NodeArch,
const currentArch = process.arch
let arch = currentArch in prettyArch ? prettyArch[process.arch] : (process.arch as NodeArch),
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
+17
View File
@@ -26,6 +26,22 @@ 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("'")
@@ -62,6 +78,7 @@ function isDirectoryAsync(path: string) {
export {
dequote,
padRight,
bound,
isWindows,
rimrafAsync,
statAsync,
+21 -25
View File
@@ -1,25 +1,23 @@
import * as nexe from '../src/nexe'
import {
getUnBuiltReleases,
getLatestGitRelease
} from '../src/releases'
import * as nexe from '../lib/nexe'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import * as ci from './ci'
import { runAlpineBuild } from './docker'
import { getTarget } from '../src/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src/util'
import { getTarget } from '../lib/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../lib/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.CIRCLECI),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.TRAVIS),
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER)
|| Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
|| Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
'Authorization': 'token ' + env.GITHUB_TOKEN,
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)'
}
@@ -32,7 +30,7 @@ if (require.main === module) {
}
}
async function build () {
async function build() {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
@@ -54,8 +52,7 @@ 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,
@@ -63,16 +60,15 @@ async function build () {
target,
output
}
const stop = keepalive()
if (target.platform === 'alpine') {
await runAlpineBuild(target, nexe.version.split('.')[0])
await runAlpineBuild(target)
} else {
await nexe.compile(options)
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
@@ -80,7 +76,7 @@ async function build () {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
Authorization: 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
@@ -90,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
+32 -29
View File
@@ -1,34 +1,13 @@
import { NexeTarget } from '../src/releases'
import { NexeTarget } from '../lib/releases'
import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerMacBuild(release: NexeTarget, branch: string) {
assert.ok(env.TRAVIS_TOKEN)
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
return got(travis, {
json: true,
body: {
request: {
branch: branch,
config: {
merge_mode: 'deep_merge',
env: {
NEXE_VERSION: release.toString()
}
}
}
},
headers: {
'Travis-API-Version': '3',
'Authorization': `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerDockerBuild (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}`
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: {
@@ -39,10 +18,34 @@ export function triggerDockerBuild (release: NexeTarget, branch: string) {
})
}
export function triggerWindowsBuild (release: NexeTarget) {
export function triggerDockerBuild(release: NexeTarget, branch: string) {
assert.ok(env.TRAVIS_TOKEN)
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
return got(travis, {
json: true,
body: {
request: {
branch: branch,
config: {
merge_mode: 'deep_merge',
env: {
//use matrix so that secure global variable is merged
matrix: {
NEXE_VERSION: release.toString()
}
}
}
}
},
headers: {
'Travis-API-Version': '3',
Authorization: `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerWindowsBuild(release: NexeTarget) {
const hasVersion = 'NEXE_VERSION' in env
env.NEXE_VERSION = hasVersion
? env.NEXE_VERSION!.trim()
: release.toString()
env.NEXE_VERSION = hasVersion ? env.NEXE_VERSION!.trim() : release.toString()
return Promise.resolve()
}
+35 -49
View File
@@ -1,73 +1,59 @@
import { NexeTarget } from '../src/target'
import { writeFileAsync, readFileAsync } from '../src/util'
import { NexeTarget } from '../lib/target'
import { writeFileAsync, readFileAsync } from '../lib/util'
import { spawn } from 'child_process'
import got = require('got')
import { createWriteStream, WriteStream } from 'fs'
import execa = require('execa')
import { appendFileSync } from 'fs'
function alpine (target: NexeTarget, nexeVersion: string) {
function alpine(target: NexeTarget) {
return `
FROM i386/alpine:3.4
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
ENV NODE_VERSION=${target.version}
ENV NEXE_VERSION=beta
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
cd /node-v\${NODE_VERSION} && \
mkdir /nexe_temp && mv /node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
cd /nexe_temp/\${NODE_VERSION} && \
./configure --prefix=/usr --fully-static && \
make -j$(getconf _NPROCESSORS_ONLN) && \
make install && \
make && make install && \
paxctl -cm /usr/bin/node
RUN mkdir /nexe_temp && mv node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
`.trim()
}
export async function runAsync (command: string, output: WriteStream) {
const commands = command.split(/\r?\n/)
.filter(x => x.trim())
.map(x => x.trim().split(' '))
const sequence = Promise.resolve()
for (const command of commands) {
await new Promise((resolve, reject) => {
const cp = spawn(command.shift() as string, command)
cp.stderr.pipe(output)
cp.stdout.pipe(output)
const close = function (this: any, e: Error) {
this.kill()
e ? reject(e) : resolve()
}
cp.on('error', (e) => {
output.write(e.stack)
})
.on('close', close)
.on('exit', close)
})
}
}
export async function runAlpineBuild (target: NexeTarget, nexeVersion: string) {
await writeFileAsync('Dockerfile', alpine(target, nexeVersion))
export async function runAlpineBuild(target: NexeTarget) {
await writeFileAsync('Dockerfile', alpine(target))
const outFilename = 'nexe-alpine-build-log.txt'
const output = createWriteStream(outFilename)
await writeFileAsync(outFilename, '')
let output: any = []
try {
await runAsync(`
docker build -t nexe-alpine .
docker run -d --name nexe nexe-alpine sh
docker cp nexe:/out out
docker rm nexe
`, output)
} catch(e) {
console.log(e)
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.close()
await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
body: await readFileAsync(outFilename),
method: 'PUT'
output.forEach((x: any) => {
appendFileSync(outFilename, x.stderr)
appendFileSync(outFilename, x.stdout)
})
.then(x => console.log(x.body))
.catch(e => console.log('Error posting log', e))
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))
}
}
+28
View File
@@ -0,0 +1,28 @@
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}`)
}
+3
View File
@@ -0,0 +1,3 @@
{
"main": "entry-file.js"
}
+1
View File
@@ -1,3 +1,4 @@
./test/**/*.spec.ts
--check-leaks
--require ts-node/register
--recursive
+48
View File
@@ -0,0 +1,48 @@
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}`))
})
})
})
+2
View File
@@ -10,7 +10,9 @@ 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`],
+1
View File
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target":"es5",
"experimentalDecorators": true,
"lib": ["es2017"],
"module": "commonjs",
"outDir": "./lib",
+3071
View File
File diff suppressed because it is too large Load Diff