Compare commits

..

42 Commits

Author SHA1 Message Date
calebboyd 9a9511a429 chore: bump version 2018-02-23 00:38:53 -06:00
foxever 4cf87e68e6 fix: pifi -> pify, and optional uglify-es in package.json (#463)
closes #462
2018-02-23 00:34:32 -06:00
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
37 changed files with 1329 additions and 522 deletions
+1
View File
@@ -0,0 +1 @@
package-lock=false
+33 -14
View File
@@ -5,6 +5,7 @@
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/dt/nexe.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/v/nexe.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/l/nexe.svg" alt="License"></a>
<a href="https://gitter.im/nexe/nexe?utm_source=badge&utm_medium=badge"><img src="https://badges.gitter.im/nexe/nexe.svg"></a>
</p>
<p align="center"><code>npm i nexe -g</code></p>
@@ -51,7 +52,7 @@ Additional files or resources can be added to the binary by passing `-r "glob/pa
## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptbale version of python 2. eg. Systems that have python2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486)
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
@@ -87,8 +88,17 @@ compile({
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string | object`
- Combination of platform-arch-version. e.g. `'win32-ia32-6.10.3'`
- An object or string describing platform-arch-version. e.g. `'windows-ia32-6.10.3'`
- each segment is optional, and will be merged with the current environment
- Examples: ([full list](https://github.com/nexe/nexe/releases))
- `'win32-x86-8.6.0`
- `{ platform: 'alpine' }`
- `darwin-8.6.0`
- `linux-x64`
- `macos-8.4.0`
See [test/target.spec.ts](test/target.spec.ts)
- If the [`build`](#build-boolean) flag is set, the platform portion of the target is ignored.
- default: `process`
- #### `bundle: string | boolean`
- If a string is provided it must be a valid relative module path
@@ -120,7 +130,7 @@ compile({
- #### `make: string[]`
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- #### `make: string[]`
- #### `vcBuild: string[]`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
@@ -134,7 +144,7 @@ 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:
@@ -150,10 +160,16 @@ compile({
- #### `clean: boolean`
- If included, nexe will remove temporary files for the accompanying configuration and exit
- #### `enableNodeCli: boolean`
- Enable the original Node CLI (will prevent application cli from working)
- Enable the original Node CLI (will prevent application cli from working).
- Node CLI arguments passed via the [NODE_OPTIONS](https://nodejs.org/api/cli.html#cli_node_options_options) environment
variable will still be processed. NODE_OPTIONS support can be disabled with the `--without-node-options` configure flag.
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `ghToken: string`
- Provide a Github Token for accessing nexe releases
- This is usually needed in CI environments
- default: `process.env.GITHUB_TOKEN`
- #### `sourceUrl: string`
- Provide an alternate url for the node source code
- Note: temporary files will still be created for this under the specified version
@@ -171,8 +187,7 @@ compile({
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)
A plugin
For examples, see the built in patches: [src/patches](src/patches).
### `NexeCompiler`
@@ -196,16 +211,15 @@ Any modifications made to `NexeFile#contents` will be maintained in the cache _w
## Native Modules
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
Take a look at the (windows) [example](examples/native-build/build.js)
Any `.node` binding can be used with nexe. These library files will be bundled and written alongside the resulting binary at runtime. Currently, nexe supports modules loaded directly (`.node` files) and those loaded with the `'bindings'` module. It does not yet support modules using `node-pre-gyp#find`.
- [ ] Implement support `node-pre-gyp#find`.
Its important to note: unless your native module conditionally loads each platform binary. Nexe builds with native modules will be platform specific. Eg. You will no longer be able to use cross platform builds.
## Maintainers
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
[![Jared Allard](https://avatars.githubusercontent.com/u/2391349?s=130)](https://jaredallard.me/) | [![Caleb Boyd](https://avatars.githubusercontent.com/u/5818726?s=130)](https://github.com/calebboyd) | [![Christopher Karper](https://avatars.githubusercontent.com/u/653156?s=130)](https://github.com/ckarper) | [![Dustin Greif](https://avatars.githubusercontent.com/u/3026298?s=130)](https://github.com/dgreif) |
---|---|---|---
[Jared Allard](https://github.com/jaredallard) | [Caleb Boyd](http://github.com/calebboyd) | [Christopher Karper](https://github.com/ckarper) | [Dustin Greif](https://github.com/dgreif) |
TODO
- [ ] Implement support for `node-pre-gyp#find`.
## Contributing
@@ -221,6 +235,11 @@ Testing
$ npm test
```
## Maintainers
[![Jared Allard](https://avatars.githubusercontent.com/u/2391349?s=130)](https://jaredallard.me/) | [![Caleb Boyd](https://avatars.githubusercontent.com/u/5818726?s=130)](https://github.com/calebboyd) | [![Christopher Karper](https://avatars.githubusercontent.com/u/653156?s=130)](https://github.com/ckarper) | [![Dustin Greif](https://avatars.githubusercontent.com/u/3026298?s=130)](https://github.com/dgreif) |
---|---|---|---
[Jared Allard](https://github.com/jaredallard) | [Caleb Boyd](http://github.com/calebboyd) | [Christopher Karper](https://github.com/ckarper) | [Dustin Greif](https://github.com/dgreif) |
### Former
+7 -1
View File
@@ -1,3 +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:
- npm run asset-compile
- yarn run asset-compile
+1 -3
View File
@@ -10,9 +10,7 @@ if (require.main === module) {
process.stderr.write(showHelp ? options.help : options.version + eol)
} else {
const nexe = require('./lib/nexe')
nexe.compile(argv).catch((e) => {
process.stderr.write(eol + e.stack)
})
nexe.compile(argv)
}
} else {
module.exports = require('./lib/nexe')
+13 -10
View File
@@ -2,7 +2,7 @@
"name": "nexe",
"description": "Create a single executable out of your Node.js application",
"license": "MIT",
"version": "2.0.0-rc.10",
"version": "2.0.0-rc.26",
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
@@ -10,10 +10,11 @@
],
"scripts": {
"asset-compile": "ts-node tasks/asset-compile",
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm test && npm run build",
"test": "mocha test/**/*.spec.ts",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
"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"
},
@@ -35,15 +36,19 @@
},
"dependencies": {
"app-builder": "^5.1.0",
"typescript": "2.5.3",
"caw": "^2.0.1",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "2.2.31",
"fuse-box": "^3.1.0",
"globby": "^6.1.0",
"got": "^8.0.3",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"ora": "^1.2.0",
"pifi": "1.0.0",
"pify": "^3.0.0",
"rimraf": "^2.6.1",
"uglify-es": "^3.3.9",
"uglify-js": "3.0.28"
},
"devDependencies": {
@@ -59,10 +64,8 @@
"@types/rimraf": "0.0.28",
"chai": "^4.1.2",
"execa": "^0.8.0",
"got": "^7.1.0",
"mocha": "^3.5.0",
"prettier": "^1.6.1",
"ts-node": "3.3.0",
"typescript": "^2.4.1"
"ts-node": "3.3.0"
}
}
+5 -3
View File
@@ -2,21 +2,23 @@
"name": "nexe-daemon",
"version": "1.0.0",
"description": "Plugin for nexe to create installable services",
"main": "index.js",
"main": "lib/index.js",
"scripts": {
"clean": "rimraf *.js",
"build": "tsc",
"postpublish": "npm run clean"
},
"files": [
"*.js",
"lib",
"winsw.exe"
],
"author": "Caleb Boyd",
"license": "MIT",
"devDependencies": {
"nexe": "^2.0.0-rc.6",
"rimraf": "^2.6.2",
"typescript": "^2.5.2"
},
"dependencies": {
"nexe": "^2.0.0-rc.11"
}
}
+11 -13
View File
@@ -12,38 +12,36 @@ interface NexeDaemonOptions {
type DaemonOptions = NexeOptions & { daemon: { windows: NexeDaemonOptions } }
function renderWinswConfig(options: any) {
return '<configuration>\r\n' +
return (
'<configuration>\r\n' +
`${Object.keys(options).reduce((config: string, element: string) => {
return config += `<${element}>${options[element]}</${element}>\r\n`
return (config += `<${element}>${options[element]}</${element}>\r\n`)
}, '')}</configuration>\r\n`
)
}
export default function daemon (compiler: NexeCompiler<DaemonOptions>, next: () => Promise<void>) {
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'))
)
readFileSync(require.resolve('../winsw.exe'))
)
const name = compiler.options.name,
options = compiler.options.daemon.windows,
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-config.xml',
'./nexe/plugin/daemon/winsw.xml',
Buffer.from(renderWinswConfig(Object.assign(defaults, options)))
)
compiler.addResource(
'./nexe/plugin/daemon/app.js',
Buffer.from(compiler.input)
)
compiler.addResource('./nexe/plugin/daemon/app.js', Buffer.from(compiler.input))
compiler.input = '{{replace:plugins/nexe-daemon/lib/nexe-daemon.js}}'
return next()
+65 -3
View File
@@ -1,4 +1,66 @@
console.log('hello world')
if (true) {
console.log('wat')
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
}
})
}
+3 -3
View File
@@ -1458,9 +1458,9 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
nexe@^2.0.0-rc.6:
version "2.0.0-rc.6"
resolved "https://registry.yarnpkg.com/nexe/-/nexe-2.0.0-rc.6.tgz#b3990bb67098a93bc488b21ad88aee67b62cf0d5"
nexe@^2.0.0-rc.11:
version "2.0.0-rc.11"
resolved "https://registry.yarnpkg.com/nexe/-/nexe-2.0.0-rc.11.tgz#cd06fc91da8db008fffa6dde1a9a8dd6decf2c35"
dependencies:
app-builder "^5.1.0"
chalk "^1.1.3"
-68
View File
@@ -3,74 +3,6 @@ import * as path from 'path'
import * as child from 'child_process'
import { createHash } from 'crypto'
export interface ExtractNodeModuleOptions {
[key: string]: { additionalFiles: string[] } | true
}
function hashName(name: string | Buffer) {
return createHash('md5')
.update(name)
.digest('hex')
.toString()
.slice(0, 8)
}
export function embedDotNode(
options: ExtractNodeModuleOptions,
file: { contents: string; absPath: string }
) {
const contents = fs.readFileSync(file.absPath)
const modulePathParts = file.absPath.split(path.sep).reverse()
const module = modulePathParts[modulePathParts.findIndex(x => x === 'node_modules') - 1]
const bindingName = path.basename(file.absPath)
const settings = options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (!settings || settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
})
let segments = [moduleDir]
while (depth--) {
segments.push(hashName(moduleDir + depth))
}
segments.push(bindingName)
file.contents += `
var cwd = process.cwd()
var bindingFileParts = ${JSON.stringify(segments)};
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
mkdirp(path.dirname(bindingFile));
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
${settings.additionalFiles.reduce((code, filename, i) => {
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
return (code += `
var file${i} = '${contents.toString('base64')}';
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
}
function findNativeModulePath(filePath: string, bindingsArg: string) {
const dirname = path.dirname(filePath)
const tempFile = Math.random() * 100 + '.js'
+72
View File
@@ -0,0 +1,72 @@
import { createHash } from 'crypto'
import * as path from 'path'
import * as fs from 'fs'
export interface EmbedNodeModuleOptions {
[key: string]: { additionalFiles: string[] } | true
}
function hashName(name: string | Buffer) {
return createHash('md5')
.update(name)
.digest('hex')
.toString()
.slice(0, 8)
}
export function embedDotNode(
options: EmbedNodeModuleOptions,
file: { contents: string; absPath: string }
) {
const contents = fs.readFileSync(file.absPath)
const modulePathParts = file.absPath.split(path.sep).reverse()
const module = modulePathParts[modulePathParts.findIndex(x => x === 'node_modules') - 1]
const bindingName = path.basename(file.absPath)
const settings = options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (!settings || settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return {}
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
})
let segments = [moduleDir]
while (depth--) {
segments.push(hashName(moduleDir + depth))
}
segments.push(bindingName)
file.contents += `
var cwd = process.cwd()
var bindingFileParts = ${JSON.stringify(segments)};
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
mkdirp(path.dirname(bindingFile));
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
${settings.additionalFiles.reduce((code, filename, i) => {
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
return (code += `
var file${i} = '${contents.toString('base64')}';
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
}
+4 -3
View File
@@ -1,4 +1,5 @@
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
import { BindingsRewrite } from './bindings-rewrite'
import { embedDotNode, EmbedNodeModuleOptions } from './embed-node'
export interface FuseBoxFile {
info: { absPath: string }
@@ -21,14 +22,14 @@ export interface FuseBoxFile {
): void
}
export default function(options: ExtractNodeModuleOptions = {}) {
export default function(options: EmbedNodeModuleOptions = {}) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test = /node_modules.*(\.js|\.node)$|\.node$/
public limit2Project = false
private modules: (keyof ExtractNodeModuleOptions)[]
private modules: (keyof EmbedNodeModuleOptions)[]
constructor(public options = {}) {}
+1
View File
@@ -0,0 +1 @@
//TODO
+44 -28
View File
@@ -4,7 +4,7 @@ import { createHash } from 'crypto'
import { createReadStream } from 'fs'
import { Readable } from 'stream'
import { spawn } from 'child_process'
import { Logger } from './logger'
import { Logger, LogStep } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util'
import { NexeOptions, version } from './options'
import { NexeTarget } from './target'
@@ -32,7 +32,7 @@ interface NexeHeader {
export class NexeCompiler<T extends NexeOptions = NexeOptions> {
private start = Date.now()
private env = { ...process.env }
private compileStep: { modify: Function; log: Function }
private compileStep: LogStep
public log = new Logger(this.options.loglevel)
public src: string
public files: NexeFile[] = []
@@ -45,10 +45,7 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
index: {},
bundle: Buffer.from('')
}
public output = isWindows
? `${(this.options.output || this.options.name).replace(/\.exe$/, '')}.exe`
: `${this.options.output || this.options.name}`
public output = this.options.output
private nodeSrcBinPath: string
constructor(public options: T) {
const { python } = (this.options = options)
@@ -132,14 +129,25 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
}
private _runBuildCommandAsync(command: string, args: string[]) {
if (this.log.verbose) {
this.compileStep.pause()
}
return new Promise((resolve, reject) => {
spawn(command, args, {
cwd: this.src,
env: this.env,
stdio: 'ignore'
stdio: this.log.verbose ? 'inherit' : 'ignore'
})
.once('error', reject)
.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))
@@ -158,9 +166,9 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
private async _buildAsync() {
this.compileStep.log(
`Configuring node build${this.options.configure.length
? ': ' + this.options.configure
: '...'}`
`Configuring node build${
this.options.configure.length ? ': ' + this.options.configure : '...'
}`
)
await this._configureAsync()
const buildOptions = this.options.make
@@ -172,11 +180,15 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
}
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
const downloadOptions: any = {
headers: {
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
let downloadOptions = this.options.downloadOptions
if (this.options.ghToken) {
downloadOptions = Object.assign({}, downloadOptions)
downloadOptions.headers = Object.assign({}, downloadOptions.headers, {
Authorization: 'token ' + this.options.ghToken
})
}
const githubRelease = await getLatestGitRelease(downloadOptions)
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
@@ -185,18 +197,18 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
throw new Error(`${assetName} not available, create it using the --build flag`)
}
const filename = this.getNodeExecutableLocation(target)
await download(
asset.browser_download_url,
dirname(filename),
downloadOptions
).on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
})
await download(asset.browser_download_url, dirname(filename), this.options.downloadOptions).on(
'response',
(res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
}
)
return createReadStream(filename)
}
@@ -233,6 +245,10 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
return this._assembleDeliverable(binary)
}
code() {
return [this.shims.join(''), this.input].join(';')
}
private _assembleDeliverable(binary: NodeJS.ReadableStream) {
if (this.options.empty) {
return binary
@@ -242,7 +258,7 @@ export class NexeCompiler<T extends NexeOptions = NexeOptions> {
artifact.push(chunk)
})
binary.on('close', () => {
const content = [this.shims.join(''), this.input].join(';')
const content = this.code()
artifact.push(content)
artifact.push(this.resources.bundle)
const lengths = Buffer.from(Array(16))
+7 -3
View File
@@ -6,10 +6,12 @@ const frameLength = 120
export interface LogStep {
modify(text: string, color?: string): void
log(text: string, color?: string): void
pause(): void
resume(): void
}
export class Logger {
private verbose: boolean
public verbose: boolean
private silent: boolean
private ora: any
private modify: Function
@@ -48,7 +50,7 @@ export class Logger {
step(text: string, method: string = 'succeed'): LogStep {
if (this.silent) {
return { modify() {}, log() {} }
return { modify() {}, log() {}, pause() {}, resume() {} }
}
if (!this.ora.id) {
this.ora.start().text = text
@@ -62,7 +64,9 @@ export class Logger {
return {
modify: this.modify,
log: this.verbose ? this.write : this.modify
log: this.verbose ? this.write : this.modify,
pause: () => this.ora.stopAndPersist(),
resume: () => this.ora.start()
} as LogStep
}
}
+26 -16
View File
@@ -1,21 +1,22 @@
import { EOL } from 'os'
import { compose, Middleware } from 'app-builder'
import resource from './steps/resource'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
import { NexeCompiler } from './compiler'
import { argv, version, help, normalizeOptionsAsync, NexeOptions, NexePatch } from './options'
import { normalizeOptions, NexeOptions, NexePatch } from './options'
import resource from './steps/resource'
import cli from './steps/cli'
import bundle from './steps/bundle'
import download from './steps/download'
import shim from './steps/shim'
import artifacts from './steps/artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = await normalizeOptionsAsync(compilerOptions)
const options = normalizeOptions(compilerOptions)
const compiler = new NexeCompiler(options)
const build = compiler.options.build
@@ -34,16 +35,25 @@ async function compile(
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
const nexe = compose(resource, bundle, cli, buildSteps, shim, options.plugins as NexePatch[])
return callback
? void nexe(compiler).then(
() => callback && callback(null),
(e: Error) => {
if (callback) callback(e)
else throw e
}
)
: nexe(compiler)
const nexe = compose(resource, bundle, shim, cli, buildSteps, options.plugins as NexePatch[])
let error = null
try {
await nexe(compiler)
} catch (e) {
error = e
}
if (error) {
if (compiler.options.loglevel !== 'silent' && error) {
process.stderr.write(EOL + error.stack + EOL)
}
compiler.quit()
if (callback) return callback(error)
return Promise.reject(error)
}
if (callback) callback(null)
}
export { argv, compile, version, NexeCompiler, NexeOptions, help }
export { compile, NexeCompiler }
export { argv, version, NexeOptions, help } from './options'
+48 -28
View File
@@ -1,11 +1,13 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { isWindows, padRight } from './util'
import { basename, extname, join, isAbsolute, relative, dirname } from 'path'
import { basename, extname, join, isAbsolute, relative, dirname, resolve } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os'
import { EOL, homedir } from 'os'
import * as c from 'chalk'
const caw = require('caw')
export const version = '{{replace:0}}'
export interface NexePatch {
@@ -33,6 +35,7 @@ export interface NexeOptions {
plugins: (string | NexePatch)[]
native: any
empty: boolean
ghToken: string
sourceUrl?: string
enableStdIn?: boolean
python?: string
@@ -81,7 +84,8 @@ const alias = {
m: 'make',
h: 'help',
l: 'loglevel',
'fake-argv': 'fakeArgv'
'fake-argv': 'fakeArgv',
'gh-token': 'ghToken'
}
const argv = parseArgv(process.argv, { alias, default: { ...defaults, enableStdIn: true } })
const g = c.gray
@@ -113,7 +117,7 @@ ${c.bold('nexe <entry-file> [options]')}
${c.underline.bold('Other options:')}
--bundle -- custom bundling module with 'createBundle' export
--temp -- temp file storage default './nexe'
--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
@@ -182,48 +186,64 @@ function extractName(options: NexeOptions) {
return name.replace(/\.exe$/, '')
}
function isEntryFile(filename: string) {
return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
function isEntryFile(filename?: string): filename is string {
return Boolean(
filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
)
}
function findInput(input: string, cwd: string) {
const maybeInput = argv._.slice().pop() || ''
export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
if (input) {
return input
return resolve(cwd, input)
}
if (isEntryFile(maybeInput)) {
return maybeInput
if (isEntryFile(maybeEntry)) {
return resolve(cwd, maybeEntry)
}
if (!process.stdin.isTTY) {
return ''
}
try {
const main = require.resolve(cwd)
return './' + relative(cwd, main)
return require.resolve(cwd)
} catch (e) {
void e
throw new Error('No entry file found')
}
return ''
}
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions> {
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
options.temp = options.temp || process.env.NEXE_TEMP || join(options.cwd, '.nexe')
options.input = findInput(options.input, options.cwd)
const cwd = (options.cwd = resolve(options.cwd))
options.temp = options.temp
? resolve(cwd, options.temp)
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
const maybeEntry = input === argv ? argv._[argv._.length - 1] : undefined
options.input = resolveEntry(options.input, cwd, maybeEntry)
options.name = extractName(options)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget)
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (!options.targets.length) {
options.targets.push(getTarget())
}
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.downloadOptions = options.downloadOptions || {}
options.downloadOptions.headers = options.downloadOptions.headers || {}
options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)'
options.downloadOptions.agent = process.env.HTTPS_PROXY
? caw(process.env.HTTPS_PROXY, { protocol: 'https' })
: options.downloadOptions.agent || require('https').globalAgent
options.downloadOptions.rejectUnauthorized = process.env.HTTPS_PROXY ? false : true
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
options.output =
(options.targets[0] as NexeTarget).platform === 'windows'
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
: `${options.output || options.name}`
options.output = resolve(cwd, options.output)
if (options.build) {
const { arch } = options.targets[0] as NexeTarget
@@ -241,14 +261,14 @@ function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOption
return x
}
options.plugins = options.plugins.map(requireDefault)
options.patches = options.patches.map(requireDefault)
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
Object.keys(alias)
.filter(k => k !== 'rc')
.forEach(x => delete opts[x])
return Promise.resolve(options)
return options
}
export { argv, normalizeOptionsAsync, help }
export { argv, normalizeOptions, help }
+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()
}
+2 -2
View File
@@ -1,5 +1,5 @@
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'
@@ -7,6 +7,6 @@ import ico from './ico'
import rc from './node-rc'
import { NexeCompiler, NexeOptions } from '../compiler'
const patches = [gyp, nexePatches, buildFixes, cli, flags, ico, rc]
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc]
export default patches
+1 -1
View File
@@ -13,7 +13,7 @@ export default async function nodeRc(compiler: NexeCompiler, next: () => Promise
const isVar = /^[A-Z_]+$/.test(value)
value = isVar ? value : `"${value}"`
file.contents = file.contents.replace(
new RegExp(`VALUE "${key}",*`),
new RegExp(`VALUE "${key}",.*`),
`VALUE "${key}", ${value}`
)
})
+3 -2
View File
@@ -11,8 +11,9 @@ export default async function snapshot(compiler: NexeCompiler, next: () => Promi
await compiler.replaceInFileAsync(
'configure',
'def configure_v8(o):',
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${warmupScript ||
snapshotFile}'`
`def configure_v8(o):\n o['variables']['embed_script'] = '${
snapshotFile
}'\n o['variables']['warmup_script'] = '${warmupScript || snapshotFile}'`
)
return next()
-1
View File
@@ -32,7 +32,6 @@ async function getJson<T>(url: string, options?: any) {
return JSON.parse((await got(url, options)).body) as T
}
//TODO only build the latest of each major...?
function isBuildableVersion(version: string) {
const major = +version.split('.')[0]
return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4'
+1 -1
View File
@@ -14,7 +14,7 @@ function readDirAsync(dir: string): Promise<string[]> {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : path as any))
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : (path as any)))
})
).then(result => {
return [].concat(...(result as any))
+11 -9
View File
@@ -10,23 +10,24 @@ function createBundle(options: NexeOptions) {
if (options.compress) {
plugins.push(
QuantumPlugin({
target: 'server',
target: 'server@esnext',
uglify: true,
bakeApiIntoBundle: options.name
})
)
}
const cwd = options.cwd
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_LOG) || false,
homeDir: options.cwd,
homeDir: cwd,
sourceMaps: false,
writeBundles: false,
output: '$name.js',
target: 'server',
target: 'server@esnext', //try not to transpile
plugins
})
const input = relative(options.cwd, options.input).replace(/\\/g, '/')
const input = relative(cwd, resolve(cwd, options.input)).replace(/\\/g, '/')
fuse.bundle(options.name).instructions(`> ${input}`)
return fuse.run().then((x: any) => {
let output = ''
@@ -36,8 +37,9 @@ function createBundle(options: NexeOptions) {
}
export default async function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
compiler.input = await readFileAsync(compiler.options.input, 'utf-8')
const { bundle, cwd, empty, input } = compiler.options
if (!bundle) {
compiler.input = await readFileAsync(resolve(cwd, input), 'utf-8')
return next()
}
@@ -47,14 +49,14 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
}
let producer = createBundle
if (typeof compiler.options.bundle === 'string') {
producer = require(resolve(compiler.options.cwd, compiler.options.bundle)).createBundle
if (typeof bundle === 'string') {
producer = require(resolve(cwd, bundle)).createBundle
}
compiler.input = await producer(compiler.options)
if ('string' === typeof compiler.options.debugBundle) {
await writeFileAsync(compiler.options.debugBundle, compiler.input)
await writeFileAsync(resolve(cwd, compiler.options.debugBundle), compiler.input)
}
return next()
+11 -7
View File
@@ -1,6 +1,6 @@
import { normalize } from 'path'
import { normalize, relative } from 'path'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { createWriteStream, chmodSync, statSync } from 'fs'
import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
@@ -39,7 +39,7 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
let stdInUsed = false
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
stdInUsed = true
compiler.input = await getStdIn(process.stdin)
compiler.input = dequote(await getStdIn(process.stdin))
}
await next()
@@ -56,11 +56,15 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
if (e) {
reject(e)
} else if (compiler.output) {
chmodSync(compiler.output, '755') //todo fix erroneous rw mode change
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]'
: compiler.options.input}' written to: ${compiler.output}`
`Entry: '${
stdInUsed ? (compiler.options.empty ? '[empty]' : '[stdin]') : inputFile
}' written to: ${outputFile}`
)
resolve(compiler.quit())
}
+117 -3
View File
@@ -1,22 +1,74 @@
import { Stats } from 'fs'
import { ok } from 'assert'
import { resolve, normalize } from 'path'
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 = () => {
const manifest = binary.resources
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]
}
@@ -27,8 +79,35 @@ if (Object.keys(manifest).length) {
})
setupManifest = () => {}
}
//TODO track inflight fs reqs??
//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]
@@ -61,6 +140,24 @@ if (Object.keys(manifest).length) {
})
})
},
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]
@@ -75,6 +172,23 @@ if (Object.keys(manifest).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)
+15 -3
View File
@@ -7,17 +7,29 @@ function wrap(code: string) {
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}}'))
}
if (compiler.options.fakeArgv) {
//compiler.shims.push(wrap('{/{replace:lib/steps/shim-require.js}}'))
if (compiler.options.fakeArgv !== false) {
const nty = !process.stdin.isTTY
const input = nty ? '[stdin]' : compiler.options.input
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}")`});`)
process.argv.splice(1,0, ${nty ? `'${input}'` : `r(${input})`});`)
)
}
+2 -1
View File
@@ -28,6 +28,7 @@ const prettyPlatform: { [key: string]: NodePlatform } = {
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
amd64: 'x64',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
@@ -64,7 +65,7 @@ export function targetsEqual(a: NexeTarget, b: NexeTarget) {
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
const currentArch = process.arch
let arch = currentArch in prettyArch ? prettyArch[process.arch] : process.arch as NodeArch,
let arch = currentArch in prettyArch ? prettyArch[process.arch] : (process.arch as NodeArch),
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
+15 -18
View File
@@ -1,8 +1,5 @@
import * as nexe from '../lib/nexe'
import {
getUnBuiltReleases,
getLatestGitRelease
} from '../lib/releases'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import * as ci from './ci'
import { runAlpineBuild } from './docker'
import { getTarget } from '../lib/target'
@@ -15,11 +12,12 @@ const env = process.env,
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.CIRCLECI),
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER)
|| Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
|| Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
'Authorization': 'token ' + env.GITHUB_TOKEN,
isPullRequest =
Boolean(env.CIRCLE_PR_NUMBER) ||
Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER) ||
Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
Authorization: 'token ' + env.GITHUB_TOKEN,
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
@@ -32,7 +30,7 @@ if (require.main === module) {
}
}
async function build () {
async function build() {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
@@ -54,8 +52,7 @@ async function build () {
}
if (env.NEXE_VERSION) {
const
target = getTarget(env.NEXE_VERSION),
const target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
@@ -63,15 +60,15 @@ async function build () {
target,
output
}
const stop = keepalive()
if (target.platform === 'alpine') {
await runAlpineBuild(target)
} else {
await nexe.compile(options)
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
@@ -79,7 +76,7 @@ async function build () {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
Authorization: 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
@@ -89,12 +86,12 @@ async function build () {
}
}
function keepalive () {
function keepalive() {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary (file: string) {
function assertNexeBinary(file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
+6 -7
View File
@@ -6,7 +6,8 @@ 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}`
branch
}?circle-token=${env.CIRCLE_TOKEN}`
return got(circle, {
json: true,
body: {
@@ -17,7 +18,7 @@ export function triggerMacBuild(release: NexeTarget, branch: string) {
})
}
export function triggerDockerBuild (release: NexeTarget, branch: string) {
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, {
@@ -38,15 +39,13 @@ export function triggerDockerBuild (release: NexeTarget, branch: string) {
},
headers: {
'Travis-API-Version': '3',
'Authorization': `token ${env.TRAVIS_TOKEN}`
Authorization: `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerWindowsBuild (release: NexeTarget) {
export function triggerWindowsBuild(release: NexeTarget) {
const hasVersion = 'NEXE_VERSION' in env
env.NEXE_VERSION = hasVersion
? env.NEXE_VERSION!.trim()
: release.toString()
env.NEXE_VERSION = hasVersion ? env.NEXE_VERSION!.trim() : release.toString()
return Promise.resolve()
}
+17 -12
View File
@@ -5,7 +5,7 @@ import got = require('got')
import execa = require('execa')
import { appendFileSync } from 'fs'
function alpine (target: NexeTarget) {
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
@@ -26,29 +26,34 @@ RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
`.trim()
}
export async function runAlpineBuild (target: NexeTarget) {
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 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) {
} 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))
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))
}
}
+5 -7
View File
@@ -1,7 +1,7 @@
import { writeFileSync, readFileSync } from 'fs'
/**
* post build step to insert code files into code files.
* post build step to insert code files into code files.
* '{{replace:path/to/file}}' => "file contents"
* And the package.json version.
*/
@@ -10,21 +10,19 @@ 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[]) {
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'))
})
return replacements[+file] ? replacements[+file] : JSON.stringify(readFileSync(file, 'utf8'))
})
writeFileSync(filename, contents)
console.log(`Wrote: ${filename}`)
}
+3
View File
@@ -0,0 +1,3 @@
{
"main": "entry-file.js"
}
+1
View File
@@ -1,3 +1,4 @@
./test/**/*.spec.ts
--check-leaks
--require ts-node/register
--recursive
+48
View File
@@ -0,0 +1,48 @@
import { normalizeOptions } from '../src/options'
import { expect } from 'chai'
import * as path from 'path'
const ext = process.platform === 'win32' ? '.exe' : ''
describe('options', () => {
describe('cwd', () => {
it('should use process.cwd() if nothing is provided', () => {
const options = normalizeOptions()
expect(options.cwd).to.equal(process.cwd())
})
it('should use the main module in a package directory', () => {
const options = normalizeOptions()
expect(options.input).to.equal(path.resolve('./index.js'))
})
it('should resolve pathed options against cwd', () => {
const cwd = '/a/b/c'
const options = normalizeOptions({
cwd,
input: '123.js',
output: 'abc',
temp: './d'
})
expect(options.temp).to.equal(path.resolve(cwd, './d'))
expect(options.input).to.equal(path.resolve(cwd, '123.js'))
expect(options.output).to.equal(path.resolve(cwd, `abc${ext}`))
})
})
describe('output', () => {
it('should work', () => {
const options = normalizeOptions({
output: './some-output'
})
expect(options.output).to.equal(path.resolve(`./some-output${ext}`))
})
it('should default to the input file name if not index', () => {
const options = normalizeOptions({
input: 'src/folder/app.js'
})
expect(options.output).to.equal(path.resolve(`./app${ext}`))
})
it('should default to the folder/project name if filename is index', () => {
const options = normalizeOptions()
expect(options.output).to.equal(path.resolve(`./nexe${ext}`))
})
})
})
+2
View File
@@ -10,7 +10,9 @@ describe('Targets', () => {
['win-ia32-6.11.2', 'windows-x86-6.11.2'],
[{ version: '6.11.2', platform: 'win', arch: 'ia32' }, 'windows-x86-6.11.2'],
['win32-x64-6.11.2', 'windows-x64-6.11.2'],
['win-amd64-6.11.2', 'windows-x64-6.11.2'],
['darwin-x64-v8.4.0', 'mac-x64-8.4.0'],
['macos-x64-v8.4.0', 'mac-x64-8.4.0'],
['static-x86-6.10.3', 'alpine-x86-6.10.3'],
['linux-x32', `linux-x86-${process.version.slice(1)}`],
['alpine-notsupported-6.10.3', `alpine-${arch}-6.10.3`],
+718 -250
View File
File diff suppressed because it is too large Load Diff