Compare commits

..

80 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
calebboyd 9906df8d55 chore: bump rc6 2017-09-13 19:08:44 -05:00
calebboyd d685ec21e5 fix: custom bundle resolution 2017-09-13 19:07:54 -05:00
calebboyd a2c4f3e9f1 fix: use cwd directory name for output resolution before timestring 2017-09-13 18:43:26 -05:00
calebboyd 79bcb753b4 fix: native module fuse plubin 2017-09-11 01:20:37 -05:00
calebboyd bc2a637cc4 docs: cleanup 2017-09-10 20:18:37 -05:00
calebboyd ad279e6daf docs: clarify resource and build documentation 2017-09-09 14:08:04 -05:00
calebboyd 2b9a8b1f0e feat: fake-argv option 2017-09-09 02:02:52 -05:00
calebboyd 7a7aaefa23 fix: set input before name (so name can derive from input) 2017-09-08 23:44:26 -05:00
calebboyd 04fdd165a4 feat: productversion and fileversion replacements in node-rc 2017-09-08 14:29:19 -05:00
calebboyd 455d6893fd fix: cache for build vs target 2017-09-08 08:13:40 -05:00
calebboyd ee529ce9b5 chore: fix target parsing 2017-09-07 22:56:30 -05:00
calebboyd 280b93b0a8 chore: add debugBundle to write out bundle 2017-09-05 23:41:25 -05:00
calebboyd 9ecd84ff8f chore: update fuse-box in example 2017-09-05 23:01:23 -05:00
calebboyd eaaffc7221 chore: rc2 2017-09-04 14:46:06 -05:00
calebboyd 75651af5f9 chore: publish beta 2017-09-04 14:46:06 -05:00
calebboyd c0b6575da0 feat: build-target options 2017-09-04 14:46:06 -05:00
calebboyd a9de25ba1f docs: update readme with new usage 2017-09-04 14:46:06 -05:00
calebboyd c7af89f12c fix: don't call next twice 2017-09-04 14:46:06 -05:00
calebboyd 7d0cdd7a50 feat: targets 2017-09-04 14:46:06 -05:00
calebboyd 9e5ab3e099 chore: options, and resource normalization 2017-09-04 14:46:06 -05:00
calebboyd c6d057ac43 feat: initial build 2017-09-04 14:46:06 -05:00
calebboyd 5df6840972 chore: inject source-map-support 2017-09-04 14:46:06 -05:00
calebboyd a0d859921d feat: initial fuse-box bundling support 2017-09-04 14:46:06 -05:00
calebboyd fcd803686c chore: test appveyor build 2017-08-24 00:12:18 -05:00
calebboyd ba78c7dc54 refactor: append in lieu of inserting payload 2017-08-23 22:35:01 -05:00
Jared Allard 430d63e7eb readme: update build link 2017-08-14 14:43:00 -07:00
calebboyd 3e435ecbf8 chore: beta 6 2017-08-13 17:34:54 -05:00
David Lechner aeefcbeba3 chore: add newline to end of command line help text (#361)
Without this, when you run `nexe --help` in a terminal, your shell prompt will be on the same line as `option can be used more than once`.
2017-08-13 16:20:23 -05:00
86 changed files with 7400 additions and 4390 deletions
+2 -1
View File
@@ -4,5 +4,6 @@ end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace=true
#https://github.com/Microsoft/vscode/issues/23983#event-1087502108
#trim_trailing_whitespace = true
insert_final_newline = true
+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
View File
@@ -0,0 +1,12 @@
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
+14 -1
View File
@@ -2,5 +2,18 @@
"typescript.tsdk": "./node_modules/typescript/lib",
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"]
"prettier.typescriptEnable": ["typescript"],
"editor.trimAutoWhitespace": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"search.exclude": {
"lib/**/*": true,
"**/node_modules": true,
"**/bower_components": true
}
}
+3 -3
View File
@@ -5,9 +5,9 @@
Nexe 2.0 is a rewrite to enable some new features. These include:
* Quick Builds!
* Userland build patches
* New Resource access
* stdin and stdout interfaces
* Opt-in bundling
* Resource storage/access rewrite
* stdin interface
* Optional, pluggable bundling
### Breaking Changes
* New options -- Please see the [readme](README.md#options)
+117 -67
View File
@@ -1,22 +1,23 @@
<p align="center"><img src="https://cloud.githubusercontent.com/assets/2391349/23598327/a17bb68a-01ee-11e7-8f55-88a5fc96e997.png" /></p>
<p align="center">
<a href="https://circleci.com/gh/jaredallard/nexe"><img src="https://img.shields.io/circleci/project/jaredallard/nexe.svg" alt="Build Status"></a>
<a href="https://circleci.com/gh/nexe/nexe"><img src="https://img.shields.io/circleci/project/nexe/nexe.svg" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/dt/nexe.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/v/nexe.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/l/nexe.svg" alt="License"></a>
<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@beta -g</code></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
- Supports production ready, ([securable](#security)) builds
- Self contained applications
- Ability to run multiple applications with *different* node.js runtimes.
- Distribute binaries without needing node / npm.
- Idempotent builds
@@ -25,43 +26,44 @@
- Flexible build pipeline
- Cross platform builds
## Usage (Beta)
## Usage
*Note: V2 API is still subject to change. * For v1 see [V1-EOL](https://github.com/nexe/nexe/tree/V1-EOL)
- Application entrypoint:
- Existing application bundle:
`nexe -i ./my-app-bundle.js -o ./my-app.exe`
`nexe my-app.js`
- 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`
## Including Additional Resources
### Examples
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`
- `nexe server.js -r public/**/*.html`
- `nexe my-bundle.js --no-bundle -o app.exe`
- `nexe --build`
- `nexe -t x86-8.0.0`
## Resources
Additional files or resources can be added to the binary by passing `-r "glob/pattern/**/*"`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`.
## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. These options are supported out of the box, and subsequent builds with compatible options will result in instant build times.
Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
By default `nexe` will attempt to download a pre-built executable. However, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptable version of python 2. eg. Systems that have python2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486).
## Node.js API
Using Nexe Programatically
#### Example
```javascript
const nexe = require('nexe')
const { compile } = require('nexe')
nexe.compile({
input: './my-app-bundle.js'
output: './my-app.exe'
compile({
input: './my-app.js',
build: true, //required to use patches
patches: [
async (compiler, next) => {
await compiler.setFileContentsAsync(
@@ -75,52 +77,65 @@ nexe.compile({
console.log('success')
})
```
## NexeOptions
### `options`
### `options: object`
- #### `build: boolean`
- Build node from source (required in beta)
- #### `input: string`
- Input bundle file path
- default: stdin or the current directory's main file (package.json)
- #### `output: string`
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string`
- Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'`
- default: `[process.platform, process.arch, process.version.slice(1)].join('-')`
- #### `bundle: string`
- Path to a custom bundling function. When executed it should either return a fusebox producer, or a string.
- If it is a string it will be required and the export `nexeBundle` will be used
- Defaults to the internal fuse-box configuration
- #### `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`
- #### `bundle: string | boolean`
- If a string is provided it must be a valid relative module path
and should provide an export with the following signature:
```typescript
export function createBundle (options: NexeOptions): Promise<string>
```
- default: true
- #### `name: string`
- Module friendly name of the application
- default: basename of the input file, or `nexe_${Date.now()}`
- #### `version: string`
- The Node version you're building for
- default: `process.version.slice(1)`
- #### `cwd: string`
- Directory nexe will operate on as though it is the cwd
- default: process.cwd()
- #### `build: boolean`
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
- #### `python: string`
- On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed
- default: `null`
- #### `flags: Array<string>`
- #### `flags: string[]`
- Array of node runtime flags to build node with.
- Example: `['--expose-gc']`
- default: `[]`
- #### `configure: Array<string>`
- #### `configure: string[]`
- Array of arguments for the node build configure step
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: Array<string>`
- Array of arguments for the node build make step
- default: `[]`
- #### `vcBuild: Array<string>`
- Array of arguments for the node build step on windows
- default: `['nosign', 'release']`
- #### `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
- #### `vcBuild: string[]`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
- #### `resources: Array<string>`
- #### `resources: string[]`
- Array of globs with files to include in the build
- Example: `['./public/**/*']`
- default: `[]`
@@ -129,34 +144,50 @@ nexe.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 accompanying configuration and exit
- 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. Should be a `.tar.gz`
- Provide an alternate url for the node source code
- Note: temporary files will still be created for this under the specified version
- #### `loglevel: string`
- Set the loglevel, info, silent, or verbose
- default: `'info'`
- #### `padding`
- Advanced option for controlling the size available in the executable.
- It must be larger than the bundle + resources in bytes
- default: 3, 6, 9, 16, 25, or 40 MB sizes are selected automatically.
- #### `patches: Array<NexePatch>`
- #### `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`
@@ -164,26 +195,45 @@ For examples, see the built in patches: [src/patches](src/patches)
- Quickly set a file's contents within the downloaded Node.js source.
- `replaceInFileAsync(filename: string, ...replaceArgs): Promise<void>`
- Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace`
- `readFileAsync(filename: string): Promise<SourceFile>`
- `readFileAsync(filename: string): Promise<NexeFile>`
- Access (or create) a file within the downloaded Node.js source.
- `files: Array<SourceFile>`
- The cache of currently read, modified, or created files 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.
#### `SourceFile`
#### `NexeFile`
- `contents: string`
- `absPath: string`
- `filename: string`
Any modifications made to `SourceFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
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
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)
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`.
Future: 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.
## Security
A common use case for Nexe is production deployment. When distributing executables it is important to [sign](https://en.wikipedia.org/wiki/Code_signing) them before distributing. Nexe was designed specifically to not mangle the binary it produces, this allows the checksum and signature of the size and location offsets to be maintained through the code signing process.
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
+18
View File
@@ -0,0 +1,18 @@
environment:
CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
install:
- ps: Install-Product node 6
- yarn
build: off
test: off
build_script:
- yarn run asset-compile
artifacts:
- path: .nexe\**\node.exe
cache:
- node_modules
+9
View File
@@ -0,0 +1,9 @@
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:
- yarn run asset-compile
+12 -22
View File
@@ -1,23 +1,13 @@
const { FuseBox } = require('fuse-box')
const nexe = '../..'
const { NativeModulePlugin } = require(nexe + '/lib/bundling/fuse')
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'
]
}
})
]
const nexe = require('../..')
nexe.compile({
output: 'native-build',
silent: true,
native: {
zmq: {
additionalFiles: [
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
]
}
}
})
fuse.bundle('app')
.target('electron')
.instructions(`> index.js`)
fuse.run()
-1
View File
@@ -8,7 +8,6 @@ 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')
+3 -5
View File
@@ -4,15 +4,13 @@
"description": "",
"main": "index.js",
"scripts": {
"bundle": "node build",
"build-windows": "nexe -i app.js -o app.exe"
"build": "node build"
},
"author": "",
"license": "ISC",
"dependencies": {
"sqlite3": "^3.1.10",
"zmq": "^2.15.3"
},
"devDependencies": {
"fuse-box": "^2.2.1-beta.7"
}
"devDependencies": {}
}
+14 -7
View File
@@ -1,10 +1,17 @@
#!/usr/bin/env node
const nexe = require('./lib/nexe')
module.exports = nexe
const options = require('./lib/options')
if (require.main === module) {
nexe.compile(nexe.argv).catch((e) => {
process.stderr.write(e.message, () => 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')
}
+1 -2
View File
@@ -11,7 +11,7 @@ If this is a bug report, What are the steps to reproduce?
```
<br/><br/>
Please also provide:
Please also provide:
- Platform(OS/Version):
- Host Node Version:
@@ -19,4 +19,3 @@ Please also provide:
- Nexe version:
- Python Version:
-2822
View File
File diff suppressed because it is too large Load Diff
+27 -15
View File
@@ -2,25 +2,31 @@
"name": "nexe",
"description": "Create a single executable out of your Node.js application",
"license": "MIT",
"version": "2.0.0-beta.4",
"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": {
"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"
"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",
"url": "git://github.com/nexe/nexe.git"
},
"files": [
"lib"
"lib",
"source-map-support.js"
],
"typings": "lib/nexe.d.ts",
"main": "index.js",
"bin": {
"nexe": "index.js"
@@ -30,29 +36,35 @@
},
"dependencies": {
"app-builder": "^5.1.0",
"bluebird": "^3.5.0",
"typescript": "2.5.3",
"caw": "^2.0.1",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "^2.2.1",
"fuse-box": "^3.1.0",
"globby": "^6.1.0",
"memory-fs": "^0.4.1",
"got": "^8.0.3",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nigel": "^2.0.2",
"ora": "^1.2.0",
"pifi": "1.0.0",
"rimraf": "^2.6.1",
"tar": "^3.1.3"
"uglify-js": "3.0.28"
},
"devDependencies": {
"@types/bluebird": "^3.5.8",
"@types/chai": "^4.0.4",
"@types/chalk": "^0.4.31",
"@types/execa": "^0.7.0",
"@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
"@types/mocha": "^2.2.42",
"@types/ora": "^0.3.31",
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28",
"mocha": "^3.2.0",
"prettier": "^1.4.4",
"typescript": "^2.4.1"
"chai": "^4.1.2",
"execa": "^0.8.0",
"mocha": "^3.5.0",
"prettier": "^1.6.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
File diff suppressed because one or more lines are too long
-62
View File
@@ -1,62 +0,0 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from './util'
import { promisify, map } from 'bluebird'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from './compiler'
const mkdirpAsync = promisify(mkdirp)
const unlinkAsync = (promisify(unlink) as any) as (path: string) => PromiseLike<void>
const readdirAsync = promisify(readdir)
function readDirAsync(dir: string): PromiseLike<string[]> {
return readdirAsync(dir)
.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then((x: boolean) => (x ? readDirAsync(path) : path as any))
})
.reduce((a: string[], b: string[] | string) => a.concat(b), [])
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await map(tmpFiles, async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
await next()
await map(tmpFiles, x => unlinkAsync(x))
return map(compiler.files, async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
}
+5 -4
View File
@@ -1,6 +1,7 @@
import * as fs from 'fs'
import * as path from 'path'
import * as child from 'child_process'
import { createHash } from 'crypto'
function findNativeModulePath(filePath: string, bindingsArg: string) {
const dirname = path.dirname(filePath)
@@ -35,13 +36,13 @@ export class BindingsRewrite {
public nativeModulePaths: string[] = []
public rewrite = false
isRequireBindings(node: any) {
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
isRequire(node: any, moduleName: string) {
return node.callee.name === 'require' && node.arguments[0].value === moduleName
}
onNode(absolutePath: string, node: any, parent: any) {
if (node.type === 'CallExpression') {
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
if (this.isRequire(node, 'bindings') && parent.type === 'VariableDeclarator') {
/**
* const loadBindings = require('bindings');
* -> const loadBindings = String('');
@@ -53,7 +54,7 @@ export class BindingsRewrite {
return
}
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
if (this.isRequire(node, 'bindings') && parent.type === 'CallExpression') {
/**
*const bindings = require('bindings')('native-module')....
* -> const bindings = require('./path/to/native/module.node').....
+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)
`
}
+70
View File
@@ -0,0 +1,70 @@
import { BindingsRewrite } from './bindings-rewrite'
import { embedDotNode, EmbedNodeModuleOptions } from './embed-node'
export interface FuseBoxFile {
info: { absPath: string }
absPath: string
contents: string
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
consume(): void
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export default function(options: EmbedNodeModuleOptions = {}) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test = /node_modules.*(\.js|\.node)$|\.node$/
public limit2Project = false
private modules: (keyof EmbedNodeModuleOptions)[]
constructor(public options = {}) {}
init(context: any) {
context.allowExtension('.node')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
embedDotNode(this.options, file)
return
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}
]
})
}
}
-137
View File
@@ -1,137 +0,0 @@
import { BindingsRewrite } from './bindings-rewrite'
import { createHash } from 'crypto'
import { readFileSync } from 'fs'
import { dirname, join, basename } from 'path'
function hashName(name: string | Buffer) {
return createHash('md5').update(name).digest('hex').toString().slice(0, 8)
}
export interface FuseBoxFile {
info: { absPath: string }
absPath: string
contents: string
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export interface NativeModulePluginOptions {
[key: string]:
| {
additionalFiles: string[]
}
| true
}
export default function(options: NativeModulePluginOptions) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test: RegExp
public limit2Project = false
private modules: (keyof NativeModulePluginOptions)[]
constructor(public options: NativeModulePluginOptions) {
this.modules = Object.keys(options)
this.test = new RegExp(`node_modules\/(${Object.keys(options).join('|')}).*\.js|\.node$`)
}
init(context: any) {
context.allowExtension('.node')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
const contents = readFileSync(file.absPath)
const module = this.modules.find(x =>
Boolean(~file.absPath.indexOf(join('node_modules', x)))
)!
const bindingName = basename(file.absPath)
const settings = this.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 = readFileSync(join(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)
`
return
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}
]
})
}
}
+1
View File
@@ -0,0 +1 @@
export * from './fuse-native-module-plugin'
-31
View File
@@ -1,31 +0,0 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin } from 'fuse-box'
import NativeModulePlugin from './fuse'
function bundleProducer(filename: string) {
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
homeDir: './',
writeBundles: false,
target: 'server',
plugins: [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
})
fuse.bundle('nexe').instructions(`> ${filename}`)
return fuse.run().then(x => {
if (x.bundles.size > 1) {
console.log('THROW', x.bundles)
}
let output = ''
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
})
}
export default function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
return next()
}
if (typeof compiler.options.bundle === 'string') {
}
}
+1
View File
@@ -0,0 +1 @@
//TODO
-73
View File
@@ -1,73 +0,0 @@
import { normalize } from 'path'
import * as Bluebird from 'bluebird'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { readFileAsync, dequote, isWindows } from './util'
import { NexeCompiler } from './compiler'
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
return new Bluebird(resolve => {
let input = ''
stream.setEncoding('utf-8')
stream.on('data', (x: string) => {
input += x
})
stream.once('end', () => resolve(dequote(input)))
stream.resume && stream.resume()
})
}
/**
* The "cli" step detects whether the process is in a tty. If it is then the input is read into memory.
* Otherwise, it is buffered from stdin. If no input options are passed in the tty, the package.json#main file is used.
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
*
* Configuration:
* - compiler.options.input - file path to the input bundle.
* - fallbacks: stdin, package.json#main
* - compiler.options.output - file path to the output executable.
* - fallbacks: stdout, nexe_ + epoch + ext
*
* @param {*} compiler
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { input, output } = compiler.options
const { log } = compiler
if (!input && !process.stdin.isTTY) {
log.step('Using stdin as input')
compiler.input = await readStreamAsync(process.stdin)
} else if (input) {
log.step(`Using input file as the main module: ${input}`)
compiler.input = await readFileAsync(normalize(input), 'utf-8')
} else if (!compiler.options.empty) {
const bundle = require.resolve(process.cwd())
log.step("Using the cwd's main file as the main module")
compiler.input = await readFileAsync(bundle, 'utf-8')
} else {
log.step('Using empty input as the main module')
compiler.input = ''
}
compiler.input = compiler.input.trim()
await next()
compiler.output = output || `${compiler.options.name}${isWindows ? '.exe' : ''}`
const deliverable = await compiler.compileAsync()
return new Bluebird((resolve, reject) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(normalize(compiler.output!)))
.on('error', reject)
.once('close', (e: Error) => {
if (e) {
reject(e)
} else if (compiler.output) {
chmodSync(compiler.output, '755')
step.log(`Executable written to: ${compiler.output}`)
resolve(compiler.quit())
}
})
})
}
+182 -195
View File
@@ -1,73 +1,61 @@
import { normalize, join } from 'path'
import * as Bluebird from 'bluebird'
import { dirname, normalize, join } from 'path'
import { Buffer } from 'buffer'
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { Readable } from 'stream'
import { spawn } from 'child_process'
import { Stream as Needle } from 'nigel'
import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
import { NexeOptions } 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'
import { IncomingMessage } from 'http'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
const configure = isWindows ? 'configure' : './configure'
const marker = Buffer.from('<nexe~sentinel>').toString('hex')
const tail = `\n//${marker}`
const needle = Buffer.from(marker)
const fixedIntegerLength = 10
const padLeft = (x: string | number, l = fixedIntegerLength, c = '0') => (c.repeat(l) + x).slice(-l)
const inflate = (value: string, size: number) => {
if (!size || value.length >= size) {
return value
}
return value + ' '.repeat(size - value.length)
}
export interface NexeFile {
filename: string
absPath: string
contents: string
}
export { NexeOptions }
interface NexeHeader {
/**
* Zero padded number indicating the extra size available in the binary
*/
paddingSize: string
/**
*
*/
binaryOffset: string
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 = join(this.options.temp, this.options.version)
public src: string
public files: NexeFile[] = []
public shims: string[] = []
public input: string
public output: string | null
public resources: { bundle: string; index: { [key: string]: number[] } } = {
public bundledInput?: string
public targets: NexeTarget[]
public target: NexeTarget
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>
private nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
constructor(public options: NexeOptions) {
public output = this.options.output
private nodeSrcBinPath: string
constructor(public options: T) {
const { python } = (this.options = options)
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
this.src = join(this.options.temp, this.target.version)
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + version, 'info')
if (python) {
if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
@@ -75,70 +63,97 @@ 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) {
cachedFile = {
filename: file,
contents: await readFileAsync(join(this.src, file), 'utf-8').catch(
{ code: 'ENOENT' },
() => ''
)
}
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
}
quit(code = 0) {
@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`)
return this.log.flush().then(x => process.exit(code))
this.log.write(`Finished in ${time / 1000}s`)
return this.log.flush()
}
private _findPaddingSize(size: number, override = this.options.padding) {
if (override === 0) {
return 0
assertBuild() {
if (!this.options.build) {
throw new Error('This feature is only available with `--build`')
}
size = override > size ? override : size
const padding = [3, 6, 9, 16, 25, 40].map(x => x * 1e6).find(p => size <= p)
if (!padding) {
throw new Error(
`No prebuilt target large enough (${(size / 1024).toFixed(
2
)}Mb).\nUse the --build flag and build for the current platform`
)
}
return padding
}
private _getNodeExecutableLocation(target?: string | null) {
public getNodeExecutableLocation(target?: NexeTarget) {
if (target) {
return join(this.options.temp, target)
return join(this.options.temp, target.toString())
}
return this.nodeSrcBinPath
}
private _runBuildCommandAsync(command: string, args: string[]) {
return new Bluebird((resolve, reject) => {
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()
})
})
}
@@ -150,136 +165,108 @@ export class NexeCompiler {
}
private async _buildAsync() {
this.compileStep.log(`Configuring node build: ${this.options.configure}`)
this.compileStep.log(
`Configuring node build${
this.options.configure.length ? ': ' + this.options.configure : '...'
}`
)
await this._configureAsync()
const buildOptions = isWindows ? this.options.vcBuild : this.options.make
this.compileStep.log(`Compiling Node with arguments: ${buildOptions}`)
const buildOptions = this.options.make
this.compileStep.log(
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
)
await this._runBuildCommandAsync(make, buildOptions)
return createReadStream(this._getNodeExecutableLocation())
return createReadStream(this.getNodeExecutableLocation())
}
private _fetchPrebuiltBinaryAsync() {
return this._buildAsync()
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
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)
if (!asset) {
throw new Error(`${assetName} not available, create it using the --build flag`)
}
const filename = this.getNodeExecutableLocation(target)
await download(asset.browser_download_url, dirname(filename), 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 _getPayload(header: NexeHeader) {
return this._serializeHeader(header) + this.input + '/**' + this.resources.bundle + `**/`
}
private _generateHeader() {
const zeros = padLeft(0)
getHeader() {
const version =
['configure', 'vcBuild', 'make'].reduce((a, c) => {
return (a += (this.options as any)[c].slice().sort().join())
return (a += (this.options as any)[c]
.slice()
.sort()
.join())
}, '') + this.options.enableNodeCli
const header = {
version: padLeft(0, 32),
resources: this.resources.index,
contentSize: zeros,
paddingSize: zeros,
resourceOffset: zeros,
binaryOffset: zeros
version: createHash('md5')
.update(version)
.digest('hex')
}
const serializedHeader = this._serializeHeader(header)
header.contentSize = padLeft(Buffer.byteLength(this._getPayload(header)))
header.paddingSize = padLeft(this._findPaddingSize(+header.contentSize))
header.resourceOffset = padLeft(Buffer.byteLength(serializedHeader + this.input + '/**'))
header.version = createHash('md5').update(version + header.paddingSize).digest('hex')
return header
return `process.__nexe=${JSON.stringify(header)};`
}
private async _getExistingBinaryHeaderAsync(target: string | undefined | null) {
const filename = this._getNodeExecutableLocation(target)
const existingBinary = await pathExistsAsync(filename)
if (existingBinary) {
return this._extractHeaderAsync(filename)
}
return null
}
private _extractHeaderAsync(path: string): Promise<NexeHeader> {
const binary = createReadStream(path)
const haystack = new Needle(needle)
let needles = 0
let stackCache: Buffer[] = []
return new Promise((resolve, reject) => {
binary
.on('error', reject)
.pipe(haystack)
.on('error', reject)
.on('close', () => reject(new Error(`Binary: ${path} is not compatible with nexe`)))
.on('haystack', (x: Buffer) => needles && stackCache.push(x))
.on('needle', () => {
if (++needles === 2) {
resolve(JSON.parse(Buffer.concat(stackCache).toString()))
binary.close()
haystack.end()
}
})
})
}
private _serializeHeader(header: NexeHeader) {
return `/**${marker}${JSON.stringify(header)}${marker}**/process.__nexe=${JSON.stringify(
header
)};`
}
async setMainModule(compiler: NexeCompiler, next: () => Promise<void>) {
await next()
const header = compiler._generateHeader()
const contents = inflate(this._getPayload(header), +header.paddingSize) + tail
const file = await compiler.readFileAsync('lib/_third_party_main.js')
file.contents += '\n' + contents
}
async compileAsync() {
async compileAsync(target: NexeTarget) {
const step = (this.compileStep = this.log.step('Compiling result'))
let target = this.options.targets.slice().shift()
let prebuiltBinary = null
const header = this._generateHeader()
target = target && `${target}-${header.version.slice(0, 6)}`
step.log(`Scanning existing binary...`)
const existingHeader = await this._getExistingBinaryHeaderAsync(target)
if (existingHeader && existingHeader.version === header.version) {
const location = this._getNodeExecutableLocation(target)
step.log(`Source already built: ${location}`)
prebuiltBinary = createReadStream(location)
const build = this.options.build
const location = this.getNodeExecutableLocation(build ? undefined : target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
if (!build && !binary) {
step.modify('Fetching prebuilt binary')
binary = await this._fetchPrebuiltBinaryAsync(target)
}
if (target) {
throw new Error('\nNot Implemented, use --build during beta\n')
// prebuiltBinary = await this._fetchPrebuiltBinaryAsync(target)
}
if (!prebuiltBinary) {
prebuiltBinary = await this._buildAsync()
if (!binary) {
binary = await this._buildAsync()
step.log('Node binary compiled')
}
return this._assembleDeliverable(header, prebuiltBinary)
return this._assembleDeliverable(binary)
}
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
const haystack = new Needle(Buffer.concat([Buffer.from('/**'), needle]))
code() {
return [this.shims.join(''), this.input].join(';')
}
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
if (this.options.empty) {
return binary
}
const artifact = new Readable({ read() {} })
let needles = 0
let currentStackSize = 0
binary.pipe(haystack)
haystack
.on('close', () => artifact.push(null))
.on('needle', () => ++needles && haystack.needle(needle))
.on('haystack', (x: Buffer) => {
if (!needles) {
currentStackSize += x.length
artifact.push(x)
}
if (needles === 1 && !+header.binaryOffset) {
header.binaryOffset = padLeft(currentStackSize)
const content = Buffer.from(inflate(this._getPayload(header), +header.paddingSize) + tail)
artifact.push(content)
}
if (needles > 2) {
artifact.push(x)
}
})
binary.on('data', (chunk: Buffer) => {
artifact.push(chunk)
})
binary.on('close', () => {
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(this.resources.bundle.byteLength, 8)
artifact.push(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths]))
artifact.push(null)
})
return artifact
}
}
+12 -5
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
@@ -46,20 +48,25 @@ export class Logger {
this.ora.color = color
}
step(text: string): LogStep {
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
if (method !== 'succeed') {
this.ora[method]()
}
} else {
this.ora.succeed().text = text
this.ora[method]().text = text
this.ora.start()
}
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
}
}
+47 -20
View File
@@ -1,32 +1,59 @@
import { compose, PromiseConfig, Middleware } from 'app-builder'
import resource from './resource'
import { NexeCompiler } from './compiler'
import { argv, normalizeOptionsAsync, NexeOptions } from './options'
import cli from './cli'
import download from './download'
import artifacts from './artifacts'
import patches from './patches'
import { EOL } from 'os'
import { compose, Middleware } from 'app-builder'
import { rimrafAsync } from './util'
import * as Bluebird from 'bluebird'
import { NexeTarget } from './target'
import { NexeCompiler } from './compiler'
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'
PromiseConfig.constructor = Bluebird
async function compile(compilerOptions: NexeOptions, callback?: (err: Error | null) => void) {
const options = await normalizeOptionsAsync(compilerOptions)
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = normalizeOptions(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 directory and contents at: ${compiler.src}`)
await rimrafAsync(compiler.src)
step.log(`Deleted directory and contents at: ${compiler.src}`)
step.log(`Deleting contents at: ${path}`)
await rimrafAsync(path)
step.log(`Deleted contents at: ${path}`)
return compiler.quit()
}
const buildSteps = build ? [download, artifacts, ...patches, ...options.patches] : []
const nexe = compose(resource, cli, buildSteps)
return nexe(compiler).asCallback(callback)
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
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 }
export { compile, NexeCompiler }
export { argv, version, NexeOptions, help } from './options'
+180 -120
View File
@@ -1,8 +1,14 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { basename, extname, join } from 'path'
import * as Bluebird from 'bluebird'
import { EOL } from 'os'
import { isWindows, padRight } from './util'
import { basename, extname, join, isAbsolute, relative, dirname, resolve } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL, homedir } from 'os'
import * as c from 'chalk'
const caw = require('caw')
export const version = '{{replace:0}}'
export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
@@ -10,130 +16,118 @@ export interface NexePatch {
export interface NexeOptions {
build: boolean
/**
* Entrypoint filepath
*/
input: string
output: string
targets: string[]
/**
* Build name, Used for executable, and stacktraces
*/
targets: (string | NexeTarget)[]
name: string
/**
* The node version to be built
*/
version: string
python?: string
/**
* Node flags e.g. "--expose-gc" baked into the executable
*/
cwd: string
flags: string[]
/**
* Pass configuration options to node build configure script
*/
configure: string[]
/**
* Pass make options to node make script
*/
make: string[]
vcBuild: string[]
make: string[]
snapshot?: string
/**
* Array of glob strings describing resources to pull into the bundle
*/
resources: string[]
/**
* Temporary directory where nexe artifacts will be cached
* TODO dot folder in home dir
*/
temp: string
ico?: string
rc: { [key: string]: string }
/**
* Causes nexe to remove all temporary files for current configuration
*/
clean: boolean
enableNodeCli: boolean
sourceUrl?: string
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
fakeArgv?: boolean
verbose?: boolean
info?: boolean
padding: number
patches: NexePatch[]
empty: boolean
ico?: string
debugBundle?: boolean
warmup?: string
downloadOptions?: any
compress?: boolean
clean?: boolean
/**
* Api Only
*/
downloadOptions: any
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
const defaults = {
temp: process.env.NEXE_TEMP || join(process.cwd(), '.nexe'),
version: process.version.slice(1),
flags: [],
cwd: process.cwd(),
configure: [],
make: [],
targets: [],
vcBuild: ['nosign', 'release'],
vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: false,
compress: false,
build: false,
bundle: true,
build: true,
patches: []
patches: [],
plugins: []
}
const alias = {
i: 'input',
o: 'output',
t: 'target',
n: 'name',
v: 'version',
t: 'target',
b: 'build',
n: 'name',
r: 'resource',
a: 'resource',
p: 'python',
f: 'flag',
c: 'configure',
m: 'make',
vc: 'vcBuild',
s: 'snapshot',
cli: 'enableNodeCli',
h: 'help',
l: 'loglevel'
l: 'loglevel',
'fake-argv': 'fakeArgv',
'gh-token': 'ghToken'
}
const argv = parseArgv(process.argv, { alias, default: defaults })
const help = `
nexe --help CLI OPTIONS
const argv = parseArgv(process.argv, { alias, default: { ...defaults, enableStdIn: true } })
const g = c.gray
let help = `
${c.bold('nexe <entry-file> [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 command
-m --make ="--loglevel" -- *pass arguments to the make command
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
-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
--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
${c.underline.bold('Options:')}
-* variable key name * option can be used more than once`.trim()
-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
function flattenFilter(...args: any[]): string[] {
${c.underline.bold('Building from source:')}
-b --build -- build from source
-p --python -- python2 (as python) executable path
-f --flag -- *v8 flags to include during compilation
-c --configure -- *arguments to the configure step
-m --make -- *arguments to the make/build step
--snapshot -- path to a warmup snapshot
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--enableNodeCli -- enable node cli enforcement (blocks app cli)
${c.underline.bold('Other options:')}
--bundle -- custom bundling module with 'createBundle' export
--temp -- temp file storage default '~/.nexe'
--cwd -- set the current working directory for the command
--fake-argv -- fake argv[1] with entry file
--clean -- force download of sources
--silent -- disable logging
--verbose -- set logging to verbose
-* variable key name * option can be used more than once`.trim()
help = EOL + help + EOL
function flatten(...args: any[]): string[] {
return ([] as string[]).concat(...args).filter(x => x)
}
@@ -154,14 +148,14 @@ function extractCliMap(match: RegExp, options: any) {
}, {})
}
function tryResolveMainFileName() {
function tryResolveMainFileName(cwd: string) {
let filename
try {
const file = require.resolve(process.cwd())
const file = require.resolve(cwd)
filename = basename(file).replace(extname(file), '')
} catch (_) {}
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
return filename ? filename : 'nexe_' + Date.now()
}
function extractLogLevel(options: NexeOptions) {
@@ -171,44 +165,110 @@ function extractLogLevel(options: NexeOptions) {
return 'info'
}
function isName(name: string) {
return name && name !== 'index'
}
function extractName(options: NexeOptions) {
let name = options.name
if (typeof options.input === 'string' && !name) {
if (!isName(name) && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '')
}
name = name || tryResolveMainFileName()
if (!isName(name)) {
name = tryResolveMainFileName(options.cwd)
}
if (!isName(name) && basename(options.cwd)) {
name = basename(options.cwd)
}
return name.replace(/\.exe$/, '')
}
function normalizeOptionsAsync(input: Partial<NexeOptions>) {
if (argv.help || argv._.some((x: string) => x === 'version')) {
process.stderr.write(argv.help ? help : '2.0.0-beta.4' + EOL, () => process.exit(0))
}
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
delete opts._
options.loglevel = extractLogLevel(options)
options.name = extractName(options)
options.flags = flattenFilter(opts.flag, options.flags)
options.targets = flattenFilter(opts.target, options.targets)
options.make = flattenFilter(options.make)
options.vcBuild = flattenFilter(options.vcBuild)
options.configure = flattenFilter(options.configure)
options.resources = flattenFilter(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (options.build || options.padding === 0) {
options.targets = []
options.build = true
} else if (!options.targets.length) {
const defaultTarget = [process.platform, process.arch, options.version].join('-')
options.targets = [defaultTarget]
}
Object.keys(alias).filter(k => k !== 'rc').forEach(x => delete opts[x])
return Promise.resolve(options)
function isEntryFile(filename?: string): filename is string {
return Boolean(
filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
)
}
export { argv, normalizeOptionsAsync }
export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
if (input) {
return resolve(cwd, input)
}
if (isEntryFile(maybeEntry)) {
return resolve(cwd, maybeEntry)
}
if (!process.stdin.isTTY) {
return ''
}
try {
return require.resolve(cwd)
} catch (e) {
throw new Error('No entry file found')
}
}
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
const cwd = (options.cwd = resolve(options.cwd))
options.temp = options.temp
? resolve(cwd, options.temp)
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
const maybeEntry = 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)
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
if (isWindows) {
options.make = Array.from(new Set(options.make.concat(arch)))
} else {
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
}
}
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.plugins = 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
}
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)
+1 -1
View File
@@ -1,7 +1,7 @@
import { NexeCompiler } from '../compiler'
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.options.version.startsWith('8.2')) {
if (!compiler.target.version.startsWith('8.2')) {
return next()
}
+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()
}
+4 -1
View File
@@ -12,7 +12,10 @@ export default async function nodeGyp(
nodeGypMarker,
`
${nodeGypMarker}
${files.filter(x => x.filename.startsWith('lib')).map(x => `'${x.filename}'`).toString()},
${files
.filter(x => x.filename.startsWith('lib'))
.map(x => `'${x.filename}'`)
.toString()},
`.trim()
)
}
+3 -12
View File
@@ -1,21 +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,
(compiler: NexeCompiler, next: () => Promise<void>) => compiler.setMainModule(compiler, next),
nexePatches,
buildFixes,
cli,
flags,
ico,
rc
]
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc]
export default patches
+9 -1
View File
@@ -12,7 +12,15 @@ 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.replace(new RegExp(`VALUE "${key}",*`), `VALUE "${key}", ${value}`)
file.contents = file.contents.replace(
new RegExp(`VALUE "${key}",.*`),
`VALUE "${key}", ${value}`
)
})
;['PRODUCTVERSION', 'FILEVERSION'].forEach(x => {
if (options[x]) {
file.contents = file.contents.replace(new RegExp(x + ' .*$', 'm'), `${x} ${options[x]}`)
}
})
return next()
+3 -72
View File
@@ -1,80 +1,11 @@
import { NexeCompiler } from '../compiler'
import { readFileSync } from 'fs'
import { join } from 'path'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
`
const fs = require('fs')
const Buffer = require('buffer').Buffer
const isString = x => typeof x === 'string' || x instanceof String
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
}
})());
const originalReadFile = fs.readFile
const originalReadFileSync = fs.readFileSync
fs.readFile = function readFile (file, options, callback) {
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 = +manifest.binaryOffset + +manifest.resourceOffset + 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) {
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 = +manifest.binaryOffset + +manifest.resourceOffset + 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
}`.trim()
'{{replace:lib/patches/boot-nexe.js}}'
)
return next()
}
+67
View File
@@ -0,0 +1,67 @@
import got = require('got')
import download = require('download')
import {
NodePlatform,
NodeArch,
platforms,
architectures,
NexeTarget,
getTarget,
targetsEqual
} from './target'
export { NexeTarget }
export interface GitAsset {
name: string
url: string
browser_download_url: string
}
export interface GitRelease {
tag_name: string
assets_url: string
upload_url: string
assets: GitAsset[]
}
interface NodeRelease {
version: string
}
async function getJson<T>(url: string, options?: any) {
return JSON.parse((await got(url, options)).body) as T
}
function isBuildableVersion(version: string) {
const major = +version.split('.')[0]
return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4'
}
export function getLatestGitRelease(options?: any) {
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options)
}
export async function getUnBuiltReleases(options?: any) {
const nodeReleases = await getJson<NodeRelease[]>(
'https://nodejs.org/download/release/index.json'
)
const existingVersions = (await getLatestGitRelease(options)).assets.map(x => getTarget(x.name))
const versionMap: { [key: string]: true } = {}
return nodeReleases
.reduce((versions: NexeTarget[], { version }) => {
version = version.replace('v', '').trim()
if (!isBuildableVersion(version) || versionMap[version]) {
return versions
}
versionMap[version] = true
platforms.forEach(platform => {
architectures.forEach(arch => {
if (arch === 'x86' && platform === 'mac') return
versions.push(getTarget({ platform, arch, version }))
})
})
return versions
}, [])
.filter(x => !existingVersions.some(t => targetsEqual(t, x)))
}
+70
View File
@@ -0,0 +1,70 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from '../compiler'
import pify = require('pify')
const mkdirpAsync = pify(mkdirp)
const unlinkAsync = pify(unlink)
const readdirAsync = pify(readdir)
function readDirAsync(dir: string): Promise<string[]> {
return readdirAsync(dir).then(paths => {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : (path as any)))
})
).then(result => {
return [].concat(...(result as any))
})
})
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await Promise.all(
tmpFiles.map(async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
)
await next()
await Promise.all(tmpFiles.map(x => unlinkAsync(x)))
return Promise.all(
compiler.files.map(async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
)
}
+63
View File
@@ -0,0 +1,63 @@
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@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: cwd,
sourceMaps: false,
writeBundles: false,
output: '$name.js',
target: 'server@esnext', //try not to transpile
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 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()
}
+73
View File
@@ -0,0 +1,73 @@
import { normalize, relative } from 'path'
import { Readable } from 'stream'
import { createWriteStream, chmodSync, statSync } from 'fs'
import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function getStdIn(stdin: NodeJS.ReadStream): Promise<string> {
return new Promise(resolve => {
let out = ''
stdin
.setEncoding('utf8')
.on('readable', () => {
let current
while ((current = stdin.read())) {
out += current
}
})
.on('end', () => resolve(out))
})
}
/**
* The "cli" step detects the appropriate input. If no input options are passed,
* the package.json#main file is used.
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
*
* Configuration:
* - compiler.options.input - file path to the input bundle.
* - fallbacks: stdin, package.json#main
* - compiler.options.output - file path to the output executable.
* - fallbacks: stdout, nexe_ + epoch + ext
*
* @param {*} compiler
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { log } = compiler
let stdInUsed = false
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
stdInUsed = true
compiler.input = dequote(await getStdIn(process.stdin))
}
await next()
const target = compiler.options.targets.shift() as NexeTarget
const deliverable = await compiler.compileAsync(target)
return new Promise((resolve, reject) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(normalize(compiler.output!)))
.on('error', reject)
.once('close', (e: Error) => {
if (e) {
reject(e)
} else if (compiler.output) {
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())
}
})
})
}
+15 -10
View File
@@ -1,12 +1,13 @@
import download = require('download')
import { isDirectoryAsync } from './util'
import { LogStep } from './logger'
import { pathExistsAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from './compiler'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options = {}) {
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
return download(url, cwd, Object.assign(options, { extract: true, strip: 1 }))
return download(url, dest, Object.assign(options, { extract: true, strip: 1 }))
.on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
@@ -18,7 +19,7 @@ function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options =
}
})
})
.then(() => step.log(`Node source extracted to: ${cwd}`))
.then(() => step.log(`Node source extracted to: ${dest}`))
}
/**
@@ -27,14 +28,18 @@ function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options =
* @param {*} next
*/
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
const { src, log } = compiler
const { version, sourceUrl, downloadOptions } = compiler.options
const { src, log, targets: [{ version }] } = compiler
const { sourceUrl, downloadOptions } = compiler.options
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
const step = log.step(`Downloading Node.js source from: ${url}`)
if (await isDirectoryAsync(src)) {
if (await pathExistsAsync(src)) {
step.log('Source already downloaded')
return next()
}
return fetchNodeSourceAsync(src, url, step, downloadOptions).then(next)
if (compiler.options.build) {
await fetchNodeSourceAsync(src, url, step, downloadOptions)
}
return next()
}
+4 -12
View File
@@ -1,8 +1,7 @@
import { each } from 'bluebird'
import { readFileAsync, isDirectoryAsync } from './util'
import { readFileAsync, isDirectoryAsync, each } from '../util'
import { Buffer } from 'buffer'
import * as globs from 'globby'
import { NexeCompiler } from './compiler'
import { NexeCompiler } from '../compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
const resources = compiler.resources
@@ -19,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
}
}
+37
View File
@@ -0,0 +1,37 @@
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 !== 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()
}
+92
View File
@@ -0,0 +1,92 @@
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
export type NodeArch = 'x86' | 'x64'
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
architectures: NodeArch[] = ['x86', 'x64']
export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform | string
arch: NodeArch | string
}
//TODO bsd
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
win: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
linux: 'linux',
static: 'alpine',
alpine: 'alpine'
}
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
amd64: 'x64',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
}
function isVersion(x: string) {
if (!x) {
return false
}
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ''))
}
function isPlatform(x: string): x is NodePlatform {
return x in prettyPlatform
}
function isArch(x: string): x is NodeArch {
return x in prettyArch
}
class Target implements NexeTarget {
constructor(public arch: NodeArch, public platform: NodePlatform, public version: string) {}
toJSON() {
return this.toString()
}
toString() {
return `${this.platform}-${this.arch}-${this.version}`
}
}
export function targetsEqual(a: NexeTarget, b: NexeTarget) {
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
}
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
const currentArch = process.arch
let arch = currentArch in prettyArch ? prettyArch[process.arch] : (process.arch as NodeArch),
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
if (typeof target !== 'string') {
target = `${target.platform}-${target.arch}-${target.version}`
}
target
.toLowerCase()
.split('-')
.forEach(x => {
if (isVersion(x)) {
version = x.replace(/v/g, '')
}
if (isPlatform(x)) {
platform = prettyPlatform[x]
}
if (isArch(x)) {
arch = prettyArch[x]
}
})
return new Target(arch, platform, version)
}
+6 -18
View File
@@ -1,23 +1,11 @@
declare module 'nigel' {
import { Writable } from 'stream'
interface Needle {
value: Buffer
lastPos: number
last: number
length: number
badCharShift: Buffer
}
export function compile(needle: Buffer): Needle
export function horspool(haystack: Buffer, needle: Needle, start: number): number
export function all(haystack: Buffer, needle: Needle, pos: number): number[]
export class Stream extends Writable {
constructor(needle: Buffer)
needle(needle: Buffer): void
flush(): void
declare module 'got' {
interface GotFn {
(url: string, options?: any): Promise<{ body: string }>
stream(url: string, optoins?: any): any
}
function got(url: string, options?: any): Promise<{ body: string }>
export = got as GotFn
}
declare module 'download' {
import { Duplex } from 'stream'
interface DownloadOptions {
+56 -13
View File
@@ -1,9 +1,46 @@
import { readFile, writeFile, stat } from 'fs'
import * as Bluebird from 'bluebird'
import { execFile } from 'child_process'
import pify = require('pify')
import rimraf = require('rimraf')
const { promisify } = Bluebird
const rimrafAsync = (promisify(rimraf) as any) as (path: string) => Bluebird<void>
const rimrafAsync = pify(rimraf)
export async function each<T>(
list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any>
) {
const l = await list
for (let i = 0; i < l.length; i++) {
await action(l[i], i, l)
}
}
function falseOnEnoent(e: any) {
if (e.code === 'ENOENT') {
return false
}
throw e
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
const bound: MethodDecorator = function bound<T>(
target: Object,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>
) {
const configurable = true
return {
configurable,
get(this: T) {
const value = (descriptor.value as any).bind(this)
Object.defineProperty(this, propertyKey, { configurable, value, writable: true })
return value
}
}
}
function dequote(input: string) {
input = input.trim()
@@ -16,30 +53,36 @@ function dequote(input: string) {
}
export interface ReadFileAsync {
(path: string): Bluebird<Buffer>
(path: string, encoding: string): Bluebird<string>
(path: string): Promise<Buffer>
(path: string, encoding: string): Promise<string>
}
const readFileAsync = (promisify(readFile) as any) as ReadFileAsync
const writeFileAsync = (promisify(writeFile) as any) as (
path: string,
contents: string | Buffer
) => Promise<void>
const statAsync = promisify(stat)
const readFileAsync = pify(readFile)
const writeFileAsync = pify(writeFile)
const statAsync = pify(stat)
const execFileAsync = pify(execFile)
const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) {
return statAsync(path).then(x => true).catch({ code: 'ENOENT' }, () => false)
return statAsync(path)
.then(x => true)
.catch(falseOnEnoent)
}
function isDirectoryAsync(path: string) {
return statAsync(path).then(x => x.isDirectory()).catch({ code: 'ENOENT' }, () => false)
return statAsync(path)
.then(x => x.isDirectory())
.catch(falseOnEnoent)
}
export {
dequote,
padRight,
bound,
isWindows,
rimrafAsync,
statAsync,
execFileAsync,
readFileAsync,
pathExistsAsync,
isDirectoryAsync,
+101
View File
@@ -0,0 +1,101 @@
import * as nexe from '../lib/nexe'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import * as ci from './ci'
import { runAlpineBuild } from './docker'
import { getTarget } from '../lib/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../lib/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.CIRCLECI),
isPullRequest =
Boolean(env.CIRCLE_PR_NUMBER) ||
Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER) ||
Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
Authorization: 'token ' + env.GITHUB_TOKEN,
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
if (require.main === module) {
if (!isPullRequest) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
}
async function build() {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
return
}
const windowsBuild = releases.find(x => x.platform === 'windows')
const macBuild = releases.find(x => x.platform === 'mac')
const linuxOrAlpine = releases.find(x => x.platform === 'linux' || x.platform === 'alpine')
if (linuxOrAlpine) {
await ci.triggerDockerBuild(linuxOrAlpine, branchName)
}
if (macBuild) {
await ci.triggerMacBuild(macBuild, branchName)
}
if (windowsBuild) {
await ci.triggerWindowsBuild(windowsBuild)
}
}
if (env.NEXE_VERSION) {
const target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
build: true,
target,
output
}
const stop = keepalive()
if (target.platform === 'alpine') {
await runAlpineBuild(target)
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
Authorization: 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
})
console.log(target + ' uploaded.')
}
}
}
function keepalive() {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary(file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
+51
View File
@@ -0,0 +1,51 @@
import { NexeTarget } from '../lib/releases'
import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerMacBuild(release: NexeTarget, branch: string) {
assert.ok(env.CIRCLE_TOKEN)
const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/${branch}?circle-token=${
env.CIRCLE_TOKEN
}`
return got(circle, {
json: true,
body: {
build_parameters: {
NEXE_VERSION: release.toString()
}
}
})
}
export function triggerDockerBuild(release: NexeTarget, branch: string) {
assert.ok(env.TRAVIS_TOKEN)
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
return got(travis, {
json: true,
body: {
request: {
branch: branch,
config: {
merge_mode: 'deep_merge',
env: {
//use matrix so that secure global variable is merged
matrix: {
NEXE_VERSION: release.toString()
}
}
}
}
},
headers: {
'Travis-API-Version': '3',
Authorization: `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerWindowsBuild(release: NexeTarget) {
const hasVersion = 'NEXE_VERSION' in env
env.NEXE_VERSION = hasVersion ? env.NEXE_VERSION!.trim() : release.toString()
return Promise.resolve()
}
+59
View File
@@ -0,0 +1,59 @@
import { NexeTarget } from '../lib/target'
import { writeFileAsync, readFileAsync } from '../lib/util'
import { spawn } from 'child_process'
import got = require('got')
import execa = require('execa')
import { appendFileSync } from 'fs'
function alpine(target: NexeTarget) {
return `
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
ENV NODE_VERSION=${target.version}
ENV NEXE_VERSION=beta
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
mkdir /nexe_temp && mv /node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
cd /nexe_temp/\${NODE_VERSION} && \
./configure --prefix=/usr --fully-static && \
make && make install && \
paxctl -cm /usr/bin/node
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
`.trim()
}
export async function runAlpineBuild(target: NexeTarget) {
await writeFileAsync('Dockerfile', alpine(target))
const outFilename = 'nexe-alpine-build-log.txt'
await writeFileAsync(outFilename, '')
let output: any = []
try {
output.push(await execa.shell(`docker build -t nexe-alpine .`))
output.push(await execa.shell(`docker run -d --name nexe nexe-alpine sh`))
output.push(await execa.shell(`docker cp nexe:/out out`))
output.push(await execa.shell(`docker rm nexe`))
} catch (e) {
console.log('Error running docker', e)
} finally {
output.forEach((x: any) => {
appendFileSync(outFilename, x.stderr)
appendFileSync(outFilename, x.stdout)
})
await got(
`https://transfer.sh/${Math.random()
.toString(36)
.substring(2)}.txt`,
{
body: await readFileAsync(outFilename),
method: 'PUT'
}
)
.then(x => console.log('Posted docker log: ', x.body))
.catch(e => console.log('Error posting log', e))
}
}
+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}`)
}
-1
View File
@@ -1 +0,0 @@
true
-11
View File
@@ -1,11 +0,0 @@
/**
* Embedded Files Test
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
const nexeres = require('nexeres');
process.stdout.write(nexeres.get('hw.txt').toString('ascii'));
-28
View File
@@ -1,28 +0,0 @@
{
"name": "embededfiles-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node index.js"
},
"dependencies": {
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"resourceFiles": [
"hw.txt"
],
"browserify": {
"excludes": [],
"requires": []
},
"temp": "../src",
"debug": false,
"runtime": {
"nodeConfigureOpts": ["--fully-static"],
"framework": "node",
"version": "5.7.0"
}
}
}
-61
View File
@@ -1,61 +0,0 @@
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var jade = require('jade');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
-93
View File
@@ -1,93 +0,0 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('express-test:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
process.stdout.write('true');
process.exit(0);
-28
View File
@@ -1,28 +0,0 @@
{
"name": "express-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
},
"nexe": {
"input": "bin/www",
"output": "test.nex",
"temp": "../src",
"debug": false,
"runtime": {
"framework": "node",
"version": "5.10.0",
"ignoreFlags": true
}
}
}
@@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
-9
View File
@@ -1,9 +0,0 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
-9
View File
@@ -1,9 +0,0 @@
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
-6
View File
@@ -1,6 +0,0 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}
-5
View File
@@ -1,5 +0,0 @@
extends layout
block content
h1= title
p Welcome to #{title}
-7
View File
@@ -1,7 +0,0 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
+3
View File
@@ -0,0 +1,3 @@
{
"main": "entry-file.js"
}
-13
View File
@@ -1,13 +0,0 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// "use strict"; w/o --use_strict
let isStrict = (function() { return !this; })();
console.log(isStrict);
-15
View File
@@ -1,15 +0,0 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": false,
"js-flags": "--use_strict",
"node-args": ""
}
}
}
-23
View File
@@ -1,23 +0,0 @@
"use strict";
let gulp = require('gulp');
let nexe = require('nexe');
gulp.task( "compile", ( callback ) => {
let options = {
input: "index.js",
output: "test.nex",
python: "python",
nodeTempDir: "./tmp",
nodeVersion: "latest",
flags: false,
framework: "nodejs"
};
nexe.compile( options, ( err ) => {
console.log( err );
callback( err );
} );
} );
gulp.task('default', ['compile']);
-2
View File
@@ -1,2 +0,0 @@
// it probably executed.
console.log(true);
-26
View File
@@ -1,26 +0,0 @@
{
"name": "gulp-test-170",
"version": "0.0.1",
"description": "gulpfile.js issue #170",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"gulp": "^3.9.0",
"nexe": "^1.0.5"
},
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
}
}
-19
View File
@@ -1,19 +0,0 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// test.nex --help
let status = false;
// console.log(process.argv);
if(process.argv[2]) {
status = true;
}
console.log(status);
-15
View File
@@ -1,15 +0,0 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
},
"dependencies": {}
}
-3
View File
@@ -1,3 +0,0 @@
require('js-yaml');
console.log(true);
-23
View File
@@ -1,23 +0,0 @@
{
"name": "js-yaml-148",
"version": "1.0.0",
"description": "#148 js-yaml fails to work",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.5.2"
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"version": "5.7.0",
"framework": "node"
}
}
}
+4 -1
View File
@@ -1 +1,4 @@
--timeout 100000000
./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}`))
})
})
})
+30
View File
@@ -0,0 +1,30 @@
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)}"`)
})
})
-249
View File
@@ -1,249 +0,0 @@
/**
* Test runner for nexe
*
* @author Jared Allard <jaredallard@outlook.com>
* @license MIT
**/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const spawn = require('child_process').spawn;
// our vars
const NEXE_VERSION = require('../package.json').version;
const testBase = __dirname;
const LOG_FILE = path.join(testBase, 'test.log');
if(fs.existsSync(LOG_FILE)) {
fs.unlinkSync(LOG_FILE);
}
let logfile = fs.createWriteStream(LOG_FILE, {
autoClose: false
});
const compileTest = function(test, cb) {
let testDir = path.join(testBase, test);
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
let npminst = spawn('npm', ['install'], {
cwd: testDir
});
npminst.on('error', function() {
throw new Error('Failed to execute NPM');
return cb(false);
});
npminst.on('close', function(code) {
if(code !== 0) {
throw new Error('NPM exited with non-zero status');
return cb(false);
}
let testinst = spawn('../../bin/nexe', [], {
cwd: testDir
});
testinst.on('error', function() {
throw new Error('Test failed to compile');
return cb(false);
});
testinst.on('close', function(code) {
let err = null;
if(code !== 0) {
throw new Error('Test gave non-zero code');
err = false;
}
logfile = fs.createWriteStream(LOG_FILE);
return cb(err);
});
testinst.stdout.on('data', function(d) {
process.stdout.write(d.toString('ascii'));
});
});
};
/**
* Run a test-generated binary
*
* @param {string} test - test name (dir)
* @param {array} optional args - array of args to pass to the binary
* @param {function} cb - callback
**/
const runTest = function(test, args, cb) {
let testDir = path.join(testBase, test);
let testBin = path.join(testDir, 'test.nex')
// check params
if(!Array.isArray(args)) {
cb = args;
args = [];
}
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
if(!fs.existsSync(testBin)) {
throw new Error('Test binary not found');
return cb(false);
}
let returned = false;
let testinst = spawn(testBin, args, {
cwd: testDir
})
testinst.stdout.on('data', function(d) {
let status = d.toString('ascii');
if(status === true || status === 'true' || status === 'true\n') {
testinst.stdin.pause();
testinst.kill();
// make it know we returned.
returned = true;
return cb();
}
})
testinst.on('close', function(c) {
if(returned !== true) {
throw new Error('Test failed (stdout never contained true)');
return cb(false);
}
});
}
console.log('NOTICE: The first test may take awhile as it may compile Node.js');
console.log(' Monitor the log file (test.log) for progress.');
after(function() {
console.log('notice: closing test.log file descriptor.')
logfile.end();
});
/**
* Tests express compatability.
**/
describe('nexe can bundle express', function() {
let testname = 'express-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe and embedding files', function() {
let testname = 'embeded-files';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('v8 flags test (strict mode)', function() {
let testname = 'flags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can be utilized in gulp test', function() {
let testname = 'gulp-test-170';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can ignore node.js flags test', function() {
let testname = 'ignoreFlags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, ['--help'], function(err) {
return next(err);
});
});
});
});
describe('nexe supports js-yaml test', function() {
let testname = 'js-yaml-148';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
+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