feat: instant builds
This commit is contained in:
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"chenxsan.vscode-standardjs"
|
||||
]
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"standard.enable": true
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
const nexe = require('./lib/nexe')
|
||||
const { error } = require('./lib/logger')
|
||||
const logger = require('./lib/logger').default
|
||||
|
||||
module.exports = nexe
|
||||
|
||||
if (require.main === module) {
|
||||
nexe.compile(nexe.argv).catch((e) => {
|
||||
error(e.stack, () => process.exit(1))
|
||||
logger.error(e.stack, () => process.exit(1))
|
||||
})
|
||||
}
|
||||
|
||||
+6
-2
@@ -30,20 +30,24 @@
|
||||
"app-builder": "^5.1.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"chalk": "^1.1.3",
|
||||
"globby": "^6.1.0",
|
||||
"html-loader": "^0.4.5",
|
||||
"json-loader": "^0.5.4",
|
||||
"memory-fs": "^0.4.1",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nigel": "^2.0.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.81.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"streamsearch": "^0.1.2",
|
||||
"tar-fs": "^1.15.2",
|
||||
"webpack": "^2.3.3"
|
||||
"webpack": "^2.3.3",
|
||||
"xbin-loader": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-preset-env": "^1.3.3",
|
||||
"mocha": "^3.2.0",
|
||||
"standard": "^10.0.1"
|
||||
|
||||
+8
-15
@@ -1,19 +1,10 @@
|
||||
import Mfs from 'memory-fs'
|
||||
import webpack from 'webpack'
|
||||
import { fromCallback } from 'bluebird'
|
||||
import { resolveModule } from './util'
|
||||
import { resolve, join } from 'path'
|
||||
import module from 'module'
|
||||
import 'json-loader'
|
||||
import 'html-loader'
|
||||
import { resolve, join, relative } from 'path'
|
||||
|
||||
const isObject = (x) => typeof x === 'object'
|
||||
const isString = (x) => typeof x === 'string'
|
||||
|
||||
function loadModule (path) {
|
||||
console.log('loading module!!!!', path)
|
||||
return module._load(path, module, false)
|
||||
}
|
||||
const isString = (x) => typeof x === 'string' || x instanceof String
|
||||
|
||||
export default async function bundle (compiler, next) {
|
||||
let bundleConfig = compiler.options.bundle
|
||||
@@ -21,21 +12,23 @@ export default async function bundle (compiler, next) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const input = compiler.options.input || resolveModule(process.cwd())
|
||||
const inputFilePath = compiler.options.input || require.resolve(process.cwd())
|
||||
const mfs = new Mfs()
|
||||
const path = resolve('nexe')
|
||||
const filename = 'virtual-bundle.js'
|
||||
|
||||
if (isString(bundleConfig)) {
|
||||
bundleConfig = loadModule(relative(process.cwd(), bundleConfig))
|
||||
bundleConfig = require(relative(process.cwd(), bundleConfig))
|
||||
} else if (!isObject(bundleConfig)) {
|
||||
bundleConfig = {
|
||||
entry: resolve(input),
|
||||
entry: resolve(inputFilePath),
|
||||
target: 'node',
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.html$/, use: 'html-loader' },
|
||||
{ test: /\.json$/, use: 'json-loader' }
|
||||
{ test: /\.json$/, use: 'json-loader' },
|
||||
{ test: /\.node$/, use: 'xbin-loader' },
|
||||
{ test: /license$|\.md$/, use: 'raw-loader' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+22
-31
@@ -1,21 +1,7 @@
|
||||
import { normalize } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import { createWriteStream } from 'fs'
|
||||
import { dequote, readFileAsync, resolveModule } from './util'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function getStdIn () {
|
||||
return new Bluebird((resolve) => {
|
||||
const bundle = []
|
||||
process.stdin.setEncoding('utf-8')
|
||||
process.stdin.on('data', x => bundle.push(x))
|
||||
process.stdin.once('end', () =>
|
||||
resolve(dequote(Buffer.concat(bundle).toString()))
|
||||
)
|
||||
process.stdin.resume()
|
||||
})
|
||||
}
|
||||
import { readFileAsync, readStreamAsync, isWindows } from './util'
|
||||
|
||||
/**
|
||||
* The "cli" step detects whether the process is in a tty. If it is then the input is read into memory.
|
||||
@@ -38,41 +24,46 @@ export default async function cli (compiler, next) {
|
||||
if (bundled) {
|
||||
await next()
|
||||
} else if (!input && !process.stdin.isTTY) {
|
||||
compiler.log.verbose('Buffering stdin as main module...')
|
||||
compiler.input = await getStdIn()
|
||||
compiler.log.verbose('Buffering stdin as bundle...')
|
||||
compiler.input = await readStreamAsync(process.stdin)
|
||||
} else if (input) {
|
||||
compiler.log.verbose('Reading input as main module: ' + input)
|
||||
compiler.log.verbose('Reading input as bundle: ' + input)
|
||||
compiler.input = await readFileAsync(normalize(input))
|
||||
} else if (!compiler.options.empty) {
|
||||
compiler.log.verbose('Resolving cwd as main module...')
|
||||
compiler.input = await readFileAsync(resolveModule(process.cwd()))
|
||||
const bundle = require.resolve(process.cwd())
|
||||
compiler.log.verbose('Resolving cwd as main bundle: ' + bundle)
|
||||
compiler.input = await readFileAsync(bundle)
|
||||
}
|
||||
|
||||
if (!bundled) {
|
||||
await next()
|
||||
}
|
||||
|
||||
const shouldPipeOutput = Boolean(!compiler.options.output && !process.stdout.isTTY)
|
||||
const outputName = compiler.options.output ||
|
||||
`${compiler.options.name}${isWindows ? '.exe' : ''}`
|
||||
|
||||
compiler.output = shouldPipeOutput ? null : outputName
|
||||
const deliverable = await compiler.compileAsync()
|
||||
|
||||
return new Bluebird((resolve, reject) => {
|
||||
deliverable.once('error', reject)
|
||||
|
||||
if (!compiler.options.output && !process.stdout.isTTY) {
|
||||
if (!compiler.output) {
|
||||
compiler.log.verbose('Writing result to stdout...')
|
||||
deliverable.pipe(process.stdout).once('error', reject)
|
||||
resolve()
|
||||
} else {
|
||||
compiler.log.verbose('Writing result to file...')
|
||||
const output = compiler.options.output || `${compiler.options.name}${isWindows ? '.exe' : ''}`
|
||||
deliverable.pipe(createWriteStream(normalize(output)))
|
||||
.once('error', reject)
|
||||
.once('close', e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
compiler.log.info('Executable written: ' + output, resolve)
|
||||
}
|
||||
})
|
||||
deliverable.pipe(createWriteStream(normalize(compiler.output)))
|
||||
.once('error', reject)
|
||||
.once('close', e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
compiler.log.info('Executable written: ' + compiler.output, resolve)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+194
-11
@@ -1,14 +1,32 @@
|
||||
import { normalize, join } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import { Buffer } from 'buffer'
|
||||
import { createReadStream } from 'fs'
|
||||
import { Readable } from 'stream'
|
||||
import { spawn } from 'child_process'
|
||||
import * as logger from './logger'
|
||||
import { readFileAsync, writeFileAsync, dequote } from './util'
|
||||
import { Stream as Needle } from 'nigel'
|
||||
import logger from './logger'
|
||||
import { deepEqual } from 'assert'
|
||||
import {
|
||||
readFileAsync,
|
||||
writeFileAsync,
|
||||
fileExistsAsync,
|
||||
dequote,
|
||||
isWindows
|
||||
} from './util'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||
const configure = isWindows ? 'configure' : './configure'
|
||||
const marker = Buffer.from('<nexe~sentinel>').toString('hex')
|
||||
const needle = Buffer.from(marker)
|
||||
const padLeft = (x, l, c = '0') => (c.repeat(l) + x).slice(-l)
|
||||
const inflate = (value, size) => {
|
||||
if (!size | value.length >= size) {
|
||||
return value
|
||||
}
|
||||
return value + Array(size - value.length + 1).join(';')
|
||||
}
|
||||
|
||||
export class NexeCompiler {
|
||||
constructor (options) {
|
||||
@@ -16,8 +34,6 @@ export class NexeCompiler {
|
||||
logger.setLevel(options.loglevel)
|
||||
this.log = logger
|
||||
this.python = options.python
|
||||
this.configure = options.configure
|
||||
this.make = isWindows ? options.vcBuild : options.make
|
||||
this.src = join(options.temp, options.version)
|
||||
this.env = Object.assign({}, process.env)
|
||||
this.files = []
|
||||
@@ -48,7 +64,24 @@ export class NexeCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
get _deliverableLocation () {
|
||||
_findPaddingSize (size, override) {
|
||||
if (override === 0) {
|
||||
return 0
|
||||
}
|
||||
size = override > size ? override : size
|
||||
const padding = [3, 6, 9, 16, 25, 40].map(x => x * 1e6)
|
||||
.filter(p => size <= p)[0]
|
||||
if (!padding) {
|
||||
throw new Error(`No prebuilt target large enough (${(size / 1024).toFixed(2)}Mb).\nUse the --build flag and build for the current platform`)
|
||||
}
|
||||
return padding
|
||||
}
|
||||
|
||||
_getArtifactLocation (target) {
|
||||
if (target) {
|
||||
return join(this.options.temp, target)
|
||||
}
|
||||
|
||||
return isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
@@ -69,18 +102,168 @@ export class NexeCompiler {
|
||||
_configureAsync () {
|
||||
return this._runBuildCommandAsync(
|
||||
this.env.PYTHON || 'python',
|
||||
[configure, ...this.configure]
|
||||
[configure, ...this.options.configure]
|
||||
)
|
||||
}
|
||||
|
||||
async _buildAsync () {
|
||||
if (this.options.clean) {
|
||||
await this._runBuildCommandAsync(make, ['clean'])
|
||||
}
|
||||
await this._configureAsync()
|
||||
return this._runBuildCommandAsync(make, this.make)
|
||||
const buildOptions = isWindows ? this.options.vcBuild : this.options.make
|
||||
await this._runBuildCommandAsync(make, buildOptions)
|
||||
return createReadStream(this._getArtifactLocation())
|
||||
}
|
||||
|
||||
compileAsync () {
|
||||
return this._buildAsync().then(() => {
|
||||
return createReadStream(this._deliverableLocation)
|
||||
_fetchPrebuiltBinary () {
|
||||
/**
|
||||
* TODO implement hosted builds
|
||||
* - CircleCI artifacts
|
||||
* - AppVeyor windows build
|
||||
* - Publish to github release from appveyor
|
||||
* github.io site build
|
||||
*/
|
||||
return this._buildAsync()
|
||||
}
|
||||
|
||||
_getPayload (header) {
|
||||
return this._serializeHeader(header) + this.input + '/**' + this.resources.bundle + `**/`
|
||||
}
|
||||
|
||||
_generateHeader (paddingOverride) {
|
||||
const zeros = padLeft(0, 20)
|
||||
const header = {
|
||||
configure: this.options.configure.slice().sort(),
|
||||
make: this.options.make.slice().sort(),
|
||||
enableNodeCli: this.options.enableNodeCli,
|
||||
vcBuild: this.options.vcBuild.slice().sort(),
|
||||
resources: this.resources.index,
|
||||
contentSize: zeros,
|
||||
paddingSize: zeros,
|
||||
resourceOffset: zeros,
|
||||
binaryOffset: zeros
|
||||
}
|
||||
const serializedHeader = this._serializeHeader(header)
|
||||
header.contentSize = padLeft(Buffer.byteLength(this._getPayload(header)), 20)
|
||||
header.paddingSize = padLeft(this._findPaddingSize(+header.contentSize, paddingOverride), 20)
|
||||
header.resourceOffset = padLeft(Buffer.byteLength(serializedHeader + this.input + '/**'), 20)
|
||||
return header
|
||||
}
|
||||
|
||||
async _getExistingBinaryHeaderAsync (target) {
|
||||
const filename = this._getArtifactLocation(target)
|
||||
const existingBinary = await fileExistsAsync(filename)
|
||||
if (existingBinary) {
|
||||
return this._extractHeaderAsync(filename)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
_extractHeaderAsync (path) {
|
||||
const haystack = createReadStream(path)
|
||||
const stream = new Needle(needle)
|
||||
let needles = 0
|
||||
let lastStack = null
|
||||
haystack.pipe(stream)
|
||||
return new Promise(resolve => {
|
||||
stream.on('needle', () => needles++)
|
||||
.on('haystack', x => {
|
||||
if (needles === 2) {
|
||||
haystack.close()
|
||||
stream.end()
|
||||
resolve(JSON.parse(lastStack.toString()))
|
||||
}
|
||||
lastStack = x
|
||||
}).on('close', () => resolve(null))
|
||||
})
|
||||
}
|
||||
|
||||
_serializeHeader (header) {
|
||||
return `/**${marker}${JSON.stringify(header)}${marker}**/process.__nexe=${JSON.stringify(header)};/**${marker}**/`
|
||||
}
|
||||
|
||||
_headersAreEqual (rhs, lhs) {
|
||||
const ignoreKeys = [
|
||||
'resourceOffset', 'binaryOffset', 'contentSize', 'resources'
|
||||
].reduce((acc, c) => {
|
||||
acc[c] = true
|
||||
return acc
|
||||
}, {})
|
||||
rhs = Object.assign({}, rhs, ignoreKeys)
|
||||
lhs = Object.assign({}, lhs, ignoreKeys)
|
||||
try {
|
||||
deepEqual(rhs, lhs)
|
||||
return true
|
||||
} catch (e) { void e }
|
||||
return false
|
||||
}
|
||||
|
||||
async setMainModule (compiler, next) {
|
||||
if (compiler.options.targets.length) {
|
||||
return
|
||||
}
|
||||
await next()
|
||||
const mainFile = await compiler.readFileAsync(`lib/${compiler.options.name}.js`)
|
||||
const header = compiler._generateHeader(compiler.options.padding)
|
||||
mainFile.contents = inflate(this._getPayload(header), +header.paddingSize) +
|
||||
'\n//' + marker
|
||||
}
|
||||
|
||||
async compileAsync () {
|
||||
let target = this.options.targets.slice().shift() // TODO support multiple targets
|
||||
let prebuiltSource = null
|
||||
const header = this._generateHeader(this.options.padding)
|
||||
target = target && `${target}-${header.paddingSize}`
|
||||
const existingBinaryHeader = await this._getExistingBinaryHeaderAsync(target)
|
||||
|
||||
if (existingBinaryHeader && this._headersAreEqual(header, existingBinaryHeader)) {
|
||||
prebuiltSource = createReadStream(this._getArtifactLocation(target))
|
||||
}
|
||||
|
||||
if (target) {
|
||||
prebuiltSource = this._fetchPrebuiltBinary(target)
|
||||
}
|
||||
|
||||
if (!prebuiltSource) {
|
||||
prebuiltSource = await this._buildAsync()
|
||||
}
|
||||
|
||||
return this._assembleDeliverable(
|
||||
header,
|
||||
prebuiltSource
|
||||
)
|
||||
}
|
||||
|
||||
_assembleDeliverable (header, binary) {
|
||||
const firstNeedle = Buffer.concat([Buffer.from('/**'), needle])
|
||||
const stream = new Needle(firstNeedle)
|
||||
const artifact = new Readable({ read () {} })
|
||||
let needles = 0
|
||||
let currentStackSize = 0
|
||||
binary.pipe(stream)
|
||||
stream.on('needle', () => {
|
||||
needles++
|
||||
stream.needle(needle)
|
||||
})
|
||||
.on('haystack', x => {
|
||||
if (needles < 1) {
|
||||
currentStackSize += x.length
|
||||
artifact.push(x)
|
||||
}
|
||||
if (needles === 1 && !+header.binaryOffset) {
|
||||
header.binaryOffset = padLeft(currentStackSize, 20)
|
||||
artifact.push(
|
||||
Buffer.from(
|
||||
inflate(this._getPayload(header), +header.paddingSize) +
|
||||
'\n//' + marker
|
||||
)
|
||||
)
|
||||
}
|
||||
if (needles > 3) {
|
||||
artifact.push(x)
|
||||
}
|
||||
}).on('close', () => artifact.push(null))
|
||||
return artifact
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -43,11 +43,4 @@ const logger = logLevels.reduce((logMethods, info) => {
|
||||
}
|
||||
})
|
||||
|
||||
const { info, error, verbose, setLevel } = logger
|
||||
|
||||
export {
|
||||
setLevel,
|
||||
info,
|
||||
error,
|
||||
verbose
|
||||
}
|
||||
export default logger
|
||||
|
||||
+11
-10
@@ -9,7 +9,7 @@ import artifacts from './artifacts'
|
||||
import patches from './patches'
|
||||
import { EOL } from 'os'
|
||||
import Bluebird from 'bluebird'
|
||||
import { error } from './logger'
|
||||
import logger from './logger'
|
||||
|
||||
PromiseConfig.constructor = Bluebird
|
||||
Bluebird.longStackTraces()
|
||||
@@ -17,25 +17,26 @@ Bluebird.longStackTraces()
|
||||
async function compile (compilerOptions, callback) {
|
||||
const options = await normalizeOptionsAsync(compilerOptions)
|
||||
const compiler = new NexeCompiler(options)
|
||||
const build = compiler.options.build
|
||||
|
||||
compiler.log.verbose('Compiler options:' +
|
||||
EOL + JSON.stringify(compiler.options, null, 4)
|
||||
)
|
||||
|
||||
const nexe = compose(
|
||||
const nexe = compose(...[
|
||||
resource,
|
||||
bundle,
|
||||
cli,
|
||||
download,
|
||||
artifacts,
|
||||
patches,
|
||||
options.patches
|
||||
)
|
||||
build && download,
|
||||
build && artifacts,
|
||||
build && patches,
|
||||
build && options.patches
|
||||
].filter(x => x))
|
||||
return nexe(compiler).asCallback(callback)
|
||||
}
|
||||
|
||||
function isNexe (callback) {
|
||||
return Bluebird.resolve(Boolean(process.__nexe)).asCallback(callback)
|
||||
function isNexe () {
|
||||
return Boolean(process.__nexe)
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -47,6 +48,6 @@ export {
|
||||
if (process.__nexe) {
|
||||
compile(argv)
|
||||
.catch((e) => {
|
||||
error(e.stack, () => process.exit(e.exitCode || 1))
|
||||
logger.error(e.stack, () => process.exit(e.exitCode || 1))
|
||||
})
|
||||
}
|
||||
|
||||
+35
-26
@@ -1,7 +1,6 @@
|
||||
import parseArgv from 'minimist'
|
||||
import { basename, extname, join } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
import { resolveModule } from './util'
|
||||
import { EOL } from 'os'
|
||||
|
||||
function padRight (str, l) {
|
||||
@@ -13,19 +12,17 @@ const defaults = {
|
||||
flags: [],
|
||||
configure: [],
|
||||
make: [],
|
||||
targets: [],
|
||||
vcBuild: ['nosign', 'release'],
|
||||
quick: false,
|
||||
bundle: true,
|
||||
bundle: false,
|
||||
build: false,
|
||||
enableNodeCli: false,
|
||||
verbose: false,
|
||||
silent: false,
|
||||
padding: 2,
|
||||
patches: []
|
||||
}
|
||||
const alias = {
|
||||
i: 'input',
|
||||
o: 'output',
|
||||
t: 'temp',
|
||||
t: 'target',
|
||||
n: 'name',
|
||||
v: 'version',
|
||||
p: 'python',
|
||||
@@ -33,7 +30,6 @@ const alias = {
|
||||
c: 'configure',
|
||||
m: 'make',
|
||||
vc: 'vcBuild',
|
||||
q: 'quick',
|
||||
s: 'snapshot',
|
||||
b: 'bundle',
|
||||
cli: 'enableNodeCli',
|
||||
@@ -44,9 +40,9 @@ const argv = parseArgv(process.argv, { alias, default: defaults })
|
||||
const help = `
|
||||
nexe --help CLI OPTIONS
|
||||
|
||||
-i --input =./index.js -- application entry point
|
||||
-o --output =./nexe.exe -- path to output file
|
||||
-t --temp =./.nexe -- nexe temp directory (3Gb+) ~ NEXE_TEMP
|
||||
-i --input =index.js -- application entry point
|
||||
-o --output =nexe.exe -- path to output file
|
||||
-t --target =win32-x64-6.10.3 -- *target a prebuilt binary
|
||||
-n --name =nexe.js -- file name for error reporting at run time
|
||||
-v --version =${padRight(process.version.slice(1), 23)}-- node version
|
||||
-p --python =/path/to/python2 -- python executable
|
||||
@@ -55,7 +51,9 @@ nexe --help CLI OPTIONS
|
||||
-m --make ="--loglevel" -- *pass arguments to the make command
|
||||
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
|
||||
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
|
||||
-b --bundle -- attempt bundling application
|
||||
-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)
|
||||
--clean -- force download of sources
|
||||
@@ -64,11 +62,10 @@ nexe --help CLI OPTIONS
|
||||
--silent -- disable logging
|
||||
--verbose -- set logging to verbose
|
||||
|
||||
|
||||
-* variable key name * option can be used more than once
|
||||
|
||||
TODO -q --quick =win32-x64-X.X.X -- use prebuilt binary (url, key or path)
|
||||
TODO --resource-* =/path/to/resource -- *embed file bytes within the binary
|
||||
|
||||
|
||||
`.trim()
|
||||
|
||||
function flattenFilter (...args) {
|
||||
@@ -95,7 +92,7 @@ function extractCliMap (match, options) {
|
||||
function tryResolveMainFileName () {
|
||||
let filename = 'nexe'
|
||||
try {
|
||||
const file = resolveModule(process.cwd())
|
||||
const file = require.resolve(process.cwd())
|
||||
filename = basename(file).replace(extname(file), '')
|
||||
} catch (_) {}
|
||||
|
||||
@@ -106,23 +103,23 @@ function extractLogLevel (options) {
|
||||
if (options.loglevel) return options.loglevel
|
||||
if (options.silent) return 'silent'
|
||||
if (options.verbose) return 'verbose'
|
||||
if (options.info) return 'info'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function extractName (options) {
|
||||
if (typeof options.input === 'string') {
|
||||
return options.name ||
|
||||
basename(options.input).replace(extname(options.input), '')
|
||||
let name = options.name
|
||||
if (typeof options.input === 'string' && !name) {
|
||||
name = basename(options.input).replace(extname(options.input), '')
|
||||
}
|
||||
const mainName = tryResolveMainFileName()
|
||||
return options.name || mainName
|
||||
name = name || tryResolveMainFileName()
|
||||
return name.replace(/\.exe$/, '')
|
||||
}
|
||||
|
||||
function normalizeOptionsAsync (input) {
|
||||
if (argv.help || Boolean(argv._.find(x => x === 'version'))) {
|
||||
if (argv.help || Boolean(argv._.filter(x => x === 'version')[0])) {
|
||||
return Bluebird.fromCallback(cb => process.stderr.write(
|
||||
argv.help ? help : '2.0.0-beta.1' + EOL,
|
||||
() => cb(null, process.exit(1))
|
||||
argv.help ? help : '2.0.0-rc.1' + EOL,
|
||||
() => cb(null, process.exit(0))
|
||||
))
|
||||
}
|
||||
|
||||
@@ -131,10 +128,22 @@ function normalizeOptionsAsync (input) {
|
||||
options.loglevel = extractLogLevel(options)
|
||||
options.name = extractName(options)
|
||||
options.flags = flattenFilter(options.flag, options.flags)
|
||||
options.targets = flattenFilter(options.target, options.targets)
|
||||
options.make = flattenFilter(options.make)
|
||||
options.vcBuild = flattenFilter(options.vcBuild)
|
||||
options.resources = flattenFilter(options.resource, options.resources)
|
||||
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
|
||||
options.resources = options.resources || extractCliMap(/^resource-.*/, options)
|
||||
|
||||
if (options.build || options.padding === 0) {
|
||||
options.targets = []
|
||||
options.build = true
|
||||
} else if (!options.targets.length) {
|
||||
const defaultTarget = [process.platform, process.arch, options.version].join('-')
|
||||
options.targets = [defaultTarget]
|
||||
}
|
||||
|
||||
// TODO validate target(s)
|
||||
|
||||
Object.keys(alias)
|
||||
.filter(k => k !== 'rc')
|
||||
.forEach(x => delete options[x])
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Buffer } from 'buffer'
|
||||
|
||||
export default async function content (compiler, next) {
|
||||
await next()
|
||||
|
||||
const filename = 'lib/' + compiler.options.name + '.js'
|
||||
const file = await compiler.readFileAsync(filename)
|
||||
const header = '/' + '*'.repeat(19) + 'nexe_'
|
||||
const end = '_' + '*'.repeat(19) + '/'
|
||||
const padding = Array(compiler.options.padding * 1000000).fill('*').join('')
|
||||
|
||||
if (!padding) {
|
||||
file.contents = compiler.input
|
||||
return
|
||||
}
|
||||
|
||||
file.contents = [
|
||||
compiler.input,
|
||||
'/',
|
||||
padding,
|
||||
'/'
|
||||
].join('')
|
||||
|
||||
file.contents = header + Buffer.byteLength(file.contents) + end + file.contents
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import gyp from './gyp'
|
||||
import content from './content'
|
||||
import main from './third-party-main'
|
||||
import nexePatches from './third-party-main'
|
||||
import cli from './disable-node-cli'
|
||||
import flags from './flags'
|
||||
import ico from './ico'
|
||||
@@ -8,8 +7,8 @@ import rc from './node-rc'
|
||||
|
||||
const patches = [
|
||||
gyp,
|
||||
content,
|
||||
main,
|
||||
(compiler, next) => compiler.setMainModule(compiler, next),
|
||||
nexePatches,
|
||||
cli,
|
||||
flags,
|
||||
ico,
|
||||
|
||||
@@ -1,22 +1,57 @@
|
||||
export default async function main (compiler, next) {
|
||||
const mainFile = await compiler.readFileAsync('lib/_third_party_main.js')
|
||||
mainFile.contents = `
|
||||
Object.defineProperty(process, '__nexe', {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: false
|
||||
});
|
||||
Object.defineProperty(process, '__nexe', (function () {
|
||||
let nexeHeader = null
|
||||
return {
|
||||
get: function () {
|
||||
return nexeHeader
|
||||
},
|
||||
set: function (value) {
|
||||
if (nexeHeader) {
|
||||
throw new Error('__nexe cannot be reconfigured')
|
||||
}
|
||||
nexeHeader = value
|
||||
Object.freeze(nexeHeader)
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
}
|
||||
})());
|
||||
|
||||
const fs = require('fs')
|
||||
const originalReadFile = fs.readFile
|
||||
const Buffer = require('buffer').Buffer
|
||||
const isString = x => typeof x === 'string' || x instanceof String
|
||||
|
||||
fs.readFile = function readFile (file, options, callback) {
|
||||
const manifest = process.__nexe
|
||||
const entry = manifest && manifest.resources[file]
|
||||
if (!manifest || !entry || !isString(file)) {
|
||||
return originalReadFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = +manifest.binaryOffset + +manifest.resourceOffset + offset
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
fs.open(process.execPath, 'r', function (err, fd) {
|
||||
if (err) return callback(err, null)
|
||||
fs.read(fd, Buffer.alloc(length), 0, length, binaryOffset, function (error, bytesRead, buffer) {
|
||||
if (error) {
|
||||
return fs.close(fd, function () {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
fs.close(fd, function (err) {
|
||||
if (err) {
|
||||
return callback(err, buffer)
|
||||
}
|
||||
callback(err, Buffer.from(buffer.toString(), 'base64'))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
require("${compiler.options.name}");
|
||||
`.trim()
|
||||
|
||||
if (compiler.options.empty === true) {
|
||||
compiler.options.resources.length = 0
|
||||
//eslint-disable-next-line
|
||||
compiler.input = 'console.log(`nexe: ${process.platform}-${process.arch}-${process.version.slice(1)}`)'
|
||||
return next()
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { each, fromCallback } from 'bluebird'
|
||||
import { readFileAsync } from './util'
|
||||
import { Buffer } from 'buffer'
|
||||
import globs from 'globby'
|
||||
import { stat } from 'fs'
|
||||
|
||||
async function isDirectoryAsync (path) {
|
||||
const stats = fromCallback(cb => stat(path, cb))
|
||||
return stats.isDirectory()
|
||||
}
|
||||
|
||||
export default async function resource (compiler, next) {
|
||||
const resources = compiler.resources = {
|
||||
index: {},
|
||||
bundle: ''
|
||||
}
|
||||
|
||||
if (!compiler.options.resources.length) {
|
||||
return next()
|
||||
}
|
||||
|
||||
await each(globs(compiler.options.resources), async (file) => {
|
||||
if (await isDirectoryAsync(file)) {
|
||||
return
|
||||
}
|
||||
const contents = await readFileAsync(file)
|
||||
const encodedContents = contents.toString('base64')
|
||||
resources.index[file] = [
|
||||
Buffer.byteLength(resources.bundle),
|
||||
Buffer.byteLength(encodedContents)
|
||||
]
|
||||
resources.bundle = resources.bundle + encodedContents
|
||||
})
|
||||
}
|
||||
+27
-12
@@ -1,16 +1,8 @@
|
||||
import { readFile, writeFile } from 'fs'
|
||||
import { readFile, writeFile, stat } from 'fs'
|
||||
import Bluebird from 'bluebird'
|
||||
import module from 'module'
|
||||
|
||||
function resolveModule (path) {
|
||||
const filename = module._resolveFilename(path, module)
|
||||
console.log('resolving module ', path)
|
||||
return filename
|
||||
}
|
||||
|
||||
function dequote (input) {
|
||||
input = input.trim()
|
||||
|
||||
const singleQuote = input.startsWith('\'') && input.endsWith('\'')
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"')
|
||||
if (singleQuote || doubleQuote) {
|
||||
@@ -19,12 +11,35 @@ function dequote (input) {
|
||||
return input
|
||||
}
|
||||
|
||||
function readStreamAsync (stream) {
|
||||
return new Bluebird((resolve) => {
|
||||
let input = ''
|
||||
stream.setEncoding('utf-8')
|
||||
stream.on('data', x => { input += x })
|
||||
stream.once('end', () => resolve(dequote(input)))
|
||||
stream.resume && stream.resume()
|
||||
})
|
||||
}
|
||||
|
||||
const readFileAsync = Bluebird.promisify(readFile)
|
||||
const writeFileAsync = Bluebird.promisify(writeFile)
|
||||
const statAsync = Bluebird.promisify(stat)
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function fileExistsAsync (path) {
|
||||
return statAsync(path).then(x => true, err => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
readFileAsync,
|
||||
writeFileAsync,
|
||||
dequote,
|
||||
resolveModule
|
||||
isWindows,
|
||||
readFileAsync,
|
||||
fileExistsAsync,
|
||||
readStreamAsync,
|
||||
writeFileAsync
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user