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
59 changed files with 2692 additions and 1851 deletions
+2 -1
View File
@@ -4,5 +4,6 @@ end_of_line = lf
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace=true #https://github.com/Microsoft/vscode/issues/23983#event-1087502108
#trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
+7
View File
@@ -0,0 +1,7 @@
language: node_js
node_js:
- '6'
script: npm run nexe-build
env:
global:
secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
+13 -1
View File
@@ -2,5 +2,17 @@
"typescript.tsdk": "./node_modules/typescript/lib", "typescript.tsdk": "./node_modules/typescript/lib",
"prettier.semi": false, "prettier.semi": false,
"prettier.singleQuote": true, "prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"] "prettier.typescriptEnable": ["typescript"],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"search.exclude": {
"lib/**/*": true,
"**/node_modules": true,
"**/bower_components": true
}
} }
+3 -3
View File
@@ -5,9 +5,9 @@
Nexe 2.0 is a rewrite to enable some new features. These include: Nexe 2.0 is a rewrite to enable some new features. These include:
* Quick Builds! * Quick Builds!
* Userland build patches * Userland build patches
* New Resource access * Resource storage/access rewrite
* stdin and stdout interfaces * stdin interface
* Opt-in bundling * Optional, pluggable bundling
### Breaking Changes ### Breaking Changes
* New options -- Please see the [readme](README.md#options) * New options -- Please see the [readme](README.md#options)
+34 -32
View File
@@ -16,7 +16,8 @@
## Motivation and Features ## Motivation and Features
- Supports production ready, ([securable](#security)) builds - Supports production ready builds
- Self contained applications
- Ability to run multiple applications with *different* node.js runtimes. - Ability to run multiple applications with *different* node.js runtimes.
- Distribute binaries without needing node / npm. - Distribute binaries without needing node / npm.
- Idempotent builds - Idempotent builds
@@ -25,13 +26,11 @@
- Flexible build pipeline - Flexible build pipeline
- Cross platform builds - Cross platform builds
## Usage (Beta) ## Usage
*Note: V2 API is still subject to change. * For v1 see [V1-EOL](https://github.com/nexe/nexe/tree/V1-EOL)
- Existing application bundle: - Existing application bundle:
`nexe -i ./my-app-bundle.js -o ./my-app.exe` `nexe my-app-bundle.js -o my-app`
- stdin interface - stdin interface
@@ -45,7 +44,7 @@ Additional resources can be added to the binary by passing `-r glob/pattern/**/*
## Compiling Node ## Compiling Node
By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. These options are supported out of the box, and subsequent builds with compatible options will result in instant build times. 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. Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation.
@@ -57,11 +56,12 @@ Using Nexe Programatically
```javascript ```javascript
const nexe = require('nexe') const { compile } = require('nexe')
nexe.compile({ compile({
input: './my-app-bundle.js' input: './my-app.js',
output: './my-app.exe' output: './my-app.exe',
build: true, //builds node
patches: [ patches: [
async (compiler, next) => { async (compiler, next) => {
await compiler.setFileContentsAsync( await compiler.setFileContentsAsync(
@@ -89,13 +89,22 @@ nexe.compile({
- #### `target: string` - #### `target: string`
- Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'` - Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'`
- default: `[process.platform, process.arch, process.version.slice(1)].join('-')` - default: `[process.platform, process.arch, process.version.slice(1)].join('-')`
- #### `bundle: string` - #### `bundle: string | boolean`
- Path to a custom bundling function. When executed it should either return a fusebox producer, or a string. - If a string is provided it must be a valid relative module path
- If it is a string it will be required and the export `nexeBundle` will be used and should provide an export with the following signature:
- Defaults to the internal fuse-box configuration ```typescript
export function createBundle (
filename: string,
options: { name: string, minify: any, cwd: string }
): Promise<string>`
```
- default: true, uses the internal fuse-box configuration
- #### `name: string` - #### `name: string`
- Module friendly name of the application - Module friendly name of the application
- default: basename of the input file, or `nexe_${Date.now()}` - 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()
- #### `version: string` - #### `version: string`
- The Node version you're building for - The Node version you're building for
- default: `process.version.slice(1)` - default: `process.version.slice(1)`
@@ -112,11 +121,10 @@ nexe.compile({
- Example: `['--with-dtrace', '--dest-cpu=x64']` - Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]` - default: `[]`
- #### `make: Array<string>` - #### `make: Array<string>`
- Array of arguments for the node build make step - Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` - default: `[]` or `['nosign', 'release']` for non windows systems
- #### `vcBuild: Array<string>` - #### `make: Array<string>`
- Array of arguments for the node build step on windows - Alias for `make` option
- default: `['nosign', 'release']`
- #### `snapshot: string` - #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build - path to a file to be used as the warmup snapshot for the build
- default: `null` - default: `null`
@@ -144,10 +152,6 @@ nexe.compile({
- #### `loglevel: string` - #### `loglevel: string`
- Set the loglevel, info, silent, or verbose - Set the loglevel, info, silent, or verbose
- default: `'info'` - default: `'info'`
- #### `padding`
- Advanced option for controlling the size available in the executable.
- It must be larger than the bundle + resources in bytes
- default: 3, 6, 9, 16, 25, or 40 MB sizes are selected automatically.
- #### `patches: Array<NexePatch>` - #### `patches: Array<NexePatch>`
- Userland patches for patching or modifying node source - Userland patches for patching or modifying node source
- default: `[]` - default: `[]`
@@ -164,26 +168,24 @@ For examples, see the built in patches: [src/patches](src/patches)
- Quickly set a file's contents within the downloaded Node.js source. - Quickly set a file's contents within the downloaded Node.js source.
- `replaceInFileAsync(filename: string, ...replaceArgs): Promise<void>` - `replaceInFileAsync(filename: string, ...replaceArgs): Promise<void>`
- Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace` - Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace`
- `readFileAsync(filename: string): Promise<SourceFile>` - `readFileAsync(filename: string): Promise<NexeFile>`
- Access (or create) a file within the downloaded Node.js source. - Access (or create) a file within the downloaded Node.js source.
- `files: Array<SourceFile>` - `files: Array<NexeFile>`
- The cache of currently read, modified, or created files within the downloaded Node.js source. - The cache of the currently read, modified, or created files within the downloaded Node.js source.
#### `SourceFile` #### `NexeFile`
- `contents: string` - `contents: string`
- `absPath: string`
- `filename: string` - `filename: string`
Any modifications made to `SourceFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`. Any modifications made to `NexeFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`.
### Native Modules ### Native Modules
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module. Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
Take a look at the [example](examples/native-build/build.js) Take a look at the [example](examples/native-build/build.js)
Future: Implement support `node-pre-gyp#find`. - [ ] Implement support `node-pre-gyp#find`.
## Security
A common use case for Nexe is production deployment. When distributing executables it is important to [sign](https://en.wikipedia.org/wiki/Code_signing) them before distributing. Nexe was designed specifically to not mangle the binary it produces, this allows the checksum and signature of the size and location offsets to be maintained through the code signing process.
## Maintainers ## Maintainers
+9 -13
View File
@@ -1,25 +1,21 @@
environment: environment:
node_version: "6" CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
branches: branches:
except: except:
- master - master
install: install:
- ps: Install-Product node $env:node_version - ps: Install-Product node 6
- npm i - npm i
build: off build: off
test: off test: off
before_build:
- npm run log-node-version
build_script: build_script:
- npm run task-build-windows - npm run nexe-build
artifacts: artifacts:
- path: .nexe\6.11.2\Release\node.exe - path: .nexe\**\node.exe
cache: cache:
- node_modules - node_modules
+3
View File
@@ -0,0 +1,3 @@
test:
override:
- npm run nexe-build
+2 -3
View File
@@ -1,6 +1,5 @@
const { FuseBox } = require('fuse-box') const { FuseBox } = require('fuse-box')
const nexe = '../..' const { NativeModulePlugin } = require('../../lib/bundling')
const { NativeModulePlugin } = require(nexe + '/lib/bundling/fuse')
const fuse = FuseBox.init({ const fuse = FuseBox.init({
homeDir: './', homeDir: './',
cache: false, cache: false,
@@ -18,6 +17,6 @@ const fuse = FuseBox.init({
] ]
}) })
fuse.bundle('app') fuse.bundle('app')
.target('electron') .target('server')
.instructions(`> index.js`) .instructions(`> index.js`)
fuse.run() fuse.run()
+1 -1
View File
@@ -5,6 +5,6 @@ module.exports = nexe
if (require.main === module) { if (require.main === module) {
nexe.compile(nexe.argv).catch((e) => { nexe.compile(nexe.argv).catch((e) => {
process.stderr.write(e.message, () => process.exit(1)) process.stderr.write(e.stack, () => process.exit(1))
}) })
} }
-1
View File
@@ -19,4 +19,3 @@ Please also provide:
- Nexe version: - Nexe version:
- Python Version: - Python Version:
+1732 -724
View File
File diff suppressed because it is too large Load Diff
+12 -9
View File
@@ -2,15 +2,14 @@
"name": "nexe", "name": "nexe",
"description": "Create a single executable out of your Node.js application", "description": "Create a single executable out of your Node.js application",
"license": "MIT", "license": "MIT",
"version": "2.0.0-beta.7", "version": "2.0.0-rc.1",
"contributors": [ "contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)", "Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>", "Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>" "Caleb Boyd <caleb.boyd@hotmail.com>"
], ],
"scripts": { "scripts": {
"task-build-windows": "node -e \"require('.').compile({ empty: true })\"", "nexe-build": "ts-node tasks/build",
"log-node-version": "node -e \"console.log([process.platform, process.arch, process.version.slice(1)].join('-'))\"",
"prebuild": "rimraf lib && npm run lint", "prebuild": "rimraf lib && npm run lint",
"prepublish": "npm run build", "prepublish": "npm run build",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"", "lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
@@ -21,7 +20,8 @@
"url": "git://github.com/nexe/nexe.git" "url": "git://github.com/nexe/nexe.git"
}, },
"files": [ "files": [
"lib" "lib",
"source-map-support.js"
], ],
"main": "index.js", "main": "index.js",
"bin": { "bin": {
@@ -32,29 +32,32 @@
}, },
"dependencies": { "dependencies": {
"app-builder": "^5.1.0", "app-builder": "^5.1.0",
"bluebird": "^3.5.0",
"chalk": "^1.1.3", "chalk": "^1.1.3",
"download": "^6.2.0", "download": "^6.2.0",
"fuse-box": "^2.2.1", "fuse-box": "^2.2.1",
"globby": "^6.1.0", "globby": "^6.1.0",
"memory-fs": "^0.4.1",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nigel": "^2.0.2",
"ora": "^1.2.0", "ora": "^1.2.0",
"pifi": "1.0.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
"tar": "^3.1.3" "uglify-js": "3.0.28"
}, },
"devDependencies": { "devDependencies": {
"@types/bluebird": "^3.5.8", "@types/bluebird": "^3.5.8",
"@types/chalk": "^0.4.31", "@types/chalk": "^0.4.31",
"@types/execa": "0.7.0",
"@types/globby": "^0.6.0", "@types/globby": "^0.6.0",
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29", "@types/mkdirp": "^0.3.29",
"@types/ora": "^0.3.31", "@types/ora": "^0.3.31",
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28", "@types/rimraf": "0.0.28",
"execa": "0.8.0",
"got": "^7.1.0",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"prettier": "^1.4.4", "prettier": "^1.6.1",
"ts-node": "3.3.0",
"typescript": "^2.4.1" "typescript": "^2.4.1"
} }
} }
File diff suppressed because one or more lines are too long
-62
View File
@@ -1,62 +0,0 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from './util'
import { promisify, map } from 'bluebird'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from './compiler'
const mkdirpAsync = promisify(mkdirp)
const unlinkAsync = (promisify(unlink) as any) as (path: string) => PromiseLike<void>
const readdirAsync = promisify(readdir)
function readDirAsync(dir: string): PromiseLike<string[]> {
return readdirAsync(dir)
.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then((x: boolean) => (x ? readDirAsync(path) : path as any))
})
.reduce((a: string[], b: string[] | string) => a.concat(b), [])
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await map(tmpFiles, async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
await next()
await map(tmpFiles, x => unlinkAsync(x))
return map(compiler.files, async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
}
+70
View File
@@ -1,6 +1,76 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import * as child from 'child_process' import * as child from 'child_process'
import { createHash } from 'crypto'
export interface ExtractNodeModuleOptions {
[key: string]: { additionalFiles: string[] } | true
}
function hashName(name: string | Buffer) {
return createHash('md5')
.update(name)
.digest('hex')
.toString()
.slice(0, 8)
}
export function embedDotNode(
options: ExtractNodeModuleOptions,
file: { contents: string; absPath: string }
) {
const contents = fs.readFileSync(file.absPath)
const module = Object.keys(options).find(x =>
Boolean(~file.absPath.indexOf(path.join('node_modules', x)))
)!
const bindingName = path.basename(file.absPath)
const settings = options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
})
let segments = [moduleDir]
while (depth--) {
segments.push(hashName(moduleDir + depth))
}
segments.push(bindingName)
file.contents += `
var cwd = process.cwd()
var bindingFileParts = ${JSON.stringify(segments)};
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
mkdirp(path.dirname(bindingFile));
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
${settings.additionalFiles.reduce((code, filename, i) => {
const contents = fs.readFileSync(path.join(path.dirname(file.absPath), filename))
return (code += `
var file${i} = '${contents.toString('base64')}';
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename.split('../').join('')}');
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
}
function findNativeModulePath(filePath: string, bindingsArg: string) { function findNativeModulePath(filePath: string, bindingsArg: string) {
const dirname = path.dirname(filePath) const dirname = path.dirname(filePath)
+70
View File
@@ -0,0 +1,70 @@
import { BindingsRewrite, embedDotNode, ExtractNodeModuleOptions } from './bindings-rewrite'
export interface FuseBoxFile {
info: { absPath: string }
absPath: string
contents: string
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export default function(options: ExtractNodeModuleOptions) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test: RegExp
public limit2Project = false
private modules: (keyof ExtractNodeModuleOptions)[]
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')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
embedDotNode(this.options, file)
return
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}
]
})
}
}
+51 -131
View File
@@ -1,137 +1,57 @@
import { BindingsRewrite } from './bindings-rewrite' import { NexeCompiler } from '../compiler'
import { createHash } from 'crypto' import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } from 'fuse-box'
import { readFileSync } from 'fs' import { readFileAsync } from '../util'
import { dirname, join, basename } from 'path' //import NativeModulePlugin from './fuse-native-module-plugin'
function hashName(name: string | Buffer) { function createBundle(filename: string, options: { name: string; minify: any; cwd: string }) {
return createHash('md5').update(name).digest('hex').toString().slice(0, 8) const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
} if (options.minify) {
plugins.push(
export interface FuseBoxFile { QuantumPlugin({
info: { absPath: string } target: 'server',
absPath: string uglify: true,
contents: string bakeApiIntoBundle: options.name
analysis: {
dependencies: string[]
requiresRegeneration: boolean
}
loadContents(): void
makeAnalysis(
parsingOptions?: any,
traversalPlugin?: {
plugins: Array<{
onNode(file: FuseBoxFile, node: any, parent: any): void
onEnd(file: FuseBoxFile): void
}>
}
): void
}
export interface NativeModulePluginOptions {
[key: string]:
| {
additionalFiles: string[]
}
| true
}
export default function(options: NativeModulePluginOptions) {
return new NativeModulePlugin(options)
}
export class NativeModulePlugin {
public test: RegExp
public limit2Project = false
private modules: (keyof NativeModulePluginOptions)[]
constructor(public options: NativeModulePluginOptions) {
this.modules = Object.keys(options)
this.test = new RegExp(`node_modules\/(${Object.keys(options).join('|')}).*\.js|\.node$`)
}
init(context: any) {
context.allowExtension('.node')
}
transform(file: FuseBoxFile) {
file.loadContents()
if (file.absPath.endsWith('.node')) {
const contents = readFileSync(file.absPath)
const module = this.modules.find(x =>
Boolean(~file.absPath.indexOf(join('node_modules', x)))
)!
const bindingName = basename(file.absPath)
const settings = this.options[module]
const moduleDir = hashName(contents)
file.contents = `
var fs=require('fs');var path=require('path');var binding='${contents.toString(
'base64'
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
if (settings === true) {
file.contents += `
mkdirp('${moduleDir}');
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
process.dlopen(module, bindingPath)
`.trim()
return
}
let depth = 0
settings.additionalFiles.forEach(file => {
let ownDepth = 0
file.split('/').forEach(x => x === '..' && ownDepth++)
depth = ownDepth > depth ? ownDepth : depth
}) })
let segments = [moduleDir] )
while (depth--) {
segments.push(hashName(moduleDir + depth))
} }
segments.push(bindingName) const fuse = FuseBox.init({
cache: false,
file.contents += ` log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
var cwd = process.cwd() homeDir: options.cwd,
var bindingFileParts = ${JSON.stringify(segments)}; sourceMaps: false,
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts)); writeBundles: false,
mkdirp(path.dirname(bindingFile)); output: '$name.js',
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64')); target: 'server',
${settings.additionalFiles.reduce((code, filename, i) => { plugins
const contents = readFileSync(join(dirname(file.absPath), filename)) })
return (code += ` fuse.bundle(options.name).instructions(`> ${filename}`)
var file${i} = '${contents.toString('base64')}'; return fuse.run().then(x => {
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename let output = ''
.split('../') x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
.join('')}'); return output
mkdirp(path.dirname(filePath${i}));
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
`)
}, '')};
process.dlopen(module, bindingFile)
`
return
}
const bindingsRewrite = new BindingsRewrite()
file.makeAnalysis(null, {
plugins: [
{
onNode(file, node, parent) {
bindingsRewrite.onNode(file.absPath, node, parent)
},
onEnd(file) {
if (bindingsRewrite.rewrite) {
const index = file.analysis.dependencies.indexOf('bindings')
if (~index) {
file.analysis.dependencies.splice(index, 1)
}
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
file.analysis.requiresRegeneration = true
}
}
}
]
}) })
} }
export default async function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
compiler.input = await readFileAsync(compiler.options.input, 'utf-8')
return next()
}
if (compiler.options.empty || !compiler.options.input) {
compiler.input = ''
return next()
}
let producer = createBundle
if (typeof compiler.options.bundle === 'string') {
producer = require(compiler.options.bundle).createBundle
}
compiler.input = await producer(compiler.options.input, {
cwd: compiler.options.cwd,
name: compiler.options.name,
minify: compiler.options.compress
})
return next()
} }
+1
View File
@@ -0,0 +1 @@
export * from './fuse-native-module-plugin'
-31
View File
@@ -1,31 +0,0 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin } from 'fuse-box'
import NativeModulePlugin from './fuse'
function bundleProducer(filename: string) {
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
homeDir: './',
writeBundles: false,
target: 'server',
plugins: [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
})
fuse.bundle('nexe').instructions(`> ${filename}`)
return fuse.run().then(x => {
if (x.bundles.size > 1) {
console.log('THROW', x.bundles)
}
let output = ''
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
})
}
export default function bundle(compiler: NexeCompiler, next: any) {
if (!compiler.options.bundle) {
return next()
}
if (typeof compiler.options.bundle === 'string') {
}
}
+39 -23
View File
@@ -1,14 +1,14 @@
import { normalize, join } from 'path' import { dirname, normalize, join } from 'path'
import * as Bluebird from 'bluebird'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { createReadStream } from 'fs' import { createReadStream } from 'fs'
import { Readable } from 'stream' import { Readable } from 'stream'
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { Stream as Needle } from 'nigel'
import { Logger } from './logger' import { Logger } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util' import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
import { NexeOptions } from './options' import { NexeOptions, nexeVersion } from './options'
import { NexeTarget } from './target'
import { getLatestGitRelease, storeAsset } from './releases'
const isBsd = Boolean(~process.platform.indexOf('bsd')) const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make' const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
@@ -33,6 +33,7 @@ export class NexeCompiler {
public src = join(this.options.temp, this.options.version) public src = join(this.options.temp, this.options.version)
public files: NexeFile[] = [] public files: NexeFile[] = []
public input: string public input: string
public bundledInput?: string
public output: string | null public output: string | null
public resources: { bundle: string; index: { [key: string]: number[] } } = { public resources: { bundle: string; index: { [key: string]: number[] } } = {
@@ -50,7 +51,7 @@ export class NexeCompiler {
constructor(public options: NexeOptions) { constructor(public options: NexeOptions) {
const { python } = (this.options = options) const { python } = (this.options = options)
this.log.step('nexe ' + nexeVersion, 'info')
if (python) { if (python) {
if (isWindows) { if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
@@ -66,7 +67,10 @@ export class NexeCompiler {
cachedFile = { cachedFile = {
absPath, absPath,
filename: file, filename: file,
contents: await readFileAsync(absPath, 'utf-8').catch({ code: 'ENOENT' }, () => '') contents: await readFileAsync(absPath, 'utf-8').catch(x => {
if (x.code === 'ENOENT') return ''
throw x
})
} }
this.files.push(cachedFile) this.files.push(cachedFile)
} }
@@ -83,21 +87,21 @@ export class NexeCompiler {
} }
} }
quit(code = 0) { quit() {
const time = Date.now() - this.start const time = Date.now() - this.start
this.log.write(`Finsihed in ${time / 1000}s`) this.log.write(`Finsihed in ${time / 1000}s`)
return this.log.flush().then(x => process.exit(code)) return this.log.flush()
} }
private _getNodeExecutableLocation(target?: string | null) { private _getNodeExecutableLocation(target?: NexeTarget) {
if (target) { if (target) {
return join(this.options.temp, target) return join(this.options.temp, target.toString())
} }
return this.nodeSrcBinPath return this.nodeSrcBinPath
} }
private _runBuildCommandAsync(command: string, args: string[]) { private _runBuildCommandAsync(command: string, args: string[]) {
return new Bluebird((resolve, reject) => { return new Promise((resolve, reject) => {
spawn(command, args, { spawn(command, args, {
cwd: this.src, cwd: this.src,
env: this.env, env: this.env,
@@ -122,7 +126,7 @@ export class NexeCompiler {
: '...'}` : '...'}`
) )
await this._configureAsync() await this._configureAsync()
const buildOptions = isWindows ? this.options.vcBuild : this.options.make const buildOptions = this.options.make
this.compileStep.log( this.compileStep.log(
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}` `Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
) )
@@ -130,38 +134,50 @@ export class NexeCompiler {
return createReadStream(this._getNodeExecutableLocation()) return createReadStream(this._getNodeExecutableLocation())
} }
private _fetchPrebuiltBinaryAsync() { private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
return this._buildAsync() 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 one using --build`)
}
const filename = this._getNodeExecutableLocation(target)
await storeAsset(asset, dirname(filename))
return createReadStream(filename)
} }
private _generateHeader() { private _generateHeader() {
const version = const version =
['configure', 'vcBuild', 'make'].reduce((a, c) => { ['configure', 'vcBuild', 'make'].reduce((a, c) => {
return (a += (this.options as any)[c].slice().sort().join()) return (a += (this.options as any)[c]
.slice()
.sort()
.join())
}, '') + this.options.enableNodeCli }, '') + this.options.enableNodeCli
const header = { const header = {
resources: this.resources.index, resources: this.resources.index,
version: createHash('md5').update(version).digest('hex') version: createHash('md5')
.update(version)
.digest('hex')
} }
const serializedHeader = this._serializeHeader(header) const serializedHeader = this._serializeHeader(header)
return header return header
} }
private _serializeHeader(header: NexeHeader) { private _serializeHeader(header: NexeHeader) {
return `/**${JSON.stringify(header)}**/process.__nexe=${JSON.stringify(header)};` return `process.__nexe=${JSON.stringify(header)};`
} }
async compileAsync() { async compileAsync(target: NexeTarget) {
const step = (this.compileStep = this.log.step('Compiling result')) const step = (this.compileStep = this.log.step('Compiling result'))
let target = this.options.targets.slice().shift() const build = this.options.build
const location = this._getNodeExecutableLocation(target) const location = this._getNodeExecutableLocation(target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
const header = this._generateHeader() const header = this._generateHeader()
step.log(`Scanning existing binary...`)
if (target && !binary) { if (target && !build) {
//throw new Error('\nNot Implemented, use --build during beta\n') binary = await this._fetchPrebuiltBinaryAsync(target)
binary = await this._fetchPrebuiltBinaryAsync()
} }
if (!binary) { if (!binary) {
+5 -2
View File
@@ -46,14 +46,17 @@ export class Logger {
this.ora.color = color this.ora.color = color
} }
step(text: string): LogStep { step(text: string, method: string = 'succeed'): LogStep {
if (this.silent) { if (this.silent) {
return { modify() {}, log() {} } return { modify() {}, log() {} }
} }
if (!this.ora.id) { if (!this.ora.id) {
this.ora.start().text = text this.ora.start().text = text
if (method !== 'succeed') {
this.ora[method]()
}
} else { } else {
this.ora.succeed().text = text this.ora[method]().text = text
this.ora.start() this.ora.start()
} }
+30 -13
View File
@@ -1,17 +1,24 @@
import { compose, PromiseConfig, Middleware } from 'app-builder' import { compose, PromiseConfig, Middleware } from 'app-builder'
import resource from './resource' import resource from './steps/resource'
import { NexeCompiler } from './compiler' import { NexeCompiler } from './compiler'
import { argv, normalizeOptionsAsync, NexeOptions } from './options' import {
import cli from './cli' argv,
import download from './download' nexeVersion as version,
import artifacts from './artifacts' normalizeOptionsAsync,
NexeOptions,
NexePatch
} from './options'
import cli from './steps/cli'
import bundle from './bundling/fuse'
import download from './steps/download'
import artifacts from './steps/artifacts'
import patches from './patches' import patches from './patches'
import { rimrafAsync } from './util' import { rimrafAsync } from './util'
import * as Bluebird from 'bluebird'
PromiseConfig.constructor = Bluebird async function compile(
compilerOptions?: Partial<NexeOptions>,
async function compile(compilerOptions: NexeOptions, callback?: (err: Error | null) => void) { callback?: (err: Error | null) => void
) {
const options = await normalizeOptionsAsync(compilerOptions) const options = await normalizeOptionsAsync(compilerOptions)
const compiler = new NexeCompiler(options) const compiler = new NexeCompiler(options)
const build = compiler.options.build const build = compiler.options.build
@@ -24,9 +31,19 @@ async function compile(compilerOptions: NexeOptions, callback?: (err: Error | nu
return compiler.quit() return compiler.quit()
} }
const buildSteps = build ? [download, artifacts, ...patches, ...options.patches] : [] const buildSteps = build
const nexe = compose(resource, cli, buildSteps) ? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
return nexe(compiler).asCallback(callback) : []
const nexe = compose(resource, bundle, cli, buildSteps)
return callback
? void nexe(compiler).then(
() => callback && callback(null),
(e: Error) => {
if (callback) callback(e)
else throw e
}
)
: nexe(compiler)
} }
export { argv, compile } export { argv, compile, version }
+84 -66
View File
@@ -1,71 +1,50 @@
import * as parseArgv from 'minimist' import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler' import { NexeCompiler } from './compiler'
import { basename, extname, join } from 'path' import { isWindows } from './util'
import * as Bluebird from 'bluebird' import { basename, extname, join, isAbsolute, relative } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os' import { EOL } from 'os'
export const nexeVersion = '2.0.0-rc.1'
export interface NexePatch { export interface NexePatch {
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void> (compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
} }
export interface NexeOptions { export interface NexeOptions {
build: boolean build: boolean
/**
* Entrypoint filepath
*/
input: string input: string
output: string output: string
targets: string[] targets: (string | NexeTarget)[]
/**
* Build name, Used for executable, and stacktraces
*/
name: string name: string
/** cwd: string
* The node version to be built
*/
version: string version: string
python?: string
/**
* Node flags e.g. "--expose-gc" baked into the executable
*/
flags: string[] flags: string[]
/**
* Pass configuration options to node build configure script
*/
configure: string[] configure: string[]
/**
* Pass make options to node make script
*/
make: string[]
vcBuild: string[] vcBuild: string[]
make: string[]
snapshot?: string snapshot?: string
/**
* Array of glob strings describing resources to pull into the bundle
*/
resources: string[] resources: string[]
/**
* Temporary directory where nexe artifacts will be cached
* TODO dot folder in home dir
*/
temp: string temp: string
ico?: string
rc: { [key: string]: string } rc: { [key: string]: string }
/**
* Causes nexe to remove all temporary files for current configuration
*/
clean: boolean
enableNodeCli: boolean enableNodeCli: boolean
sourceUrl?: string
bundle: boolean | string bundle: boolean | string
patches: (string | NexePatch)[]
empty: boolean
sourceUrl?: string
python?: string
loglevel: 'info' | 'silent' | 'verbose' loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean silent?: boolean
verbose?: boolean verbose?: boolean
info?: boolean info?: boolean
patches: NexePatch[] ico?: string
empty: boolean
warmup?: string warmup?: string
downloadOptions?: any compress?: boolean
clean?: boolean
/**
* Api Only
*/
downloadOptions: any
} }
function padRight(str: string, l: number) { function padRight(str: string, l: number) {
@@ -73,16 +52,17 @@ function padRight(str: string, l: number) {
} }
const defaults = { const defaults = {
temp: process.env.NEXE_TEMP || join(process.cwd(), '.nexe'),
version: process.version.slice(1), version: process.version.slice(1),
flags: [], flags: [],
cwd: process.cwd(),
configure: [], configure: [],
make: [], make: [],
targets: [], targets: [],
vcBuild: ['nosign', 'release', process.arch], vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: false, enableNodeCli: false,
compress: false,
build: false,
bundle: true, bundle: true,
build: true,
patches: [] patches: []
} }
const alias = { const alias = {
@@ -97,9 +77,7 @@ const alias = {
f: 'flag', f: 'flag',
c: 'configure', c: 'configure',
m: 'make', m: 'make',
vc: 'vcBuild',
s: 'snapshot', s: 'snapshot',
cli: 'enableNodeCli',
h: 'help', h: 'help',
l: 'loglevel' l: 'loglevel'
} }
@@ -116,14 +94,14 @@ nexe --help CLI OPTIONS
-v --version =${padRight(process.version.slice(1), 23)}-- node version -v --version =${padRight(process.version.slice(1), 23)}-- node version
-p --python =/path/to/python2 -- python executable -p --python =/path/to/python2 -- python executable
-f --flag ="--expose-gc" -- *v8 flags to include during compilation -f --flag ="--expose-gc" -- *v8 flags to include during compilation
-c --configure ="--with-dtrace" -- *pass arguments to the configure command -c --configure ="--with-dtrace" -- *pass arguments to the configure step
-m --make ="--loglevel" -- *pass arguments to the make command -m --make ="--loglevel" -- *pass arguments to the make/build step
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
-s --snapshot =/path/to/snapshot -- build with warmup snapshot -s --snapshot =/path/to/snapshot -- build with warmup snapshot
-r --resource =./paths/**/* -- *embed file bytes within the binary -r --resource =./paths/**/* -- *embed file bytes within the binary
--bundle =./path/to/config -- pass a module path that exports nexeBundle --bundle =./path/to/config -- pass a module path that exports nexeBundle
--temp =./path/to/temp -- nexe temp files (for downloads and source builds) --temp =./path/to/temp -- nexe temp files (for downloads and source builds)
--no-bundle -- set when input is already bundled --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) --ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows) --rc-* -- populate rc file options (windows)
--clean -- force download of sources --clean -- force download of sources
@@ -155,10 +133,10 @@ function extractCliMap(match: RegExp, options: any) {
}, {}) }, {})
} }
function tryResolveMainFileName() { function tryResolveMainFileName(cwd: string) {
let filename let filename
try { try {
const file = require.resolve(process.cwd()) const file = require.resolve(cwd)
filename = basename(file).replace(extname(file), '') filename = basename(file).replace(extname(file), '')
} catch (_) {} } catch (_) {}
@@ -174,40 +152,80 @@ function extractLogLevel(options: NexeOptions) {
function extractName(options: NexeOptions) { function extractName(options: NexeOptions) {
let name = options.name let name = options.name
if (typeof options.input === 'string' && !name) { if (!name && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '') name = basename(options.input).replace(extname(options.input), '')
} }
name = name || tryResolveMainFileName() name = name || tryResolveMainFileName(options.cwd)
return name.replace(/\.exe$/, '') return name.replace(/\.exe$/, '')
} }
function normalizeOptionsAsync(input: Partial<NexeOptions>) { function isEntryFile(filename: string) {
if (argv.help || argv._.some((x: string) => x === 'version')) { return filename && !isAbsolute(filename) && filename !== 'node' && /\.(tsx?|jsx?)$/.test(filename)
process.stderr.write(argv.help ? help : '2.0.0-beta.7' + EOL, () => process.exit(0))
} }
function findInput(input: string, cwd: string) {
const maybeInput = argv._.slice().pop() || ''
if (input) {
return input
}
if (isEntryFile(maybeInput)) {
return maybeInput
}
try {
const main = require.resolve(cwd)
return './' + relative(cwd, main)
} catch (e) {
void e
}
return ''
}
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions | never> {
if (argv.help || argv._.some((x: string) => x === 'version') || argv.version === true) {
return new Promise(() => {
process.stderr.write(argv.help ? help : nexeVersion + EOL, () => process.exit(0))
})
}
const options = Object.assign({}, defaults, input) as NexeOptions const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any const opts = options as any
delete opts._
options.loglevel = extractLogLevel(options) options.temp = process.env.NEXE_TEMP || join(options.cwd, '.nexe')
options.name = extractName(options) options.name = extractName(options)
options.input = findInput(options.input, options.cwd)
options.loglevel = extractLogLevel(options)
options.flags = flattenFilter(opts.flag, options.flags) options.flags = flattenFilter(opts.flag, options.flags)
options.targets = flattenFilter(opts.target, options.targets) options.targets = flattenFilter(opts.target, options.targets)
options.make = flattenFilter(options.make) options.make = flattenFilter(options.vcBuild, options.make)
options.vcBuild = flattenFilter(options.vcBuild)
options.configure = flattenFilter(options.configure) options.configure = flattenFilter(options.configure)
options.resources = flattenFilter(opts.resource, options.resources) options.resources = flattenFilter(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options) options.rc = options.rc || extractCliMap(/^rc-.*/, options)
if (options.build) { options.targets = options.targets.map(getTarget)
options.targets = []
options.build = true if (options.build && options.targets.length) {
} else if (!options.targets.length) { const { arch } = options.targets[0] as NexeTarget
const defaultTarget = [process.platform, process.arch, options.version].join('-') if (isWindows) {
options.targets = [defaultTarget] options.make = Array.from(new Set(options.make.concat([arch])))
} else {
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
}
} }
Object.keys(alias).filter(k => k !== 'rc').forEach(x => delete opts[x]) if (!options.targets.length) {
options.targets = [getTarget(options.version)]
}
options.patches = options.patches.map(x => {
if (typeof x === 'string') {
return require(x).default
}
return x
})
Object.keys(alias)
.filter(k => k !== 'rc')
.forEach(x => delete opts[x])
return Promise.resolve(options) return Promise.resolve(options)
} }
+4 -1
View File
@@ -12,7 +12,10 @@ export default async function nodeGyp(
nodeGypMarker, nodeGypMarker,
` `
${nodeGypMarker} ${nodeGypMarker}
${files.filter(x => x.filename.startsWith('lib')).map(x => `'${x.filename}'`).toString()}, ${files
.filter(x => x.filename.startsWith('lib'))
.map(x => `'${x.filename}'`)
.toString()},
`.trim() `.trim()
) )
} }
+31 -10
View File
@@ -1,20 +1,23 @@
import { NexeCompiler } from '../compiler' import { NexeCompiler } from '../compiler'
import { readFileSync } from 'fs'
import { join } from 'path'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) { export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
await compiler.setFileContentsAsync( await compiler.setFileContentsAsync(
'lib/_third_party_main.js', 'lib/_third_party_main.js',
` `
const fs = require('fs') const fs = require('fs')
const path = require('path')
const Buffer = require('buffer').Buffer const Buffer = require('buffer').Buffer
const isString = x => typeof x === 'string' || x instanceof String const isString = x => typeof x === 'string' || x instanceof String
const fd = fs.openSync(process.execPath, 'r') const fd = fs.openSync(process.execPath, 'r')
const size = fs.statSync(process.execPath).size const size = fs.statSync(process.execPath).size
const footer = Buffer.from(Array(46)) const footer = Buffer.from(Array(32))
fs.readSync(fd, footer, 0, 32, size - 32) fs.readSync(fd, footer, 0, 32, size - 32)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) { if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
throw new Error('Invalid Nexe binary') throw 'Invalid Nexe binary'
} }
const contentSize = footer.readDoubleLE(16) const contentSize = footer.readDoubleLE(16)
@@ -43,8 +46,25 @@ Object.defineProperty(process, '__nexe', (function () {
if (resourceSize) { if (resourceSize) {
const originalReadFile = fs.readFile const originalReadFile = fs.readFile
const originalReadFileSync = fs.readFileSync const originalReadFileSync = fs.readFileSync
let setupManifest = () => {
const manifest = process.__nexe && process.__nexe.resources
if (!manifest) {
return
}
Object.keys(manifest).forEach((key) => {
const absolutePath = path.resolve(key)
if (!manifest[absolutePath]) {
manifest[absolutePath] = manifest[key]
}
const normalizedPath = path.normalize(key)
if (!manifest[normalizedPath]) {
manifest[normalizedPath] = manifest[key]
}
})
normalizeManifest = () => {}
};
fs.readFile = function readFile (file, options, callback) { fs.readFile = function readFile (file, options, callback) {
setupManifest()
const manifest = process.__nexe const manifest = process.__nexe
const entry = manifest && manifest.resources[file] const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) { if (!manifest || !entry || !isString(file)) {
@@ -75,6 +95,7 @@ if (resourceSize) {
} }
fs.readFileSync = function readFileSync (file, options) { fs.readFileSync = function readFileSync (file, options) {
setupManifest()
const manifest = process.__nexe const manifest = process.__nexe
const entry = manifest && manifest.resources[file] const entry = manifest && manifest.resources[file]
if (!manifest || !entry || !isString(file)) { if (!manifest || !entry || !isString(file)) {
@@ -92,13 +113,13 @@ if (resourceSize) {
} }
} }
const contentBuffer = Buffer.from(Array(contentSize)) const contentBuffer = Buffer.from(Array(contentSize));
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart) fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
fs.closeSync(fd) fs.closeSync(fd);
const Module = require('module') const Module = require('module');
process.mainModule = new Module(process.execPath, null) process.mainModule = new Module(process.execPath, null);
process.mainModule.loaded = true process.mainModule.loaded = true;
process.mainModule._compile(contentBuffer.toString(), process.execPath) process.mainModule._compile(contentBuffer.toString(), process.execPath);
`.trim() `.trim()
) )
return next() return next()
+73
View File
@@ -0,0 +1,73 @@
import got = require('got')
import download = require('download')
import {
NodePlatform,
NodeArch,
platforms,
architectures,
NexeTarget,
getTarget,
targetsEqual
} from './target'
export { NexeTarget }
export interface GitAsset {
name: string
url: string
browser_download_url: string
}
export interface GitRelease {
tag_name: string
assets_url: string
upload_url: string
assets: GitAsset[]
}
interface NodeRelease {
version: string
}
async function getJson<T>(url: string, options?: any) {
return JSON.parse((await got(url, options)).body) as T
}
//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'
}
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'
)
const existingVersions = (await getLatestGitRelease(options)).assets.map(x => getTarget(x.name))
const versionMap: { [key: string]: true } = {}
return nodeReleases
.reduce((versions: NexeTarget[], { version }) => {
version = version.replace('v', '').trim()
if (!isBuildableVersion(version) || versionMap[version]) {
return versions
}
versionMap[version] = true
platforms.forEach(platform => {
architectures.forEach(arch => {
if (arch === 'x86' && platform === 'mac') return
versions.push(getTarget({ platform, arch, version }))
})
})
return versions
}, [])
.filter(x => !existingVersions.some(t => targetsEqual(t, x)))
}
+70
View File
@@ -0,0 +1,70 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from '../compiler'
import pify = require('pify')
const mkdirpAsync = pify(mkdirp)
const unlinkAsync = pify(unlink)
const readdirAsync = pify(readdir)
function readDirAsync(dir: string): Promise<string[]> {
return readdirAsync(dir).then(paths => {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : path as any))
})
).then(result => {
return [].concat(...(result as any))
})
})
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await Promise.all(
tmpFiles.map(async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
)
await next()
await Promise.all(tmpFiles.map(x => unlinkAsync(x)))
return Promise.all(
compiler.files.map(async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
)
}
+17 -23
View File
@@ -1,12 +1,12 @@
import { normalize } from 'path' import { normalize } from 'path'
import * as Bluebird from 'bluebird'
import { Readable } from 'stream' import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs' import { createWriteStream, chmodSync } from 'fs'
import { readFileAsync, dequote, isWindows } from './util' import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from './compiler' import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> { function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
return new Bluebird(resolve => { return new Promise(resolve => {
let input = '' let input = ''
stream.setEncoding('utf-8') stream.setEncoding('utf-8')
stream.on('data', (x: string) => { stream.on('data', (x: string) => {
@@ -18,8 +18,8 @@ function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
} }
/** /**
* The "cli" step detects whether the process is in a tty. If it is then the input is read into memory. * The "cli" step detects the appropriate input. If no input options are passed,
* Otherwise, it is buffered from stdin. If no input options are passed in the tty, the package.json#main file is used. * the package.json#main file is used.
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout. * After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
* *
* Configuration: * Configuration:
@@ -32,30 +32,24 @@ function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
* @param {*} next * @param {*} next
*/ */
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) { export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { input, output } = compiler.options const { output } = compiler.options
const { log } = compiler const { log, input: bundledInput } = compiler
if (!input && !process.stdin.isTTY) {
if (!process.stdin.isTTY) {
log.step('Using stdin as input') log.step('Using stdin as input')
compiler.input = await readStreamAsync(process.stdin) compiler.input = await readStreamAsync(process.stdin)
} else if (input) {
log.step(`Using input file as the main module: ${input}`)
compiler.input = await readFileAsync(normalize(input), 'utf-8')
} else if (!compiler.options.empty) {
const bundle = require.resolve(process.cwd())
log.step("Using the cwd's main file as the main module")
compiler.input = await readFileAsync(bundle, 'utf-8')
} else {
log.step('Using empty input as the main module')
compiler.input = ''
} }
compiler.input = compiler.input.trim()
await next() await next()
compiler.output = output || `${compiler.options.name}${isWindows ? '.exe' : ''}` compiler.output = isWindows
const deliverable = await compiler.compileAsync() ? `${(output || compiler.options.name).replace(/\.exe$/, '')}.exe`
: `${output || compiler.options.name}`
return new Bluebird((resolve, reject) => { const target = compiler.options.targets.shift() as NexeTarget
const deliverable = await compiler.compileAsync(target)
return new Promise((resolve, reject) => {
const step = log.step('Writing result to file') const step = log.step('Writing result to file')
deliverable deliverable
.pipe(createWriteStream(normalize(compiler.output!))) .pipe(createWriteStream(normalize(compiler.output!)))
+12 -8
View File
@@ -1,12 +1,12 @@
import download = require('download') import download = require('download')
import { isDirectoryAsync } from './util' import { pathExistsAsync } from '../util'
import { LogStep } from './logger' import { LogStep } from '../logger'
import { IncomingMessage } from 'http' import { IncomingMessage } from 'http'
import { NexeCompiler } from './compiler' import { NexeCompiler } from '../compiler'
function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options = {}) { function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`) const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
return download(url, cwd, Object.assign(options, { extract: true, strip: 1 })) return download(url, dest, Object.assign(options, { extract: true, strip: 1 }))
.on('response', (res: IncomingMessage) => { .on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']! const total = +res.headers['content-length']!
let current = 0 let current = 0
@@ -18,7 +18,7 @@ function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options =
} }
}) })
}) })
.then(() => step.log(`Node source extracted to: ${cwd}`)) .then(() => step.log(`Node source extracted to: ${dest}`))
} }
/** /**
@@ -31,10 +31,14 @@ export default async function downloadNode(compiler: NexeCompiler, next: () => P
const { version, sourceUrl, downloadOptions } = compiler.options const { version, sourceUrl, downloadOptions } = compiler.options
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz` const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
const step = log.step(`Downloading Node.js source from: ${url}`) const step = log.step(`Downloading Node.js source from: ${url}`)
if (await isDirectoryAsync(src)) { if (await pathExistsAsync(src)) {
step.log('Source already downloaded') step.log('Source already downloaded')
return next() return next()
} }
return fetchNodeSourceAsync(src, url, step, downloadOptions).then(next) if (compiler.options.build) {
await fetchNodeSourceAsync(src, url, step, downloadOptions)
}
return next()
} }
+2 -3
View File
@@ -1,8 +1,7 @@
import { each } from 'bluebird' import { readFileAsync, isDirectoryAsync, each } from '../util'
import { readFileAsync, isDirectoryAsync } from './util'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import * as globs from 'globby' import * as globs from 'globby'
import { NexeCompiler } from './compiler' import { NexeCompiler } from '../compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) { export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
const resources = compiler.resources const resources = compiler.resources
+89
View File
@@ -0,0 +1,89 @@
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
export type NodeArch = 'x86' | 'x64'
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
architectures: NodeArch[] = ['x86', 'x64']
export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform
arch: NodeArch
}
//TODO bsd
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
linux: 'linux',
static: 'alpine',
alpine: 'alpine'
}
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
}
function isVersion(x: string) {
if (!x) {
return false
}
return /\d/.test(x.replace(/v|\./g, ''))
}
function isPlatform(x: string): x is NodePlatform {
return x in prettyPlatform
}
function isArch(x: string): x is NodeArch {
return x in prettyArch
}
class Target implements NexeTarget {
constructor(public arch: NodeArch, public platform: NodePlatform, public version: string) {}
toJSON() {
return this.toString()
}
toString() {
return `${this.platform}-${this.arch}-${this.version}`
}
}
export function targetsEqual(a: NexeTarget, b: NexeTarget) {
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
}
export function getTarget(target: string | NexeTarget = ''): NexeTarget {
let arch = process.arch as NodeArch,
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
if (typeof target !== 'string') {
target = `${target.platform}-${target.arch}-${target.version}`
}
target
.toLowerCase()
.split('-')
.forEach(x => {
if (isVersion(x)) {
version = x
}
if (isPlatform(x)) {
platform = prettyPlatform[x]
}
if (isArch(x)) {
arch = prettyArch[x]
}
})
return new Target(arch, platform, version)
}
+6 -18
View File
@@ -1,23 +1,11 @@
declare module 'nigel' { declare module 'got' {
import { Writable } from 'stream' interface GotFn {
interface Needle { (url: string, options?: any): Promise<{ body: string }>
value: Buffer stream(url: string, optoins?: any): any
lastPos: number
last: number
length: number
badCharShift: Buffer
} }
function got(url: string, options?: any): Promise<{ body: string }>
export function compile(needle: Buffer): Needle export = got as GotFn
export function horspool(haystack: Buffer, needle: Needle, start: number): number
export function all(haystack: Buffer, needle: Needle, pos: number): number[]
export class Stream extends Writable {
constructor(needle: Buffer)
needle(needle: Buffer): void
flush(): void
} }
}
declare module 'download' { declare module 'download' {
import { Duplex } from 'stream' import { Duplex } from 'stream'
interface DownloadOptions { interface DownloadOptions {
+34 -13
View File
@@ -1,9 +1,26 @@
import { readFile, writeFile, stat } from 'fs' import { readFile, writeFile, stat } from 'fs'
import * as Bluebird from 'bluebird' import { execFile } from 'child_process'
import pify = require('pify')
import rimraf = require('rimraf') import rimraf = require('rimraf')
const { promisify } = Bluebird const rimrafAsync = pify(rimraf)
const rimrafAsync = (promisify(rimraf) as any) as (path: string) => Bluebird<void>
export async function each<T>(
list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any>
) {
const l = await list
for (let i = 0; i < l.length; i++) {
await action(l[i], i, l)
}
}
function falseOnEnoent(e: any) {
if (e.code === 'ENOENT') {
return false
}
throw e
}
function dequote(input: string) { function dequote(input: string) {
input = input.trim() input = input.trim()
@@ -16,30 +33,34 @@ function dequote(input: string) {
} }
export interface ReadFileAsync { export interface ReadFileAsync {
(path: string): Bluebird<Buffer> (path: string): Promise<Buffer>
(path: string, encoding: string): Bluebird<string> (path: string, encoding: string): Promise<string>
} }
const readFileAsync = (promisify(readFile) as any) as ReadFileAsync const readFileAsync = pify(readFile)
const writeFileAsync = (promisify(writeFile) as any) as ( const writeFileAsync = pify(writeFile)
path: string, const statAsync = pify(stat)
contents: string | Buffer const execFileAsync = pify(execFile)
) => Promise<void>
const statAsync = promisify(stat)
const isWindows = process.platform === 'win32' const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) { function pathExistsAsync(path: string) {
return statAsync(path).then(x => true).catch({ code: 'ENOENT' }, () => false) return statAsync(path)
.then(x => true)
.catch(falseOnEnoent)
} }
function isDirectoryAsync(path: string) { function isDirectoryAsync(path: string) {
return statAsync(path).then(x => x.isDirectory()).catch({ code: 'ENOENT' }, () => false) return statAsync(path)
.then(x => x.isDirectory())
.catch(falseOnEnoent)
} }
export { export {
dequote, dequote,
isWindows, isWindows,
rimrafAsync, rimrafAsync,
statAsync,
execFileAsync,
readFileAsync, readFileAsync,
pathExistsAsync, pathExistsAsync,
isDirectoryAsync, isDirectoryAsync,
+101
View File
@@ -0,0 +1,101 @@
import * as nexe from '../src/nexe'
import {
getUnBuiltReleases,
getLatestGitRelease
} from '../src/releases'
import * as ci from './ci'
import { runAlpineBuild } from './docker'
import { getTarget } from '../src/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.CIRCLECI),
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER)
|| Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
|| Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = { 'Authorization': 'token ' + env.GITHUB_TOKEN }
if (require.main === module) {
if (!isPullRequest) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
}
async function build () {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
return
}
const windowsBuild = releases.find(x => x.platform === 'windows')
const macBuild = releases.find(x => x.platform === 'mac')
const linuxOrAlpine = releases.find(x => x.platform === 'linux' || x.platform === 'alpine')
if (linuxOrAlpine) {
await ci.triggerDockerBuild(linuxOrAlpine, branchName)
}
if (macBuild) {
await ci.triggerMacBuild(macBuild, branchName)
}
if (windowsBuild) {
await ci.triggerWindowsBuild(windowsBuild)
}
}
if (env.NEXE_VERSION) {
const
target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
build: true,
target, //FIXME
output
}
const stop = keepalive()
if (target.platform === 'alpine') {
await runAlpineBuild(target, nexe.version.split('.')[0])
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream'
}
})
console.log(target + ' uploaded.')
}
}
}
function keepalive () {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary (file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
+48
View File
@@ -0,0 +1,48 @@
import { NexeTarget } from '../src/releases'
import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerDockerBuild(release: NexeTarget, branch: string) {
assert.ok(env.TRAVIS_TOKEN)
const travis = `https://api.travis-ci.org/repo/nexe%2Fnexe/requests`
return got(travis, {
json: true,
body: {
request: {
branch: branch,
config: {
merge_mode: 'deep_merge',
env: {
NEXE_VERSION: release
}
}
}
},
headers: {
'Travis-API-Version': '3',
'Authorization': `token ${env.TRAVIS_TOKEN}`
}
})
}
export function triggerMacBuild (release: NexeTarget, branch: string) {
assert.ok(env.CIRCLE_TOKEN)
const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/${branch}?circle-token=${env.CIRCLE_TOKEN}`
return got(circle, {
json: true,
body: {
build_parameters: {
NEXE_VERSION: release.toString()
}
}
})
}
export function triggerWindowsBuild (release: NexeTarget) {
const hasVersion = 'NEXE_VERSION' in env
env.NEXE_VERSION = hasVersion
? env.NEXE_VERSION!.trim()
: release.toString()
return Promise.resolve()
}
+32
View File
@@ -0,0 +1,32 @@
import { NexeTarget } from '../src/target'
import { writeFileAsync } from '../src/util'
import exec = require('execa')
function alpine (target: NexeTarget, nexeVersion: string) {
const version = target.version
return `
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
WORKDIR /
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$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
make install && \
paxctl -cm /usr/bin/node
RUN mkdir nexe_temp && mv node-v${version} nexe_temp/${version}
RUN npm i nexe@beta -g
ENV NEXE_TEMP=/nexe_temp
RUN nexe --empty -c="--fully-static" -o /nexe-out`.trim()
}
export async function runAlpineBuild (target: NexeTarget, nexeVersion: string) {
await writeFileAsync('Dockerfile', alpine(target, nexeVersion))
const option = { stido: 'inherit' } as any
await exec('docker', ['build', '-t', 'nexe-alpine', '.'], option)
await exec('docker', ['run', '--name', 'nexe', 'nexe-alpine'], option)
await exec('docker', ['cp', 'nexe:/nexe-out', 'out'], option)
await exec('docker', ['rm', 'nexe'], option)
}
-1
View File
@@ -1 +0,0 @@
true
-11
View File
@@ -1,11 +0,0 @@
/**
* Embedded Files Test
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
const nexeres = require('nexeres');
process.stdout.write(nexeres.get('hw.txt').toString('ascii'));
-28
View File
@@ -1,28 +0,0 @@
{
"name": "embededfiles-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node index.js"
},
"dependencies": {
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"resourceFiles": [
"hw.txt"
],
"browserify": {
"excludes": [],
"requires": []
},
"temp": "../src",
"debug": false,
"runtime": {
"nodeConfigureOpts": ["--fully-static"],
"framework": "node",
"version": "5.7.0"
}
}
}
-61
View File
@@ -1,61 +0,0 @@
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var jade = require('jade');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
-93
View File
@@ -1,93 +0,0 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('express-test:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
process.stdout.write('true');
process.exit(0);
-28
View File
@@ -1,28 +0,0 @@
{
"name": "express-test",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
},
"nexe": {
"input": "bin/www",
"output": "test.nex",
"temp": "../src",
"debug": false,
"runtime": {
"framework": "node",
"version": "5.10.0",
"ignoreFlags": true
}
}
}
@@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
-9
View File
@@ -1,9 +0,0 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
-9
View File
@@ -1,9 +0,0 @@
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
-6
View File
@@ -1,6 +0,0 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}
-5
View File
@@ -1,5 +0,0 @@
extends layout
block content
h1= title
p Welcome to #{title}
-7
View File
@@ -1,7 +0,0 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
-13
View File
@@ -1,13 +0,0 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// "use strict"; w/o --use_strict
let isStrict = (function() { return !this; })();
console.log(isStrict);
-15
View File
@@ -1,15 +0,0 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": false,
"js-flags": "--use_strict",
"node-args": ""
}
}
}
-23
View File
@@ -1,23 +0,0 @@
"use strict";
let gulp = require('gulp');
let nexe = require('nexe');
gulp.task( "compile", ( callback ) => {
let options = {
input: "index.js",
output: "test.nex",
python: "python",
nodeTempDir: "./tmp",
nodeVersion: "latest",
flags: false,
framework: "nodejs"
};
nexe.compile( options, ( err ) => {
console.log( err );
callback( err );
} );
} );
gulp.task('default', ['compile']);
-2
View File
@@ -1,2 +0,0 @@
// it probably executed.
console.log(true);
-26
View File
@@ -1,26 +0,0 @@
{
"name": "gulp-test-170",
"version": "0.0.1",
"description": "gulpfile.js issue #170",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"gulp": "^3.9.0",
"nexe": "^1.0.5"
},
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
}
}
-19
View File
@@ -1,19 +0,0 @@
/**
* Test to verify if we support strict mode (flags)
*
* @author Jared Allard <jaredallard@outlook.com>
* @version 0.0.1
* @license MIT
**/
// test.nex --help
let status = false;
// console.log(process.argv);
if(process.argv[2]) {
status = true;
}
console.log(status);
-15
View File
@@ -1,15 +0,0 @@
{
"name": "flags-test",
"nexe": {
"input": "./index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"framework": "node",
"version": "5.7.0",
"ignoreFlags": true,
"node-args": ""
}
},
"dependencies": {}
}
-3
View File
@@ -1,3 +0,0 @@
require('js-yaml');
console.log(true);
-23
View File
@@ -1,23 +0,0 @@
{
"name": "js-yaml-148",
"version": "1.0.0",
"description": "#148 js-yaml fails to work",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jared Allard <jaredallard@outlook.com>",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.5.2"
},
"nexe": {
"input": "index.js",
"output": "test.nex",
"temp": "../src",
"runtime": {
"version": "5.7.0",
"framework": "node"
}
}
}
-1
View File
@@ -1 +0,0 @@
--timeout 100000000
-249
View File
@@ -1,249 +0,0 @@
/**
* Test runner for nexe
*
* @author Jared Allard <jaredallard@outlook.com>
* @license MIT
**/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const spawn = require('child_process').spawn;
// our vars
const NEXE_VERSION = require('../package.json').version;
const testBase = __dirname;
const LOG_FILE = path.join(testBase, 'test.log');
if(fs.existsSync(LOG_FILE)) {
fs.unlinkSync(LOG_FILE);
}
let logfile = fs.createWriteStream(LOG_FILE, {
autoClose: false
});
const compileTest = function(test, cb) {
let testDir = path.join(testBase, test);
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
let npminst = spawn('npm', ['install'], {
cwd: testDir
});
npminst.on('error', function() {
throw new Error('Failed to execute NPM');
return cb(false);
});
npminst.on('close', function(code) {
if(code !== 0) {
throw new Error('NPM exited with non-zero status');
return cb(false);
}
let testinst = spawn('../../bin/nexe', [], {
cwd: testDir
});
testinst.on('error', function() {
throw new Error('Test failed to compile');
return cb(false);
});
testinst.on('close', function(code) {
let err = null;
if(code !== 0) {
throw new Error('Test gave non-zero code');
err = false;
}
logfile = fs.createWriteStream(LOG_FILE);
return cb(err);
});
testinst.stdout.on('data', function(d) {
process.stdout.write(d.toString('ascii'));
});
});
};
/**
* Run a test-generated binary
*
* @param {string} test - test name (dir)
* @param {array} optional args - array of args to pass to the binary
* @param {function} cb - callback
**/
const runTest = function(test, args, cb) {
let testDir = path.join(testBase, test);
let testBin = path.join(testDir, 'test.nex')
// check params
if(!Array.isArray(args)) {
cb = args;
args = [];
}
if(!fs.existsSync(testDir)) {
throw new Error('Test not found');
return cb(false);
}
if(!fs.existsSync(testBin)) {
throw new Error('Test binary not found');
return cb(false);
}
let returned = false;
let testinst = spawn(testBin, args, {
cwd: testDir
})
testinst.stdout.on('data', function(d) {
let status = d.toString('ascii');
if(status === true || status === 'true' || status === 'true\n') {
testinst.stdin.pause();
testinst.kill();
// make it know we returned.
returned = true;
return cb();
}
})
testinst.on('close', function(c) {
if(returned !== true) {
throw new Error('Test failed (stdout never contained true)');
return cb(false);
}
});
}
console.log('NOTICE: The first test may take awhile as it may compile Node.js');
console.log(' Monitor the log file (test.log) for progress.');
after(function() {
console.log('notice: closing test.log file descriptor.')
logfile.end();
});
/**
* Tests express compatability.
**/
describe('nexe can bundle express', function() {
let testname = 'express-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe and embedding files', function() {
let testname = 'embeded-files';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('v8 flags test (strict mode)', function() {
let testname = 'flags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can be utilized in gulp test', function() {
let testname = 'gulp-test-170';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});
describe('nexe can ignore node.js flags test', function() {
let testname = 'ignoreFlags-test';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, ['--help'], function(err) {
return next(err);
});
});
});
});
describe('nexe supports js-yaml test', function() {
let testname = 'js-yaml-148';
describe('build', function () {
it('compiles without errors', function (next) {
compileTest(testname, function(err) {
return next(err);
});
});
it('runs successfully', function(next) {
runTest(testname, function(err) {
return next(err);
});
});
});
});