chore: typescript and native module bundles
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
temp/
|
||||
coverage
|
||||
node_modules
|
||||
examples
|
||||
*.log
|
||||
*.exe
|
||||
.idea
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"chenxsan.vscode-standardjs"
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"standard.enable": true
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ nexe.compile({
|
||||
|
||||
### `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)
|
||||
@@ -124,12 +126,6 @@ nexe.compile({
|
||||
- Array of globs with files to include in the build
|
||||
- Example: `['./public/**/*']`
|
||||
- default: `[]`
|
||||
- `bundle: boolean | string | object`
|
||||
- `boolean`: indicating whether integrated bundling should be tried
|
||||
- `string`: filepath to user-written webpack configuration
|
||||
- `object`: user provided webpack configuration
|
||||
- Example: `'./webpack.config.js'`
|
||||
- default: `false`
|
||||
- `temp: string`
|
||||
- Path to use for storing nexe's build files
|
||||
- Override in the env with `NEXE_TEMP`
|
||||
@@ -162,10 +158,10 @@ nexe.compile({
|
||||
|
||||
A patch is just a middleware function that takes two arguments, the `compiler`, and `next`. The compiler is described below, and `next` ensures that the pipeline continues. Its invocation should always be awaited or returned to ensure correct behavior.
|
||||
|
||||
### `NexeCompiler`
|
||||
|
||||
For examples, see the built in patches: [src/patches](src/patches)
|
||||
|
||||
### `NexeCompiler`
|
||||
|
||||
- `setFileContentsAsync(filename: string, contents: string): Promise<void>`
|
||||
- Quickly set a file's contents within the downloaded Node.js source.
|
||||
- `replaceInFileAsync(filename: string, ...replaceArgs): Promise<void>`
|
||||
@@ -181,6 +177,15 @@ For examples, see the built in patches: [src/patches](src/patches)
|
||||
|
||||
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`.
|
||||
|
||||
## Bundling
|
||||
|
||||
Bundling in nexe has been decoupled from the compiler pipeline. While in beta it is completely seperate. Any bundler will always work with nexe as long a node-compatible bundle is created.
|
||||
|
||||
### Native Modules
|
||||
|
||||
Nexe has a plugin built for use with [fuse-box](http://fuse-box.org). This plugin currently supports modules that require `.node` files and those that use the `bindings` module.
|
||||
Future plans are in place to support `node-pre-gyp#find`. Take a look at the [example](examples/native-build/build.js)
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const { FuseBox } = require('fuse-box')
|
||||
const nexe = '../..'
|
||||
const { NativeModulePlugin } = require(nexe + '/lib/bundling/fuse')
|
||||
const fuse = FuseBox.init({
|
||||
homeDir: './',
|
||||
cache: false,
|
||||
log: true,
|
||||
debug: true,
|
||||
output: '$name.js',
|
||||
plugins: [
|
||||
new NativeModulePlugin({
|
||||
zmq: {
|
||||
additionalFiles: [
|
||||
'../../windows/lib/x64/libzmq-v100-mt-4_0_4.dll'
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
fuse.bundle('app')
|
||||
.target('electron')
|
||||
.instructions(`> index.js`)
|
||||
fuse.run()
|
||||
@@ -0,0 +1,19 @@
|
||||
var zmq = require('zmq')
|
||||
var pub = zmq.socket('pub')
|
||||
|
||||
pub.bindSync('tcp://127.0.0.1:3000')
|
||||
console.log('Publisher bound to port 3000')
|
||||
|
||||
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')
|
||||
console.log('Subscriber connected to port 3000')
|
||||
|
||||
sub.on('message', function(topic, message) {
|
||||
console.log('received a message related to:', topic.toString(), 'containing message:', message.toString())
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "native-build",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"bundle": "node build",
|
||||
"build": "nexe -i app.js -o app.exe"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fuse-box": "^2.2.0",
|
||||
"zmq": "^2.15.3"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@ module.exports = nexe
|
||||
|
||||
if (require.main === module) {
|
||||
nexe.compile(nexe.argv).catch((e) => {
|
||||
process.stderr.write(e.stack, () => process.exit(1))
|
||||
process.stderr.write(e.message, () => process.exit(1))
|
||||
})
|
||||
}
|
||||
|
||||
+9
-7
@@ -11,9 +11,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"clean": "rimraf lib",
|
||||
"build": "babel src -d lib --copy-files",
|
||||
"lint": "standard index.js ./src/**/* --fix",
|
||||
"test": "npm run build && mocha"
|
||||
"build": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\" && tsc --declaration"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -44,10 +42,14 @@
|
||||
"tar": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-preset-env": "^1.3.3",
|
||||
"@types/bluebird": "^3.5.8",
|
||||
"@types/chalk": "^0.4.31",
|
||||
"@types/mkdirp": "^0.3.29",
|
||||
"@types/ora": "^0.3.31",
|
||||
"@types/rimraf": "0.0.28",
|
||||
"mocha": "^3.2.0",
|
||||
"standard": "^10.0.1"
|
||||
"prettier": "^1.4.4",
|
||||
"standard": "^10.0.1",
|
||||
"typescript": "^2.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
import { join, dirname } from 'path'
|
||||
import { readdir, unlink } from 'fs'
|
||||
import { readFileAsync, writeFileAsync, isDirectoryAsync } from './util'
|
||||
import Bluebird from 'bluebird'
|
||||
import mkdirp from 'mkdirp'
|
||||
import { promisify, map } from 'bluebird'
|
||||
import * as mkdirp from 'mkdirp'
|
||||
import { NexeCompiler } from './compiler'
|
||||
|
||||
const { promisify, map } = Bluebird
|
||||
const mkdirpAsync = promisify(mkdirp)
|
||||
const unlinkAsync = promisify(unlink)
|
||||
const unlinkAsync = (promisify(unlink) as any) as (path: string) => PromiseLike<void>
|
||||
const readdirAsync = promisify(readdir)
|
||||
|
||||
function readDirAsync (dir) {
|
||||
return readdirAsync(dir).map((file) => {
|
||||
const path = join(dir, file)
|
||||
return isDirectoryAsync(path).then(x => x ? readDirAsync(path) : path)
|
||||
}).reduce((a, b) => a.concat(b), [])
|
||||
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) {
|
||||
return readFileAsync(file, 'utf-8')
|
||||
.catch(e => {
|
||||
if (e.code === 'ENOENT') {
|
||||
return ''
|
||||
}
|
||||
throw e
|
||||
})
|
||||
function maybeReadFileContentsAsync(file: string) {
|
||||
return readFileAsync(file, 'utf-8').catch(e => {
|
||||
if (e.code === 'ENOENT') {
|
||||
return ''
|
||||
}
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,20 +37,20 @@ function maybeReadFileContentsAsync (file) {
|
||||
* - Finally, The patched files are written into source.
|
||||
*
|
||||
*/
|
||||
export default async function artifacts (compiler, next) {
|
||||
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))
|
||||
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) => {
|
||||
return map(compiler.files, async file => {
|
||||
const sourceFile = join(src, file.filename)
|
||||
const tempFile = join(temp, file.filename)
|
||||
const fileContents = await maybeReadFileContentsAsync(sourceFile)
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
FuseBox,
|
||||
CSSPlugin,
|
||||
JSONPlugin,
|
||||
HTMLPlugin } from 'fuse-box'
|
||||
import { fromCallback } from 'bluebird'
|
||||
import * as path from 'path'
|
||||
|
||||
export default async function bundle (compiler, next) {
|
||||
let options = compiler.options.bundle
|
||||
if (!options) {
|
||||
return next()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,13 @@ import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as child from 'child_process'
|
||||
|
||||
function findNativeModulePath (filePath, bindingsArg) {
|
||||
function findNativeModulePath(filePath: string, bindingsArg: string) {
|
||||
const dirname = path.dirname(filePath)
|
||||
const tempFile = Math.random() * 100 + '.js'
|
||||
const tempFilePath = path.join(dirname, tempFile)
|
||||
fs.writeFileSync(tempFilePath, `
|
||||
fs.writeFileSync(
|
||||
tempFilePath,
|
||||
`
|
||||
var bindings = require('bindings');
|
||||
var Module = require('module');
|
||||
var originalRequire = Module.prototype.require;
|
||||
@@ -16,8 +18,9 @@ function findNativeModulePath (filePath, bindingsArg) {
|
||||
return mod
|
||||
};
|
||||
bindings('${bindingsArg}')
|
||||
`)
|
||||
//using exec because it can be done syncronously
|
||||
`
|
||||
)
|
||||
//using exec because it can be done sync
|
||||
const nativeFileName = child.execSync('node ' + tempFile, { cwd: dirname }).toString()
|
||||
fs.unlinkSync(tempFilePath)
|
||||
const relativePath = './' + path.relative(dirname, nativeFileName).replace(/\\/g, '/')
|
||||
@@ -25,20 +28,18 @@ function findNativeModulePath (filePath, bindingsArg) {
|
||||
}
|
||||
/**
|
||||
* Traverse all nodes in a file and evaluate usages of the bindings module
|
||||
* handles two common cases
|
||||
*/
|
||||
export class BindingsRewrite {
|
||||
private bindingsIdNodes: any[] = []
|
||||
public nativeModulePaths: string[] = []
|
||||
public rewrite = false
|
||||
|
||||
constructor () {
|
||||
this.bindingsIdNodes = []
|
||||
this.nativeModulePaths = []
|
||||
this.rewrite = false
|
||||
}
|
||||
|
||||
isRequireBindings (node) {
|
||||
isRequireBindings(node: any) {
|
||||
return node.callee.name === 'require' && node.arguments[0].value === 'bindings'
|
||||
}
|
||||
|
||||
onNode (absolutePath, node, parent) {
|
||||
onNode(absolutePath: string, node: any, parent: any) {
|
||||
if (node.type === 'CallExpression') {
|
||||
if (this.isRequireBindings(node) && parent.type === 'VariableDeclarator') {
|
||||
/**
|
||||
@@ -0,0 +1,132 @@
|
||||
import { BindingsRewrite } from './bindings-rewrite'
|
||||
import { createHash } from 'crypto'
|
||||
import { readFileSync } from 'fs'
|
||||
import { dirname, join, basename } from 'path'
|
||||
|
||||
function hashName(name: string | Buffer) {
|
||||
return createHash('md5').update(name).digest('hex').toString().slice(0, 8)
|
||||
}
|
||||
|
||||
export interface FuseBoxFile {
|
||||
info: { absPath: string }
|
||||
contents: string
|
||||
analysis: {
|
||||
dependencies: string[]
|
||||
requiresRegeneration: boolean
|
||||
}
|
||||
loadContents(): void
|
||||
makeAnalysis(
|
||||
parsingOptions?: any,
|
||||
traversalPlugin?: {
|
||||
plugins: Array<{
|
||||
onNode(file: FuseBoxFile, node: any, parent: any): void
|
||||
onEnd(file: FuseBoxFile): void
|
||||
}>
|
||||
}
|
||||
): void
|
||||
}
|
||||
|
||||
export interface NativeModulePluginOptions {
|
||||
[key: string]:
|
||||
| {
|
||||
additionalFiles: string[]
|
||||
}
|
||||
| true
|
||||
}
|
||||
|
||||
export 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.info.absPath.endsWith('.node')) {
|
||||
const contents = readFileSync(file.info.absPath)
|
||||
const module = this.modules.find(x =>
|
||||
Boolean(~file.info.absPath.indexOf(join('node_modules', x)))
|
||||
)!
|
||||
const bindingName = basename(file.info.absPath)
|
||||
const settings = this.options[module]
|
||||
const moduleDir = hashName(contents)
|
||||
file.contents = `
|
||||
var fs=require('fs');var path=require('path');var binding='${contents.toString(
|
||||
'base64'
|
||||
)}';function mkdirp(r,t){t=t||null,r=path.resolve(r);try{fs.mkdirSync(r),t=t||r}catch(c){if("ENOENT"===c.code)t=mkdirp(path.dirname(r),t),mkdirp(r,t);else{var i;try{i=fs.statSync(r)}catch(r){throw c}if(!i.isDirectory())throw c}}return t};`
|
||||
|
||||
if (settings === true) {
|
||||
file.contents += `
|
||||
mkdirp('${moduleDir}');
|
||||
var bindingPath = path.join(process.cwd(), '${moduleDir}', '${bindingName}')
|
||||
require('fs').writeFileSync(bindingPath, Buffer.from(binding, 'base64'))
|
||||
process.dlopen(module, bindingPath)
|
||||
`.trim()
|
||||
return
|
||||
}
|
||||
|
||||
let depth = 0
|
||||
settings.additionalFiles.forEach(file => {
|
||||
let ownDepth = 0
|
||||
file.split('/').forEach(x => x === '..' && ownDepth++)
|
||||
depth = ownDepth > depth ? ownDepth : depth
|
||||
})
|
||||
let segments = [moduleDir]
|
||||
while (depth--) {
|
||||
segments.push(hashName(moduleDir + depth))
|
||||
}
|
||||
segments.push(bindingName)
|
||||
|
||||
file.contents += `
|
||||
var cwd = process.cwd()
|
||||
var bindingFileParts = ${JSON.stringify(segments)};
|
||||
var bindingFile = path.join.apply(path, [cwd].concat(bindingFileParts));
|
||||
mkdirp(path.dirname(bindingFile));
|
||||
fs.writeFileSync(bindingFile, Buffer.from(binding, 'base64'));
|
||||
${settings.additionalFiles.reduce((code, filename, i) => {
|
||||
const contents = readFileSync(join(dirname(file.info.absPath), filename))
|
||||
return (code += `
|
||||
var file${i} = '${contents.toString('base64')}';
|
||||
var filePath${i} = path.join(cwd, bindingFileParts[0], '${filename
|
||||
.split('../')
|
||||
.join('')}');
|
||||
mkdirp(path.dirname(filePath${i}));
|
||||
fs.writeFileSync(filePath${i}, Buffer.from(file${i}, 'base64'));
|
||||
`)
|
||||
}, '')};
|
||||
process.dlopen(module, bindingFile)
|
||||
`
|
||||
return
|
||||
}
|
||||
|
||||
const bindingsRewrite = new BindingsRewrite()
|
||||
file.makeAnalysis(null, {
|
||||
plugins: [
|
||||
{
|
||||
onNode(file, node, parent) {
|
||||
bindingsRewrite.onNode(file.info.absPath, node, parent)
|
||||
},
|
||||
onEnd(file) {
|
||||
if (bindingsRewrite.rewrite) {
|
||||
const index = file.analysis.dependencies.indexOf('bindings')
|
||||
if (~index) {
|
||||
file.analysis.dependencies.splice(index, 1)
|
||||
}
|
||||
file.analysis.dependencies.push(...bindingsRewrite.nativeModulePaths)
|
||||
file.analysis.requiresRegeneration = true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { BindingsRewrite } from './bindings-rewrite'
|
||||
|
||||
export class NativeModulePlugin {
|
||||
constructor (...moduleNames) {
|
||||
this.test = new RegExp(`node_modules\/(${moduleNames.join('|')}).*\.js`)
|
||||
this.limit2project = false
|
||||
}
|
||||
|
||||
transform (file) {
|
||||
const bindingsRewrite = new BindingsRewrite()
|
||||
file.loadContents()
|
||||
file.makeAnalysis(null, {
|
||||
plugins: [{
|
||||
onNode(file, node, parent) {
|
||||
bindingsRewrite.onNode(file.info.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
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
+20
-22
@@ -1,13 +1,17 @@
|
||||
import { normalize } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { Readable } from 'stream'
|
||||
import { createWriteStream, chmodSync } from 'fs'
|
||||
import { readFileAsync, dequote, isWindows } from './util'
|
||||
import { NexeCompiler } from './compiler'
|
||||
|
||||
function readStreamAsync (stream) {
|
||||
return new Bluebird((resolve) => {
|
||||
function readStreamAsync(stream: NodeJS.ReadStream): PromiseLike<string> {
|
||||
return new Bluebird(resolve => {
|
||||
let input = ''
|
||||
stream.setEncoding('utf-8')
|
||||
stream.on('data', x => { input += x })
|
||||
stream.on('data', (x: string) => {
|
||||
input += x
|
||||
})
|
||||
stream.once('end', () => resolve(dequote(input)))
|
||||
stream.resume && stream.resume()
|
||||
})
|
||||
@@ -27,35 +31,28 @@ function readStreamAsync (stream) {
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function cli (compiler, next) {
|
||||
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { input, output } = compiler.options
|
||||
const { log } = compiler
|
||||
const bundled = Boolean(compiler.input)
|
||||
if (bundled) {
|
||||
log.step('Using bundled input as the main module')
|
||||
await next()
|
||||
} else if (!input && !process.stdin.isTTY) {
|
||||
if (!input && !process.stdin.isTTY) {
|
||||
log.step('Using stdin as input')
|
||||
compiler.input = await readStreamAsync(process.stdin)
|
||||
} else if (input) {
|
||||
log.step(`Using input file as the main module: ${input}`)
|
||||
compiler.input = await readFileAsync(normalize(input))
|
||||
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)
|
||||
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 = ''
|
||||
}
|
||||
|
||||
if (!bundled) {
|
||||
await next()
|
||||
}
|
||||
await next()
|
||||
|
||||
const shouldPipeOutput = Boolean(!output && !process.stdout.isTTY)
|
||||
const outputName = output ||
|
||||
`${compiler.options.name}${isWindows ? '.exe' : ''}`
|
||||
const outputName = output || `${compiler.options.name}${isWindows ? '.exe' : ''}`
|
||||
|
||||
compiler.output = shouldPipeOutput ? null : outputName
|
||||
const deliverable = await compiler.compileAsync()
|
||||
@@ -67,14 +64,15 @@ export default async function cli (compiler, next) {
|
||||
log.step('Writing binary to stdout')
|
||||
deliverable.pipe(process.stdout).once('error', reject)
|
||||
resolve()
|
||||
} else {
|
||||
} else if (compiler.output) {
|
||||
const step = log.step('Writing result to file')
|
||||
deliverable.pipe(createWriteStream(normalize(compiler.output)))
|
||||
deliverable
|
||||
.pipe(createWriteStream(normalize(compiler.output)))
|
||||
.on('error', reject)
|
||||
.once('close', e => {
|
||||
.once('close', (e: Error) => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
} else if (compiler.output) {
|
||||
chmodSync(compiler.output, '755')
|
||||
step.log(`Executable written to: ${compiler.output}`)
|
||||
resolve(compiler.quit())
|
||||
@@ -1,5 +1,5 @@
|
||||
import { normalize, join } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { Buffer } from 'buffer'
|
||||
import { createHash } from 'crypto'
|
||||
import { createReadStream } from 'fs'
|
||||
@@ -7,13 +7,8 @@ import { Readable } from 'stream'
|
||||
import { spawn } from 'child_process'
|
||||
import { Stream as Needle } from 'nigel'
|
||||
import { Logger } from './logger'
|
||||
import {
|
||||
readFileAsync,
|
||||
writeFileAsync,
|
||||
pathExistsAsync,
|
||||
dequote,
|
||||
isWindows
|
||||
} from './util'
|
||||
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util'
|
||||
import { NexeOptions } from './options'
|
||||
|
||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||
@@ -22,25 +17,56 @@ const marker = Buffer.from('<nexe~sentinel>').toString('hex')
|
||||
const tail = `\n//${marker}`
|
||||
const needle = Buffer.from(marker)
|
||||
const fixedIntegerLength = 10
|
||||
const padLeft = (x, l = fixedIntegerLength, c = '0') => (c.repeat(l) + x).slice(-l)
|
||||
const inflate = (value, size) => {
|
||||
const padLeft = (x: string | number, l = fixedIntegerLength, c = '0') => (c.repeat(l) + x).slice(-l)
|
||||
const inflate = (value: string, size: number) => {
|
||||
if (!size || value.length >= size) {
|
||||
return value
|
||||
}
|
||||
return value + ' '.repeat(size - value.length)
|
||||
}
|
||||
|
||||
export interface NexeFile {
|
||||
filename: string
|
||||
contents: string
|
||||
}
|
||||
|
||||
interface NexeHeader {
|
||||
/**
|
||||
* Zero padded number indicating the extra size available in the binary
|
||||
*/
|
||||
paddingSize: string
|
||||
/**
|
||||
*
|
||||
*/
|
||||
binaryOffset: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export class NexeCompiler {
|
||||
constructor (options) {
|
||||
this.start = Date.now()
|
||||
const { python } = this.options = options
|
||||
this.log = new Logger(options.loglevel)
|
||||
this.src = join(options.temp, options.version)
|
||||
this.env = Object.assign({}, process.env)
|
||||
this.files = []
|
||||
this.nodeSrcBinPath = isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
private start = Date.now()
|
||||
private env = { ...process.env }
|
||||
private compileStep: { modify: Function; log: Function }
|
||||
public log = new Logger(this.options.loglevel)
|
||||
public src = join(this.options.temp, this.options.version)
|
||||
public files: NexeFile[] = []
|
||||
public input: string
|
||||
public output: string | null
|
||||
|
||||
public resources: { bundle: string; index: { [key: string]: number[] } } = {
|
||||
index: {},
|
||||
bundle: ''
|
||||
}
|
||||
public readFileAsync: (file: string) => Promise<NexeFile>
|
||||
public writeFileAsync: (file: string, contents: Buffer | string) => Promise<void>
|
||||
public replaceInFileAsync: (file: string, replacer: any, replaceValue: string) => Promise<void>
|
||||
public setFileContentsAsync: (file: string, contents: string | Buffer) => Promise<void>
|
||||
|
||||
private nodeSrcBinPath = isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
|
||||
constructor(public options: NexeOptions) {
|
||||
const { python } = (this.options = options)
|
||||
|
||||
if (python) {
|
||||
if (isWindows) {
|
||||
@@ -50,74 +76,80 @@ export class NexeCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
this.readFileAsync = async (file) => {
|
||||
this.readFileAsync = async (file: string) => {
|
||||
let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file))
|
||||
if (!cachedFile) {
|
||||
cachedFile = {
|
||||
filename: file,
|
||||
contents: await readFileAsync(join(this.src, file), 'utf-8')
|
||||
.catch({ code: 'ENOENT' }, () => '')
|
||||
contents: await readFileAsync(join(this.src, file), 'utf-8').catch(
|
||||
{ code: 'ENOENT' },
|
||||
() => ''
|
||||
)
|
||||
}
|
||||
this.files.push(cachedFile)
|
||||
}
|
||||
return cachedFile
|
||||
}
|
||||
this.writeFileAsync = (file, contents) => writeFileAsync(join(this.src, file), contents)
|
||||
this.replaceInFileAsync = async (file, ...replacements) => {
|
||||
this.replaceInFileAsync = async (file, replace: string | RegExp, value: string) => {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = entry.contents.replace(...replacements)
|
||||
entry.contents = entry.contents.replace(replace, value)
|
||||
}
|
||||
this.setFileContentsAsync = async (file, contents) => {
|
||||
this.setFileContentsAsync = async (file: string, contents: string) => {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = contents
|
||||
}
|
||||
}
|
||||
|
||||
quit (code = 0) {
|
||||
quit(code = 0) {
|
||||
const time = Date.now() - this.start
|
||||
this.log.write(`Finsihed in ${time / 1000}s`)
|
||||
return this.log.flush().then(x => process.exit(code))
|
||||
}
|
||||
|
||||
_findPaddingSize (size, override = this.options.padding) {
|
||||
private _findPaddingSize(size: number, override = this.options.padding) {
|
||||
if (override === 0) {
|
||||
return 0
|
||||
}
|
||||
size = override > size ? override : size
|
||||
const padding = [3, 6, 9, 16, 25, 40].map(x => x * 1e6).find(p => size <= p)
|
||||
if (!padding) {
|
||||
throw new Error(`No prebuilt target large enough (${(size / 1024).toFixed(2)}Mb).\nUse the --build flag and build for the current platform`)
|
||||
throw new Error(
|
||||
`No prebuilt target large enough (${(size / 1024).toFixed(
|
||||
2
|
||||
)}Mb).\nUse the --build flag and build for the current platform`
|
||||
)
|
||||
}
|
||||
return padding
|
||||
}
|
||||
|
||||
_getNodeExecutableLocation (target) {
|
||||
private _getNodeExecutableLocation(target?: string | null) {
|
||||
if (target) {
|
||||
return join(this.options.temp, target)
|
||||
}
|
||||
return this.nodeSrcBinPath
|
||||
}
|
||||
|
||||
_runBuildCommandAsync (command, args) {
|
||||
private _runBuildCommandAsync(command: string, args: string[]) {
|
||||
return new Bluebird((resolve, reject) => {
|
||||
spawn(command, args, {
|
||||
cwd: this.src,
|
||||
env: this.env,
|
||||
stdio: 'ignore'
|
||||
})
|
||||
.once('error', reject)
|
||||
.once('close', resolve)
|
||||
.once('error', reject)
|
||||
.once('close', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
_configureAsync () {
|
||||
return this._runBuildCommandAsync(
|
||||
this.env.PYTHON || 'python',
|
||||
[configure, ...this.options.configure]
|
||||
)
|
||||
private _configureAsync() {
|
||||
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
|
||||
configure,
|
||||
...this.options.configure
|
||||
])
|
||||
}
|
||||
|
||||
async _buildAsync () {
|
||||
private async _buildAsync() {
|
||||
this.compileStep.log(`Configuring node build: ${this.options.configure}`)
|
||||
await this._configureAsync()
|
||||
const buildOptions = isWindows ? this.options.vcBuild : this.options.make
|
||||
@@ -126,19 +158,20 @@ export class NexeCompiler {
|
||||
return createReadStream(this._getNodeExecutableLocation())
|
||||
}
|
||||
|
||||
_fetchPrebuiltBinaryAsync () {
|
||||
private _fetchPrebuiltBinaryAsync() {
|
||||
return this._buildAsync()
|
||||
}
|
||||
|
||||
_getPayload (header) {
|
||||
private _getPayload(header: NexeHeader) {
|
||||
return this._serializeHeader(header) + this.input + '/**' + this.resources.bundle + `**/`
|
||||
}
|
||||
|
||||
_generateHeader () {
|
||||
private _generateHeader() {
|
||||
const zeros = padLeft(0)
|
||||
const version = ['configure', 'vcBuild', 'make'].reduce((a, c) => {
|
||||
return (a += this.options[c].slice().sort().join())
|
||||
}, '') + this.options.enableNodeCli
|
||||
const version =
|
||||
['configure', 'vcBuild', 'make'].reduce((a, c) => {
|
||||
return (a += (this.options as any)[c].slice().sort().join())
|
||||
}, '') + this.options.enableNodeCli
|
||||
const header = {
|
||||
version: padLeft(0, 32),
|
||||
resources: this.resources.index,
|
||||
@@ -155,7 +188,7 @@ export class NexeCompiler {
|
||||
return header
|
||||
}
|
||||
|
||||
async _getExistingBinaryHeaderAsync (target) {
|
||||
private async _getExistingBinaryHeaderAsync(target: string | undefined | null) {
|
||||
const filename = this._getNodeExecutableLocation(target)
|
||||
const existingBinary = await pathExistsAsync(filename)
|
||||
if (existingBinary) {
|
||||
@@ -164,16 +197,18 @@ export class NexeCompiler {
|
||||
return null
|
||||
}
|
||||
|
||||
_extractHeaderAsync (path) {
|
||||
private _extractHeaderAsync(path: string): Promise<NexeHeader> {
|
||||
const binary = createReadStream(path)
|
||||
const haystack = new Needle(needle)
|
||||
let needles = 0
|
||||
let stackCache = []
|
||||
let stackCache: Buffer[] = []
|
||||
return new Promise((resolve, reject) => {
|
||||
binary.on('error', reject).pipe(haystack)
|
||||
binary
|
||||
.on('error', reject)
|
||||
.pipe(haystack)
|
||||
.on('error', reject)
|
||||
.on('close', () => reject(new Error(`Binary: ${path} is not compatible with nexe`)))
|
||||
.on('haystack', x => needles && stackCache.push(x))
|
||||
.on('haystack', (x: Buffer) => needles && stackCache.push(x))
|
||||
.on('needle', () => {
|
||||
if (++needles === 2) {
|
||||
resolve(JSON.parse(Buffer.concat(stackCache).toString()))
|
||||
@@ -184,19 +219,21 @@ export class NexeCompiler {
|
||||
})
|
||||
}
|
||||
|
||||
_serializeHeader (header) {
|
||||
return `/**${marker}${JSON.stringify(header)}${marker}**/process.__nexe=${JSON.stringify(header)};`
|
||||
private _serializeHeader(header: NexeHeader) {
|
||||
return `/**${marker}${JSON.stringify(header)}${marker}**/process.__nexe=${JSON.stringify(
|
||||
header
|
||||
)};`
|
||||
}
|
||||
|
||||
async setMainModule (compiler, next) {
|
||||
async setMainModule(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await next()
|
||||
const header = compiler._generateHeader()
|
||||
const contents = inflate(this._getPayload(header), +header.paddingSize) + tail
|
||||
return compiler.setFileContentsAsync(`lib/${compiler.options.name}.js`, contents)
|
||||
}
|
||||
|
||||
async compileAsync () {
|
||||
const step = this.compileStep = this.log.step('Compiling result')
|
||||
async compileAsync() {
|
||||
const step = (this.compileStep = this.log.step('Compiling result'))
|
||||
let target = this.options.targets.slice().shift()
|
||||
let prebuiltBinary = null
|
||||
const header = this._generateHeader()
|
||||
@@ -209,29 +246,26 @@ export class NexeCompiler {
|
||||
prebuiltBinary = createReadStream(location)
|
||||
}
|
||||
if (target) {
|
||||
throw new Error('\nNot Implemented, use --build during beta')
|
||||
throw new Error('\nNot Implemented, use --build during beta\n')
|
||||
// prebuiltBinary = await this._fetchPrebuiltBinaryAsync(target)
|
||||
}
|
||||
if (!prebuiltBinary) {
|
||||
prebuiltBinary = await this._buildAsync()
|
||||
step.log('Node binary compiled')
|
||||
}
|
||||
return this._assembleDeliverable(
|
||||
header,
|
||||
prebuiltBinary
|
||||
)
|
||||
return this._assembleDeliverable(header, prebuiltBinary)
|
||||
}
|
||||
|
||||
_assembleDeliverable (header, binary) {
|
||||
private _assembleDeliverable(header: NexeHeader, binary: NodeJS.ReadableStream) {
|
||||
const haystack = new Needle(Buffer.concat([Buffer.from('/**'), needle]))
|
||||
const artifact = new Readable({ read () {} })
|
||||
const artifact = new Readable({ read() {} })
|
||||
let needles = 0
|
||||
let currentStackSize = 0
|
||||
binary.pipe(haystack)
|
||||
haystack
|
||||
.on('close', () => artifact.push(null))
|
||||
.on('needle', () => ++needles && haystack.needle(needle))
|
||||
.on('haystack', x => {
|
||||
.on('haystack', (x: Buffer) => {
|
||||
if (!needles) {
|
||||
currentStackSize += x.length
|
||||
artifact.push(x)
|
||||
@@ -1,23 +1,24 @@
|
||||
import download from 'download'
|
||||
import download = require('download')
|
||||
import { isDirectoryAsync } from './util'
|
||||
import { LogStep } from './logger'
|
||||
import { IncomingMessage } from 'http'
|
||||
import { NexeCompiler } from './compiler'
|
||||
|
||||
function fetchNodeSourceAsync (cwd, url, step, options = {}) {
|
||||
const setText = (p) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
|
||||
function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options = {}) {
|
||||
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
|
||||
return download(url, cwd, Object.assign(options, { extract: true, strip: 1 }))
|
||||
.on('response', res => {
|
||||
.on('response', (res: IncomingMessage) => {
|
||||
const total = +res.headers['content-length']
|
||||
let current = 0
|
||||
res.on('data', data => {
|
||||
current += data.length
|
||||
setText((current / total) * 100)
|
||||
setText(current / total * 100)
|
||||
if (current === total) {
|
||||
step.log('Extracting Node...')
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(
|
||||
() => step.log(`Node source extracted to: ${cwd}`)
|
||||
)
|
||||
.then(() => step.log(`Node source extracted to: ${cwd}`))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +26,7 @@ function fetchNodeSourceAsync (cwd, url, step, options = {}) {
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function downloadNode (compiler, next) {
|
||||
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { src, log } = compiler
|
||||
const { version, sourceUrl, downloadOptions } = compiler.options
|
||||
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
|
||||
@@ -1,42 +1,54 @@
|
||||
import colors from 'chalk'
|
||||
import ora from 'ora'
|
||||
import colors = require('chalk')
|
||||
import ora = require('ora')
|
||||
|
||||
const frameLength = 120
|
||||
|
||||
export interface LogStep {
|
||||
modify(text: string, color?: string): void
|
||||
log(text: string, color?: string): void
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
constructor (level) {
|
||||
private verbose: boolean
|
||||
private silent: boolean
|
||||
private ora: any
|
||||
private modify: Function
|
||||
public write: (text: string, color?: string) => void
|
||||
|
||||
constructor(level: 'verbose' | 'silent' | 'info') {
|
||||
this.verbose = level === 'verbose'
|
||||
this.silent = level === 'silent'
|
||||
if (!this.silent) {
|
||||
this.ora = ora('Starting...', {
|
||||
this.ora = ora({
|
||||
text: 'Starting...',
|
||||
color: 'blue',
|
||||
spinner: 'dots'
|
||||
})
|
||||
this.ora.stop()
|
||||
}
|
||||
const noop = () => {}
|
||||
this.modify = this.slient ? noop : this._modify.bind(this)
|
||||
this.modify = this.silent ? noop : this._modify.bind(this)
|
||||
this.write = this.silent ? noop : this._write.bind(this)
|
||||
}
|
||||
|
||||
flush () {
|
||||
flush() {
|
||||
!this.silent && this.ora.succeed()
|
||||
return new Promise(resolve => setTimeout(resolve, frameLength))
|
||||
}
|
||||
|
||||
_write (update, color = 'green') {
|
||||
this.ora.succeed().text = colors[color](update)
|
||||
_write(update: string, color = 'green') {
|
||||
this.ora.succeed().text = (colors as any)[color](update)
|
||||
this.ora.start()
|
||||
}
|
||||
|
||||
_modify (update, color = this.ora.color) {
|
||||
_modify(update: string, color = this.ora.color) {
|
||||
this.ora.text = update
|
||||
this.ora.color = color
|
||||
}
|
||||
|
||||
step (text) {
|
||||
step(text: string): LogStep {
|
||||
if (this.silent) {
|
||||
return { modify () {}, log () {} }
|
||||
return { modify() {}, log() {} }
|
||||
}
|
||||
if (!this.ora.id) {
|
||||
this.ora.start().text = text
|
||||
@@ -48,6 +60,6 @@ export class Logger {
|
||||
return {
|
||||
modify: this.modify,
|
||||
log: this.verbose ? this.write : this.modify
|
||||
}
|
||||
} as LogStep
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import { compose, PromiseConfig } from 'app-builder'
|
||||
import bundle from './bundle'
|
||||
import { compose, PromiseConfig, Middleware } from 'app-builder'
|
||||
import resource from './resource'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import { argv, normalizeOptionsAsync } from './options'
|
||||
import { argv, normalizeOptionsAsync, NexeOptions } from './options'
|
||||
import cli from './cli'
|
||||
import download from './download'
|
||||
import artifacts from './artifacts'
|
||||
import patches from './patches'
|
||||
import { rimrafAsync } from './util'
|
||||
import Bluebird from 'bluebird'
|
||||
import * as Bluebird from 'bluebird'
|
||||
|
||||
PromiseConfig.constructor = Bluebird
|
||||
|
||||
async function compile (compilerOptions, callback) {
|
||||
async function compile(compilerOptions: NexeOptions, callback?: (err: Error | null) => void) {
|
||||
const options = await normalizeOptionsAsync(compilerOptions)
|
||||
const compiler = new NexeCompiler(options)
|
||||
const build = compiler.options.build
|
||||
@@ -25,19 +24,9 @@ async function compile (compilerOptions, callback) {
|
||||
return compiler.quit()
|
||||
}
|
||||
|
||||
const nexe = compose(...[
|
||||
resource,
|
||||
bundle,
|
||||
cli,
|
||||
build && download,
|
||||
build && artifacts,
|
||||
build && patches,
|
||||
build && options.patches
|
||||
].filter(x => x))
|
||||
const buildSteps = build ? [download, artifacts, ...patches, ...options.patches] : []
|
||||
const nexe = compose(resource, cli, buildSteps)
|
||||
return nexe(compiler).asCallback(callback)
|
||||
}
|
||||
|
||||
export {
|
||||
argv,
|
||||
compile
|
||||
}
|
||||
export { argv, compile }
|
||||
@@ -1,9 +1,74 @@
|
||||
import parseArgv from 'minimist'
|
||||
import * as parseArgv from 'minimist'
|
||||
import { NexeCompiler } from './compiler'
|
||||
import { basename, extname, join } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { EOL } from 'os'
|
||||
|
||||
function padRight (str, l) {
|
||||
export interface NexePatch {
|
||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
||||
}
|
||||
|
||||
export interface NexeOptions {
|
||||
build: boolean
|
||||
/**
|
||||
* Entrypoint filepath
|
||||
*/
|
||||
input: string
|
||||
output: string
|
||||
targets: string[]
|
||||
/**
|
||||
* Build name, Used for executable, and stacktraces
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The node version to be built
|
||||
*/
|
||||
version: string
|
||||
python?: string
|
||||
/**
|
||||
* Node flags e.g. "--expose-gc" baked into the executable
|
||||
*/
|
||||
flags: string[]
|
||||
/**
|
||||
* Pass configuration options to node build configure script
|
||||
*/
|
||||
configure: string[]
|
||||
/**
|
||||
* Pass make options to node make script
|
||||
*/
|
||||
make: string[]
|
||||
vcBuild: string[]
|
||||
snapshot?: string
|
||||
/**
|
||||
* Array of glob strings describing resources to pull into the bundle
|
||||
*/
|
||||
resources: string[]
|
||||
/**
|
||||
* Temporary directory where nexe artifacts will be cached
|
||||
* TODO dot folder in home dir
|
||||
*/
|
||||
temp: string
|
||||
ico?: string
|
||||
rc: { [key: string]: string }
|
||||
/**
|
||||
* Causes nexe to remove all temporary files for current configuration
|
||||
*/
|
||||
clean: boolean
|
||||
enableNodeCli: boolean
|
||||
sourceUrl?: string
|
||||
loglevel: 'info' | 'silent' | 'verbose'
|
||||
silent?: boolean
|
||||
verbose?: boolean
|
||||
info?: boolean
|
||||
padding: number
|
||||
patches: NexePatch[]
|
||||
|
||||
empty: boolean
|
||||
warmup?: string
|
||||
downloadOptions?: any
|
||||
}
|
||||
|
||||
function padRight(str: string, l: number) {
|
||||
return (str + ' '.repeat(l)).substr(0, l)
|
||||
}
|
||||
const defaults = {
|
||||
@@ -14,8 +79,6 @@ const defaults = {
|
||||
make: [],
|
||||
targets: [],
|
||||
vcBuild: ['nosign', 'release'],
|
||||
bundle: false,
|
||||
build: false,
|
||||
enableNodeCli: false,
|
||||
patches: []
|
||||
}
|
||||
@@ -33,7 +96,6 @@ const alias = {
|
||||
m: 'make',
|
||||
vc: 'vcBuild',
|
||||
s: 'snapshot',
|
||||
b: 'bundle',
|
||||
cli: 'enableNodeCli',
|
||||
h: 'help',
|
||||
l: 'loglevel'
|
||||
@@ -54,7 +116,6 @@ nexe --help CLI OPTIONS
|
||||
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
|
||||
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
|
||||
-r --resource =./paths/**/* -- *embed file bytes within the binary
|
||||
-b --bundle =webpack-config.js -- use default configuration or provide custom webpack config
|
||||
--temp =./path/to/temp -- nexe temp files (for downloads and source builds)
|
||||
--ico -- file name for alternate icon file (windows)
|
||||
--rc-* -- populate rc file options (windows)
|
||||
@@ -66,8 +127,8 @@ nexe --help CLI OPTIONS
|
||||
|
||||
-* variable key name * option can be used more than once`.trim()
|
||||
|
||||
function flattenFilter (...args) {
|
||||
return [].concat(...args).filter(x => x)
|
||||
function flattenFilter(...args: any[]): string[] {
|
||||
return ([] as string[]).concat(...args).filter(x => x)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,9 +137,10 @@ function flattenFilter (...args) {
|
||||
* @param {*} match
|
||||
* @param {*} options
|
||||
*/
|
||||
function extractCliMap (match, options) {
|
||||
return Object.keys(options).filter(x => match.test(x))
|
||||
.reduce((map, option) => {
|
||||
function extractCliMap(match: RegExp, options: any) {
|
||||
return Object.keys(options)
|
||||
.filter(x => match.test(x))
|
||||
.reduce((map: { [key: string]: string }, option: keyof NexeOptions) => {
|
||||
const key = option.split('-')[1]
|
||||
map[key] = options[option]
|
||||
delete options[option]
|
||||
@@ -86,24 +148,24 @@ function extractCliMap (match, options) {
|
||||
}, {})
|
||||
}
|
||||
|
||||
function tryResolveMainFileName () {
|
||||
function tryResolveMainFileName() {
|
||||
let filename
|
||||
try {
|
||||
const file = require.resolve(process.cwd())
|
||||
filename = basename(file).replace(extname(file), '')
|
||||
} catch (_) {}
|
||||
|
||||
return !filename || filename === 'index' ? ('nexe_' + Date.now()) : filename
|
||||
return !filename || filename === 'index' ? 'nexe_' + Date.now() : filename
|
||||
}
|
||||
|
||||
function extractLogLevel (options) {
|
||||
function extractLogLevel(options: NexeOptions) {
|
||||
if (options.loglevel) return options.loglevel
|
||||
if (options.silent) return 'silent'
|
||||
if (options.verbose) return 'verbose'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function extractName (options) {
|
||||
function extractName(options: NexeOptions) {
|
||||
let name = options.name
|
||||
if (typeof options.input === 'string' && !name) {
|
||||
name = basename(options.input).replace(extname(options.input), '')
|
||||
@@ -112,25 +174,23 @@ function extractName (options) {
|
||||
return name.replace(/\.exe$/, '')
|
||||
}
|
||||
|
||||
function normalizeOptionsAsync (input) {
|
||||
function normalizeOptionsAsync(input: Partial<NexeOptions>) {
|
||||
if (argv.help || argv._.some(x => x === 'version')) {
|
||||
return Bluebird.fromCallback(cb => process.stderr.write(
|
||||
argv.help ? help : '2.0.0-beta.1' + EOL,
|
||||
() => cb(null, process.exit(0))
|
||||
))
|
||||
process.stderr.write(argv.help ? help : '2.0.0-beta.1' + EOL, () => process.exit(0))
|
||||
}
|
||||
|
||||
const options = Object.assign({}, defaults, input)
|
||||
delete options._
|
||||
const options = Object.assign({}, defaults, input) as NexeOptions
|
||||
const opts = options as any
|
||||
delete opts._
|
||||
options.loglevel = extractLogLevel(options)
|
||||
options.name = extractName(options)
|
||||
options.flags = flattenFilter(options.flag, options.flags)
|
||||
options.targets = flattenFilter(options.target, options.targets)
|
||||
options.flags = flattenFilter(opts.flag, options.flags)
|
||||
options.targets = flattenFilter(opts.target, options.targets)
|
||||
options.make = flattenFilter(options.make)
|
||||
options.vcBuild = flattenFilter(options.vcBuild)
|
||||
options.resources = flattenFilter(options.resource, options.resources)
|
||||
options.resources = flattenFilter(opts.resource, options.resources)
|
||||
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
|
||||
|
||||
options.build = true // FIXME
|
||||
if (options.build || options.padding === 0) {
|
||||
options.targets = []
|
||||
options.build = true
|
||||
@@ -139,14 +199,9 @@ function normalizeOptionsAsync (input) {
|
||||
options.targets = [defaultTarget]
|
||||
}
|
||||
|
||||
Object.keys(alias)
|
||||
.filter(k => k !== 'rc')
|
||||
.forEach(x => delete options[x])
|
||||
Object.keys(alias).filter(k => k !== 'rc').forEach(x => delete opts[x])
|
||||
|
||||
return Promise.resolve(options)
|
||||
}
|
||||
|
||||
export {
|
||||
argv,
|
||||
normalizeOptionsAsync
|
||||
}
|
||||
export { argv, normalizeOptionsAsync }
|
||||
@@ -1,14 +0,0 @@
|
||||
export default async function disableNodeCli (compiler, next) {
|
||||
if (compiler.options.enableNodeCli) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const nodeccMarker = "argv[index][0] == '-'"
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
nodeccMarker,
|
||||
nodeccMarker.replace('-', ']')
|
||||
)
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
if (compiler.options.enableNodeCli) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const nodeccMarker = "argv[index][0] == '-'"
|
||||
|
||||
await compiler.replaceInFileAsync('src/node.cc', nodeccMarker, nodeccMarker.replace('-', ']'))
|
||||
return next()
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export default async function flags (compiler, next) {
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const nodeflags = compiler.options.flags
|
||||
if (!nodeflags.length) {
|
||||
return next()
|
||||
@@ -1,12 +0,0 @@
|
||||
export default async function nodeGyp ({ files, replaceInFileAsync }, next) {
|
||||
await next()
|
||||
|
||||
const nodeGypMarker = "'lib/fs.js',"
|
||||
await replaceInFileAsync('node.gyp', nodeGypMarker, `
|
||||
${nodeGypMarker}
|
||||
${files
|
||||
.filter(x => x.filename.startsWith('lib'))
|
||||
.map(x => `'${x.filename}'`)
|
||||
.toString()},
|
||||
`.trim())
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function nodeGyp(
|
||||
{ files, replaceInFileAsync }: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
await next()
|
||||
|
||||
const nodeGypMarker = "'lib/fs.js',"
|
||||
await replaceInFileAsync(
|
||||
'node.gyp',
|
||||
nodeGypMarker,
|
||||
`
|
||||
${nodeGypMarker}
|
||||
${files.filter(x => x.filename.startsWith('lib')).map(x => `'${x.filename}'`).toString()},
|
||||
`.trim()
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { normalize } from 'path'
|
||||
import { readFileAsync } from '../util'
|
||||
|
||||
export default async function ico (compiler, next) {
|
||||
const iconFile = compiler.options.ico
|
||||
if (!iconFile) {
|
||||
return next()
|
||||
}
|
||||
await compiler.setFileContentsAsync(
|
||||
'src/res/node.ico',
|
||||
await readFileAsync(normalize(iconFile))
|
||||
)
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { normalize } from 'path'
|
||||
import { readFileAsync } from '../util'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function ico(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const iconFile = compiler.options.ico
|
||||
if (!iconFile) {
|
||||
return next()
|
||||
}
|
||||
await compiler.setFileContentsAsync('src/res/node.ico', await readFileAsync(normalize(iconFile)))
|
||||
return next()
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import cli from './disable-node-cli'
|
||||
import flags from './flags'
|
||||
import ico from './ico'
|
||||
import rc from './node-rc'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
const patches = [
|
||||
gyp,
|
||||
(compiler, next) => compiler.setMainModule(compiler, next),
|
||||
(compiler: NexeCompiler, next: () => Promise<void>) => compiler.setMainModule(compiler, next),
|
||||
nexePatches,
|
||||
cli,
|
||||
flags,
|
||||
@@ -1,4 +1,6 @@
|
||||
export default async function nodeRc (compiler, next) {
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function nodeRc(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const options = compiler.options.rc
|
||||
if (!options) {
|
||||
return next()
|
||||
@@ -6,7 +8,7 @@ export default async function nodeRc (compiler, next) {
|
||||
|
||||
const file = await compiler.readFileAsync('src/res/node.rc')
|
||||
|
||||
Object.keys(options).forEach((key) => {
|
||||
Object.keys(options).forEach(key => {
|
||||
let value = options[key]
|
||||
const isVar = /^[A-Z_]+$/.test(value)
|
||||
value = isVar ? value : `"${value}"`
|
||||
@@ -1,14 +0,0 @@
|
||||
export default async function snapshot (compiler, next) {
|
||||
const snapshotFile = compiler.options.snapshot
|
||||
|
||||
if (!snapshotFile) {
|
||||
return next()
|
||||
}
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'configure',
|
||||
'def configure_v8(o):',
|
||||
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${snapshotFile}'`
|
||||
)
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function snapshot(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const snapshotFile = compiler.options.snapshot
|
||||
const warmupScript = compiler.options.warmup
|
||||
|
||||
if (!snapshotFile) {
|
||||
return next()
|
||||
}
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'configure',
|
||||
'def configure_v8(o):',
|
||||
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${warmupScript ||
|
||||
snapshotFile}'`
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
export default async function main (compiler, next) {
|
||||
await compiler.setFileContentsAsync('lib/_third_party_main.js', `
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await compiler.setFileContentsAsync(
|
||||
'lib/_third_party_main.js',
|
||||
`
|
||||
const fs = require('fs')
|
||||
const Buffer = require('buffer').Buffer
|
||||
const isString = x => typeof x === 'string' || x instanceof String
|
||||
@@ -1,20 +1,18 @@
|
||||
import { each } from 'bluebird'
|
||||
import { readFileAsync, isDirectoryAsync } from './util'
|
||||
import { Buffer } from 'buffer'
|
||||
import globs from 'globby'
|
||||
import * as globs from 'globby'
|
||||
import { NexeCompiler } from './compiler'
|
||||
|
||||
export default async function resource (compiler, next) {
|
||||
const resources = compiler.resources = {
|
||||
index: {},
|
||||
bundle: ''
|
||||
}
|
||||
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const resources = compiler.resources
|
||||
|
||||
if (!compiler.options.resources.length) {
|
||||
return next()
|
||||
}
|
||||
const step = compiler.log.step('Bundling Resources...')
|
||||
let count = 0
|
||||
await each(globs(compiler.options.resources), async (file) => {
|
||||
await each(globs(compiler.options.resources), async file => {
|
||||
if (await isDirectoryAsync(file)) {
|
||||
return
|
||||
}
|
||||
@@ -28,6 +26,8 @@ export default async function resource (compiler, next) {
|
||||
]
|
||||
resources.bundle += commentSafeContents
|
||||
})
|
||||
step.log(`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`)
|
||||
step.log(
|
||||
`Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB`
|
||||
)
|
||||
return next()
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
declare module 'nigel' {
|
||||
import { Writable } from 'stream'
|
||||
interface Needle {
|
||||
value: Buffer
|
||||
lastPos: number
|
||||
last: number
|
||||
length: number
|
||||
badCharShift: Buffer
|
||||
}
|
||||
|
||||
export function compile(needle: Buffer): Needle
|
||||
export function horspool(haystack: Buffer, needle: Needle, start: number): number
|
||||
export function all(haystack: Buffer, needle: Needle, pos: number): number[]
|
||||
export class Stream extends Writable {
|
||||
constructor(needle: Buffer)
|
||||
needle(needle: Buffer): void
|
||||
flush(): void
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'download' {
|
||||
import { Duplex } from 'stream'
|
||||
interface DownloadOptions {
|
||||
extract?: boolean
|
||||
strip?: number
|
||||
filename?: string
|
||||
proxy?: string
|
||||
}
|
||||
function download(
|
||||
url: string,
|
||||
destination?: string | DownloadOptions,
|
||||
options?: DownloadOptions
|
||||
): PromiseLike<Buffer> & Duplex
|
||||
export = download
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import { readFile, writeFile, stat } from 'fs'
|
||||
import Bluebird from 'bluebird'
|
||||
import rimraf from 'rimraf'
|
||||
|
||||
const rimrafAsync = Bluebird.promisify(rimraf)
|
||||
|
||||
function dequote (input) {
|
||||
input = input.trim()
|
||||
const singleQuote = input.startsWith('\'') && input.endsWith('\'')
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"')
|
||||
if (singleQuote || doubleQuote) {
|
||||
return input.slice(1).slice(0, -1)
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
const readFileAsync = Bluebird.promisify(readFile)
|
||||
const writeFileAsync = Bluebird.promisify(writeFile)
|
||||
const statAsync = Bluebird.promisify(stat)
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function pathExistsAsync (path) {
|
||||
return statAsync(path).then(x => true, err => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function isDirectoryAsync (path) {
|
||||
return statAsync(path)
|
||||
.then(x => x.isDirectory())
|
||||
.catch({ code: 'ENOENT' }, () => false)
|
||||
}
|
||||
|
||||
export {
|
||||
dequote,
|
||||
isWindows,
|
||||
rimrafAsync,
|
||||
readFileAsync,
|
||||
pathExistsAsync,
|
||||
isDirectoryAsync,
|
||||
writeFileAsync
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { readFile, writeFile, stat } from 'fs'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import rimraf = require('rimraf')
|
||||
|
||||
const { promisify } = Bluebird
|
||||
const rimrafAsync = (promisify(rimraf) as any) as (path: string) => Bluebird<void>
|
||||
|
||||
function dequote(input: string) {
|
||||
input = input.trim()
|
||||
const singleQuote = input.startsWith("'") && input.endsWith("'")
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"')
|
||||
if (singleQuote || doubleQuote) {
|
||||
return input.slice(1).slice(0, -1)
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
export interface ReadFileAsync {
|
||||
(path: string): Bluebird<Buffer>
|
||||
(path: string, encoding: string): Bluebird<string>
|
||||
}
|
||||
|
||||
const readFileAsync = (promisify(readFile) as any) as ReadFileAsync
|
||||
const writeFileAsync = (promisify(writeFile) as any) as (
|
||||
path: string,
|
||||
contents: string | Buffer
|
||||
) => Promise<void>
|
||||
const statAsync = promisify(stat)
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function pathExistsAsync(path: string) {
|
||||
return statAsync(path).then(x => true).catch({ code: 'ENOENT' }, () => false)
|
||||
}
|
||||
|
||||
function isDirectoryAsync(path: string) {
|
||||
return statAsync(path).then(x => x.isDirectory()).catch({ code: 'ENOENT' }, () => false)
|
||||
}
|
||||
|
||||
export {
|
||||
dequote,
|
||||
isWindows,
|
||||
rimrafAsync,
|
||||
readFileAsync,
|
||||
pathExistsAsync,
|
||||
isDirectoryAsync,
|
||||
writeFileAsync
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target":"es5",
|
||||
"lib": ["es2017"],
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
"strict": true
|
||||
},
|
||||
"include":[
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user