Compare commits

..

8 Commits

Author SHA1 Message Date
calebboyd 34c51064f4 feat: build-target options 2017-09-01 00:28:10 -05:00
calebboyd be48e5a2f3 docs: update readme with new usage 2017-08-31 13:59:01 -05:00
calebboyd 0340c01b36 fix: don't call next twice 2017-08-31 10:02:12 -05:00
calebboyd e62cd19b42 feat: targets 2017-08-31 08:46:50 -05:00
calebboyd fc99386199 chore: options, and resource normalization 2017-08-31 01:15:51 -05:00
calebboyd f949bf3040 feat: initial build 2017-08-30 15:32:26 -05:00
calebboyd 0a1e747ca3 chore: inject source-map-support 2017-08-26 23:34:03 -05:00
calebboyd 91aa014b7d feat: initial fuse-box bundling support 2017-08-25 23:23:57 -05:00
28 changed files with 1183 additions and 485 deletions
+2 -4
View File
@@ -1,9 +1,7 @@
os: osx
language: node_js
node_js:
- '6'
script: npm run nexe-build
env:
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
notifications:
email: false
global:
secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
-1
View File
@@ -3,7 +3,6 @@
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"],
"editor.trimAutoWhitespace": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
+40 -35
View File
@@ -7,7 +7,7 @@
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/l/nexe.svg" alt="License"></a>
</p>
<p align="center"><code>npm i nexe -g</code></p>
<p align="center"><code>npm i nexe@beta -g</code></p>
<p align="center">Nexe is a command-line utility that compiles your Node.js application into a single executable file.</p>
<p align="center">
@@ -16,6 +16,7 @@
## Motivation and Features
- Supports production ready builds
- Self contained applications
- Ability to run multiple applications with *different* node.js runtimes.
- Distribute binaries without needing node / npm.
@@ -27,37 +28,40 @@
## Usage
- Application entrypoint:
- Existing application bundle:
`nexe my-app.js`
`nexe my-app-bundle.js -o my-app`
- stdin interface
`rollup -c | nexe --resource "./public/**/*" -o my-app.exe`
`rollup -c | nexe --resource ./public/**/* -o my-app.exe`
For more CLI options see: `nexe --help`
# Advanced
## Including Additional Resources
## Resources
Additional files or resources can be added to the binary by passing `-r "glob/pattern/**/*"`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`
Additional resources can be added to the binary by passing `-r glob/pattern/**/*`. These included files can be read in the application by using `fs.readFile` or `fs.readFileSync`
## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, It may be unavailable ([github releases](https://github.com/nexe/nexe/releases))
or you may want to customize what is built. See `nexe --help` for a list of options available when passing the `--build` option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md)
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. To build node, use the `build` option/flag
Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
## Node.js API
Using Nexe Programatically
#### Example
```javascript
const { compile } = require('nexe')
compile({
input: './my-app.js',
build: true, //required to use patches
output: './my-app.exe',
build: true, //builds node
patches: [
async (compiler, next) => {
await compiler.setFileContentsAsync(
@@ -71,56 +75,60 @@ compile({
console.log('success')
})
```
## NexeOptions
### `options: object`
### `options`
- #### `build: boolean`
- Build node from source (required in beta)
- #### `input: string`
- Input bundle file path
- default: stdin or the current directory's main file (package.json)
- #### `output: string`
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string | object`
- Combination of platform-arch-version. e.g. `'win32-ia32-6.10.3'`
- each segment is optional, and will be merged with the current environment
- default: `process`
- #### `target: string`
- Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'`
- default: `[process.platform, process.arch, process.version.slice(1)].join('-')`
- #### `bundle: string | boolean`
- If a string is provided it must be a valid relative module path
and should provide an export with the following signature:
```typescript
export function createBundle (options: NexeOptions): Promise<string>`
export function createBundle (
filename: string,
options: { name: string, minify: any, cwd: string }
): Promise<string>`
```
- default: true
- default: true, uses the internal fuse-box configuration
- #### `name: string`
- Module friendly name of the application
- default: basename of the input file, or `nexe_${Date.now()}`
- #### `cwd: string`
- Directory nexe will operate on as though it is the cwd
- default: process.cwd()
- #### `build: boolean`
- Build node from source
- #### `version: string`
- The Node version you're building for
- default: `process.version.slice(1)`
- #### `python: string`
- On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed
- default: `null`
- #### `flags: string[]`
- #### `flags: Array<string>`
- Array of node runtime flags to build node with.
- Example: `['--expose-gc']`
- default: `[]`
- #### `configure: string[]`
- #### `configure: Array<string>`
- Array of arguments for the node build configure step
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: string[]`
- #### `make: Array<string>`
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- #### `make: string[]`
- #### `make: Array<string>`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
- #### `resources: string[]`
- #### `resources: Array<string>`
- Array of globs with files to include in the build
- Example: `['./public/**/*']`
- default: `[]`
@@ -135,19 +143,16 @@ compile({
- Example: `{ CompanyName: "ACME Corp" }`
- default: `{}`
- #### `clean: boolean`
- If included, nexe will remove temporary files for the accompanying configuration and exit
- If included, nexe will remove temporary files for accompanying configuration and exit
- #### `enableNodeCli: boolean`
- Enable the original Node CLI (will prevent application cli from working)
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `sourceUrl: string`
- Provide an alternate url for the node source code
- Note: temporary files will still be created for this under the specified version
- Provide an alternate url for the node source. Should be a `.tar.gz`
- #### `loglevel: string`
- Set the loglevel, info, silent, or verbose
- default: `'info'`
- #### `patches: NexePatch[]`
- #### `patches: Array<NexePatch>`
- Userland patches for patching or modifying node source
- default: `[]`
@@ -165,7 +170,7 @@ For examples, see the built in patches: [src/patches](src/patches)
- Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace`
- `readFileAsync(filename: string): Promise<NexeFile>`
- Access (or create) a file within the downloaded Node.js source.
- `files: NexeFile[]`
- `files: Array<NexeFile>`
- The cache of the currently read, modified, or created files within the downloaded Node.js source.
#### `NexeFile`
@@ -175,10 +180,10 @@ For examples, see the built in patches: [src/patches](src/patches)
Any modifications made to `NexeFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
## Native Modules
### Native Modules
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)
Take a look at the [example](examples/native-build/build.js)
- [ ] Implement support `node-pre-gyp#find`.
-5
View File
@@ -1,8 +1,3 @@
machine:
node:
version: 6.11.2
services:
- docker
test:
override:
- npm run nexe-build
+21 -12
View File
@@ -1,13 +1,22 @@
const nexe = require('../..')
nexe.compile({
output: 'native-build',
silent: true,
native: {
zmq: {
additionalFiles: [
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
]
}
}
const { FuseBox } = require('fuse-box')
const { NativeModulePlugin } = require('../../lib/bundling')
const fuse = FuseBox.init({
homeDir: './',
cache: false,
log: true,
debug: true,
output: '$name.js',
plugins: [
new NativeModulePlugin({
zmq: {
additionalFiles: [
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
]
}
})
]
})
fuse.bundle('app')
.target('server')
.instructions(`> index.js`)
fuse.run()
+1
View File
@@ -8,6 +8,7 @@ setInterval(function(){
console.log('sending a multipart message envelope')
pub.send(['kitty cats', 'meow!'])
}, 2500)
console.log('global', global.require.main)
var sub = zmq.socket('sub')
sub.connect('tcp://127.0.0.1:3000')
sub.subscribe('kitty cats')
+5 -3
View File
@@ -4,13 +4,15 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "node build"
"bundle": "node build",
"build-windows": "nexe -i app.js -o app.exe"
},
"author": "",
"license": "ISC",
"dependencies": {
"sqlite3": "^3.1.10",
"zmq": "^2.15.3"
},
"devDependencies": {}
"devDependencies": {
"fuse-box": "^2.2.1-beta.7"
}
}
+1 -2
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env node
const nexe = require('./lib/nexe')
const eol = require('os').EOL
module.exports = nexe
if (require.main === module) {
nexe.compile(nexe.argv).catch((e) => {
process.stderr.write(eol + e.stack, () => process.exit(1))
process.stderr.write(e.stack, () => process.exit(1))
})
}
+963 -128
View File
File diff suppressed because it is too large Load Diff
+7 -8
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.5",
"version": "2.0.0-rc.1",
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
@@ -11,8 +11,7 @@
"scripts": {
"nexe-build": "ts-node tasks/build",
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm test && npm run build",
"test": "mocha test/**/*.spec.ts",
"prepublish": "npm run build",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
"build": "tsc --declaration"
},
@@ -35,7 +34,7 @@
"app-builder": "^5.1.0",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "2.2.31",
"fuse-box": "^2.2.1",
"globby": "^6.1.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
@@ -45,18 +44,18 @@
"uglify-js": "3.0.28"
},
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/bluebird": "^3.5.8",
"@types/chalk": "^0.4.31",
"@types/execa": "0.7.0",
"@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
"@types/mocha": "^2.2.42",
"@types/ora": "^0.3.31",
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28",
"chai": "^4.1.2",
"execa": "0.8.0",
"got": "^7.1.0",
"mocha": "^3.5.0",
"mocha": "^3.2.0",
"prettier": "^1.6.1",
"ts-node": "3.3.0",
"typescript": "^2.4.1"
+8 -7
View File
@@ -20,8 +20,9 @@ export function embedDotNode(
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 module = Object.keys(options).find(x =>
Boolean(~file.absPath.indexOf(path.join('node_modules', x)))
)!
const bindingName = path.basename(file.absPath)
const settings = options[module]
const moduleDir = hashName(contents)
@@ -30,7 +31,7 @@ export function embedDotNode(
'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) {
if (settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
@@ -104,13 +105,13 @@ export class BindingsRewrite {
public nativeModulePaths: string[] = []
public rewrite = false
isRequire(node: any, moduleName: string) {
return node.callee.name === 'require' && node.arguments[0].value === moduleName
isRequireBindings(node: any) {
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
}
onNode(absolutePath: string, node: any, parent: any) {
if (node.type === 'CallExpression') {
if (this.isRequire(node, 'bindings') && parent.type === 'VariableDeclarator') {
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
/**
* const loadBindings = require('bindings');
* -> const loadBindings = String('');
@@ -122,7 +123,7 @@ export class BindingsRewrite {
return
}
if (this.isRequire(node, 'bindings') && parent.type === 'CallExpression') {
if (this.isRequireBindings(node) && parent.type === 'CallExpression') {
/**
*const bindings = require('bindings')('native-module')....
* -> const bindings = require('./path/to/native/module.node').....
+6 -5
View File
@@ -8,7 +8,6 @@ export interface FuseBoxFile {
dependencies: string[]
requiresRegeneration: boolean
}
consume(): void
loadContents(): void
makeAnalysis(
parsingOptions?: any,
@@ -21,16 +20,19 @@ export interface FuseBoxFile {
): void
}
export default function(options: ExtractNodeModuleOptions = {}) {
export default function(options: ExtractNodeModuleOptions) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test = /node_modules.*(\.js|\.node)$|\.node$/
public test: RegExp
public limit2Project = false
private modules: (keyof ExtractNodeModuleOptions)[]
constructor(public options = {}) {}
constructor(public options: ExtractNodeModuleOptions) {
this.options = options
this.test = new RegExp(`node_modules\/(${Object.keys(options).join('|')}).*\.js|\.node$`)
}
init(context: any) {
context.allowExtension('.node')
@@ -45,7 +47,6 @@ export class NativeModulePlugin {
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
+12 -14
View File
@@ -1,12 +1,11 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } from 'fuse-box'
import { readFileAsync, writeFileAsync } from '../util'
import NativeModulePlugin from '../bundling/fuse-native-module-plugin'
import { NexeOptions } from '../options'
import { readFileAsync } from '../util'
//import NativeModulePlugin from './fuse-native-module-plugin'
function createBundle(options: NexeOptions) {
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin(), NativeModulePlugin(options.native)]
if (options.compress) {
function createBundle(filename: string, options: { name: string; minify: any; cwd: string }) {
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
if (options.minify) {
plugins.push(
QuantumPlugin({
target: 'server',
@@ -17,7 +16,7 @@ function createBundle(options: NexeOptions) {
}
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_LOG) || false,
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
homeDir: options.cwd,
sourceMaps: false,
writeBundles: false,
@@ -25,7 +24,7 @@ function createBundle(options: NexeOptions) {
target: 'server',
plugins
})
fuse.bundle(options.name).instructions(`> ${options.input}`)
fuse.bundle(options.name).instructions(`> ${filename}`)
return fuse.run().then(x => {
let output = ''
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
@@ -49,11 +48,10 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
producer = require(compiler.options.bundle).createBundle
}
compiler.input = await producer(compiler.options)
if ('string' === typeof compiler.options.debugBundle) {
await writeFileAsync(compiler.options.debugBundle, compiler.input)
}
compiler.input = await producer(compiler.options.input, {
cwd: compiler.options.cwd,
name: compiler.options.name,
minify: compiler.options.compress
})
return next()
}
+17 -38
View File
@@ -8,9 +8,7 @@ import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
import { NexeOptions, nexeVersion } from './options'
import { NexeTarget } from './target'
import download = require('download')
import { getLatestGitRelease } from './releases'
import { IncomingMessage } from 'http'
import { getLatestGitRelease, storeAsset } from './releases'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
@@ -32,13 +30,12 @@ export class NexeCompiler {
private env = { ...process.env }
private compileStep: { modify: Function; log: Function }
public log = new Logger(this.options.loglevel)
public src: string
public src = join(this.options.temp, this.options.version)
public files: NexeFile[] = []
public input: string
public bundledInput?: string
public output: string | null
public targets: NexeTarget[]
public target: NexeTarget
public resources: { bundle: string; index: { [key: string]: number[] } } = {
index: {},
bundle: ''
@@ -47,16 +44,13 @@ export class NexeCompiler {
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: string
private nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
constructor(public options: NexeOptions) {
const { python } = (this.options = options)
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
this.src = join(this.options.temp, this.target.version)
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + nexeVersion, 'info')
if (python) {
if (isWindows) {
@@ -99,7 +93,7 @@ export class NexeCompiler {
return this.log.flush()
}
public getNodeExecutableLocation(target?: NexeTarget) {
private _getNodeExecutableLocation(target?: NexeTarget) {
if (target) {
return join(this.options.temp, target.toString())
}
@@ -137,35 +131,19 @@ export class NexeCompiler {
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
)
await this._runBuildCommandAsync(make, buildOptions)
return createReadStream(this.getNodeExecutableLocation())
return createReadStream(this._getNodeExecutableLocation())
}
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
const downloadOptions: any = {
headers: {
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
}
const githubRelease = await getLatestGitRelease(downloadOptions)
const githubRelease = await getLatestGitRelease()
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
if (!asset) {
throw new Error(`${assetName} not available, create it using the --build flag`)
throw new Error(`${assetName} not available, create one using --build`)
}
const filename = this.getNodeExecutableLocation(target)
await download(
asset.browser_download_url,
dirname(filename),
downloadOptions
).on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep.modify(`Downloading...${(current / total * 100).toFixed()}%`)
})
})
const filename = this._getNodeExecutableLocation(target)
await storeAsset(asset, dirname(filename))
return createReadStream(filename)
}
@@ -194,13 +172,14 @@ export class NexeCompiler {
async compileAsync(target: NexeTarget) {
const step = (this.compileStep = this.log.step('Compiling result'))
const build = this.options.build
const location = this.getNodeExecutableLocation(build ? undefined : target)
const location = this._getNodeExecutableLocation(target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
const header = this._generateHeader()
if (!build && !binary) {
step.modify('Fetching prebuilt binary')
if (target && !build) {
binary = await this._fetchPrebuiltBinaryAsync(target)
}
if (!binary) {
binary = await this._buildAsync()
step.log('Node binary compiled')
+5 -11
View File
@@ -9,13 +9,11 @@ import {
NexePatch
} from './options'
import cli from './steps/cli'
import bundle from './steps/bundle'
import bundle from './bundling/fuse'
import download from './steps/download'
import shim from './steps/shim'
import artifacts from './steps/artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import { NexeTarget } from './target'
async function compile(
compilerOptions?: Partial<NexeOptions>,
@@ -26,21 +24,17 @@ async function compile(
const build = compiler.options.build
if (options.clean) {
let path = compiler.src
if (!options.build) {
path = compiler.getNodeExecutableLocation(compiler.options.targets[0] as NexeTarget)
}
const step = compiler.log.step('Cleaning up nexe build artifacts...')
step.log(`Deleting contents at: ${path}`)
await rimrafAsync(path)
step.log(`Deleted contents at: ${path}`)
step.log(`Deleting directory and contents at: ${compiler.src}`)
await rimrafAsync(compiler.src)
step.log(`Deleted directory and contents at: ${compiler.src}`)
return compiler.quit()
}
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
const nexe = compose(resource, bundle, cli, buildSteps, shim)
const nexe = compose(resource, bundle, cli, buildSteps)
return callback
? void nexe(compiler).then(
() => callback && callback(null),
+56 -69
View File
@@ -1,12 +1,11 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { isWindows, padRight } from './util'
import { basename, extname, join, isAbsolute, relative, dirname } from 'path'
import { isWindows } from './util'
import { basename, extname, join, isAbsolute, relative } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os'
import * as c from 'chalk'
export const nexeVersion = '2.0.0-rc.5'
export const nexeVersion = '2.0.0-rc.1'
export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
@@ -19,6 +18,7 @@ export interface NexeOptions {
targets: (string | NexeTarget)[]
name: string
cwd: string
version: string
flags: string[]
configure: string[]
vcBuild: string[]
@@ -30,17 +30,14 @@ export interface NexeOptions {
enableNodeCli: boolean
bundle: boolean | string
patches: (string | NexePatch)[]
native: any
empty: boolean
sourceUrl?: string
python?: string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
fakeArgv?: boolean
verbose?: boolean
info?: boolean
ico?: string
debugBundle?: boolean
warmup?: string
compress?: boolean
clean?: boolean
@@ -50,7 +47,12 @@ export interface NexeOptions {
downloadOptions: any
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
const defaults = {
version: process.version.slice(1),
flags: [],
cwd: process.cwd(),
configure: [],
@@ -66,61 +68,51 @@ const defaults = {
const alias = {
i: 'input',
o: 'output',
v: 'version',
t: 'target',
b: 'build',
n: 'name',
v: 'version',
r: 'resource',
a: 'resource',
p: 'python',
f: 'flag',
c: 'configure',
m: 'make',
s: 'snapshot',
h: 'help',
l: 'loglevel',
'fake-argv': 'fakeArgv'
l: 'loglevel'
}
const argv = parseArgv(process.argv, { alias, default: defaults })
const g = c.gray
let help = `
${c.bold('nexe <entry-file> [options]')}
const help =
`
nexe --help CLI OPTIONS
${c.underline.bold('Options:')}
-b --build -- build from source
-i --input =index.js -- application entry point
-o --output =my-app.exe -- path to output file
-t --target =win32-x64-6.10.3 -- *target a prebuilt binary
-n --name =my-app -- main app module name
-v --version =${padRight(process.version.slice(1), 23)}-- node version
-p --python =/path/to/python2 -- python executable
-f --flag ="--expose-gc" -- *v8 flags to include during compilation
-c --configure ="--with-dtrace" -- *pass arguments to the configure step
-m --make ="--loglevel" -- *pass arguments to the make/build step
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
-r --resource =./paths/**/* -- *embed file bytes within the binary
--bundle =./path/to/config -- pass a module path that exports nexeBundle
--temp =./path/to/temp -- nexe temp files (for downloads and source builds)
--no-bundle -- set when input is already bundled
--cwd -- set the current working directory for the command
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--clean -- force download of sources
--enableNodeCli -- enable node cli enforcement (blocks app cli)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--silent -- disable logging
--verbose -- set logging to verbose
-i --input ${g('=index.js')} -- application entry point
-o --output ${g('=my-app.exe')} -- path to output file
-t --target ${g('=8.4.0-x64')} -- node version description
-n --name ${g('=my-app')} -- main app module name
-* variable key name * option can be used more than once`.trim() + EOL
-r --resource -- *embed files (glob) within the binary
${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 TODO -- fake argv[1] with entry file
--clean -- force download of sources
--silent -- disable logging
--verbose -- set logging to verbose
-* variable key name * option can be used more than once`.trim()
help = EOL + help + EOL
function flatten(...args: any[]): string[] {
function flattenFilter(...args: any[]): string[] {
return ([] as string[]).concat(...args).filter(x => x)
}
@@ -148,11 +140,7 @@ function tryResolveMainFileName(cwd: string) {
filename = basename(file).replace(extname(file), '')
} catch (_) {}
if (filename === 'index' && basename(cwd)) {
return basename(cwd)
}
return filename ? filename : 'nexe_' + Date.now()
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
}
function extractLogLevel(options: NexeOptions) {
@@ -167,8 +155,7 @@ function extractName(options: NexeOptions) {
if (!name && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '')
}
name = name === 'index' ? tryResolveMainFileName(options.cwd) : name
name = name || tryResolveMainFileName(options.cwd)
return name.replace(/\.exe$/, '')
}
@@ -184,15 +171,13 @@ function findInput(input: string, cwd: string) {
if (isEntryFile(maybeInput)) {
return maybeInput
}
if (!process.stdin.isTTY) {
return ''
}
try {
const main = require.resolve(cwd)
return './' + relative(cwd, main)
} catch (e) {
void e
}
return ''
}
@@ -206,29 +191,31 @@ function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOption
const opts = options as any
options.temp = process.env.NEXE_TEMP || join(options.cwd, '.nexe')
options.input = findInput(options.input, options.cwd)
options.name = extractName(options)
options.input = findInput(options.input, options.cwd)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget)
options.make = flatten(options.vcBuild, options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
options.flags = flattenFilter(opts.flag, options.flags)
options.targets = flattenFilter(opts.target, options.targets)
options.make = flattenFilter(options.vcBuild, options.make)
options.configure = flattenFilter(options.configure)
options.resources = flattenFilter(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (!options.targets.length) {
options.targets.push(getTarget())
}
options.targets = options.targets.map(getTarget)
if (options.build) {
if (options.build && options.targets.length) {
const { arch } = options.targets[0] as NexeTarget
if (isWindows) {
options.make = Array.from(new Set(options.make.concat(arch)))
options.make = Array.from(new Set(options.make.concat([arch])))
} else {
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
}
}
if (!options.targets.length) {
options.targets = [getTarget(options.version)]
}
options.patches = options.patches.map(x => {
if (typeof x === 'string') {
return require(x).default
+1 -1
View File
@@ -1,7 +1,7 @@
import { NexeCompiler } from '../compiler'
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.target.version.startsWith('8.2')) {
if (!compiler.options.version.startsWith('8.2')) {
return next()
}
+1 -9
View File
@@ -12,15 +12,7 @@ export default async function nodeRc(compiler: NexeCompiler, next: () => Promise
let value = options[key]
const isVar = /^[A-Z_]+$/.test(value)
value = isVar ? value : `"${value}"`
file.contents = file.contents.replace(
new RegExp(`VALUE "${key}",*`),
`VALUE "${key}", ${value}`
)
})
;['PRODUCTVERSION', 'FILEVERSION'].forEach(x => {
if (options[x]) {
file.contents = file.contents.replace(new RegExp(x + ' .*$', 'm'), `${x} ${options[x]}`)
}
file.contents.replace(new RegExp(`VALUE "${key}",*`), `VALUE "${key}", ${value}`)
})
return next()
+5
View File
@@ -42,6 +42,11 @@ export function getLatestGitRelease(options?: any) {
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options)
}
export async function storeAsset(asset: GitAsset, dest: string, headers?: any) {
const options = headers ? { headers } : undefined
await download(asset.browser_download_url, dest, options as any)
}
export async function getUnBuiltReleases(options?: any) {
const nodeReleases = await getJson<NodeRelease[]>(
'https://nodejs.org/download/release/index.json'
+2 -3
View File
@@ -3,7 +3,6 @@ import { pathExistsAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
@@ -28,8 +27,8 @@ function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options
* @param {*} next
*/
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
const { src, log, targets: [{ version }] } = compiler
const { sourceUrl, downloadOptions } = compiler.options
const { src, log } = compiler
const { version, sourceUrl, downloadOptions } = compiler.options
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
const step = log.step(`Downloading Node.js source from: ${url}`)
if (await pathExistsAsync(src)) {
-18
View File
@@ -1,18 +0,0 @@
import { NexeCompiler } from '../compiler'
export default function(compiler: NexeCompiler, next: () => Promise<void>) {
if (!compiler.options.fakeArgv) {
return next()
}
const nty = !process.stdin.isTTY
const input = nty ? '[stdin]' : compiler.options.input
compiler.input =
`!(() => {
var r = require('path').resolve;
process.argv.splice(1,0, ${nty ? `'${input}'` : `r("${input}")`});
})();` + compiler.input
return next()
}
+5 -6
View File
@@ -8,15 +8,14 @@ export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform | string
arch: NodeArch | string
platform: NodePlatform
arch: NodeArch
}
//TODO bsd
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
win: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
@@ -37,7 +36,7 @@ function isVersion(x: string) {
if (!x) {
return false
}
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ''))
return /\d/.test(x.replace(/v|\./g, ''))
}
function isPlatform(x: string): x is NodePlatform {
@@ -62,7 +61,7 @@ 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 {
export function getTarget(target: string | NexeTarget = ''): NexeTarget {
let arch = process.arch as NodeArch,
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
@@ -76,7 +75,7 @@ export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget
.split('-')
.forEach(x => {
if (isVersion(x)) {
version = x.replace(/v/g, '')
version = x
}
if (isPlatform(x)) {
platform = prettyPlatform[x]
-5
View File
@@ -22,10 +22,6 @@ function falseOnEnoent(e: any) {
throw e
}
function padRight(str: string, l: number) {
return (str + ' '.repeat(l)).substr(0, l)
}
function dequote(input: string) {
input = input.trim()
const singleQuote = input.startsWith("'") && input.endsWith("'")
@@ -61,7 +57,6 @@ function isDirectoryAsync(path: string) {
export {
dequote,
padRight,
isWindows,
rimrafAsync,
statAsync,
+5 -9
View File
@@ -12,16 +12,13 @@ import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.CIRCLECI),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.TRAVIS),
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)'
}
headers = { 'Authorization': 'token ' + env.GITHUB_TOKEN }
if (require.main === module) {
if (!isPullRequest) {
@@ -60,7 +57,7 @@ async function build () {
options = {
empty: true,
build: true,
target,
target, //FIXME
output
}
@@ -81,8 +78,7 @@ async function build () {
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
'Content-Type': 'application/octet-stream'
}
})
console.log(target + ' uploaded.')
+3 -3
View File
@@ -3,7 +3,7 @@ import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerMacBuild(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, {
@@ -14,7 +14,7 @@ export function triggerMacBuild(release: NexeTarget, branch: string) {
config: {
merge_mode: 'deep_merge',
env: {
NEXE_VERSION: release.toString()
NEXE_VERSION: release
}
}
}
@@ -26,7 +26,7 @@ export function triggerMacBuild(release: NexeTarget, branch: string) {
})
}
export function triggerDockerBuild (release: NexeTarget, branch: string) {
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, {
+17 -58
View File
@@ -1,73 +1,32 @@
import { NexeTarget } from '../src/target'
import { writeFileAsync, readFileAsync } from '../src/util'
import { spawn } from 'child_process'
import got = require('got')
import { createWriteStream, WriteStream } from 'fs'
import { writeFileAsync } from '../src/util'
import exec = require('execa')
function alpine (target: NexeTarget, nexeVersion: string) {
function alpine (target: NexeTarget, nexeVersion: string) {
const version = target.version
return `
FROM i386/alpine:3.4
FROM i386/alpine:3.6
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
ENV NODE_VERSION=${target.version}
ENV NEXE_VERSION=beta
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
cd /node-v\${NODE_VERSION} && \
RUN curl -sSL https://nodejs.org/dist/v${version}/node-v${version}.tar.gz | tar -xz && \
cd /node-v${version} && \
./configure --prefix=/usr --fully-static && \
make -j$(getconf _NPROCESSORS_ONLN) && \
make -j$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
make install && \
paxctl -cm /usr/bin/node
RUN mkdir /nexe_temp && mv node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
`.trim()
}
RUN mkdir nexe_temp && mv node-v${version} nexe_temp/${version}
RUN npm i nexe@beta -g
ENV NEXE_TEMP=/nexe_temp
export async function runAsync (command: string, output: WriteStream) {
const commands = command.split(/\r?\n/)
.filter(x => x.trim())
.map(x => x.trim().split(' '))
const sequence = Promise.resolve()
for (const command of commands) {
await new Promise((resolve, reject) => {
const cp = spawn(command.shift() as string, command)
cp.stderr.pipe(output)
cp.stdout.pipe(output)
const close = function (this: any, e: Error) {
this.kill()
e ? reject(e) : resolve()
}
cp.on('error', (e) => {
output.write(e.stack)
})
.on('close', close)
.on('exit', close)
})
}
RUN nexe --empty -c="--fully-static" -o /nexe-out`.trim()
}
export async function runAlpineBuild (target: NexeTarget, nexeVersion: string) {
await writeFileAsync('Dockerfile', alpine(target, nexeVersion))
const outFilename = 'nexe-alpine-build-log.txt'
const output = createWriteStream(outFilename)
try {
await runAsync(`
docker build -t nexe-alpine .
docker run -d --name nexe nexe-alpine sh
docker cp nexe:/out out
docker rm nexe
`, output)
} catch(e) {
console.log(e)
} finally {
output.close()
await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
body: await readFileAsync(outFilename),
method: 'PUT'
})
.then(x => console.log(x.body))
.catch(e => console.log('Error posting log', e))
}
const option = { stido: 'inherit' } as any
await exec('docker', ['build', '-t', 'nexe-alpine', '.'], option)
await exec('docker', ['run', '--name', 'nexe', 'nexe-alpine'], option)
await exec('docker', ['cp', 'nexe:/nexe-out', 'out'], option)
await exec('docker', ['rm', 'nexe'], option)
}
-3
View File
@@ -1,3 +0,0 @@
--check-leaks
--require ts-node/register
--recursive
-28
View File
@@ -1,28 +0,0 @@
import { padRight, isWindows } from '../src/util'
import { expect } from 'chai'
import { blue as b } from 'chalk'
import { getTarget, NexeTarget } from '../src/target'
const arch = process.arch === 'ia32' ? 'x86' : process.arch
describe('Targets', () => {
[
['win-ia32-6.11.2', 'windows-x86-6.11.2'],
[{ version: '6.11.2', platform: 'win', arch: 'ia32' }, 'windows-x86-6.11.2'],
['win32-x64-6.11.2', 'windows-x64-6.11.2'],
['darwin-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)}"`)
})
})