Compare commits

...

7 Commits

Author SHA1 Message Date
calebboyd 75ae41b7b9 chore: bump version 2019-04-04 09:48:17 -05:00
calebboyd 3692c142fb doc: describe mangle option 2019-04-04 09:47:20 -05:00
calebboyd af2e381ed4 feat: ensure output directory exists
closes #590
2019-04-04 09:35:15 -05:00
jerry_liu 44393a22da More flexiable nexe footer search range to fix digital sign binary launch issue
Fix #372
2019-04-03 13:37:31 -05:00
jerry_liu 57a08d2cfd remove compress option 2019-04-03 08:15:21 -05:00
calebboyd 6825cef609 feat: asset option 2019-04-03 08:12:26 -05:00
calebboyd b3c2d2e764 fix: use hardcoded version for asset url 2019-04-01 16:38:47 -05:00
10 changed files with 105 additions and 86 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"recommendations": [ "eg2.tslint" ] "recommendations": [ "ms-vscode.vscode-typescript-tslint-plugin" ]
} }
+7
View File
@@ -113,8 +113,15 @@ compile({
- #### `cwd: string` - #### `cwd: string`
- Directory nexe will operate on as though it is the cwd - Directory nexe will operate on as though it is the cwd
- default: process.cwd() - default: process.cwd()
- #### `mangle: boolean`
- If set to false, nexe will not include the virtual filesystem (your application and resources) on the output.
- This will cause the output to error as an "Invalid Binary" unless a userland patch alters the contents of lib/_third_party_main.js in the nodejs source.
- default: true
- #### `build: boolean` - #### `build: boolean`
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean) - Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
- #### `asset: string`
- Provide a pre-built nexe binary asset, this can either be an http or https URL or a file path.
- default: `null`
- #### `python: string` - #### `python: string`
- On Linux this is the path pointing to your python2 executable - On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed - On Windows this is the directory where `python` can be accessed
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "nexe", "name": "nexe",
"version": "3.0.0", "version": "3.0.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+1 -1
View File
@@ -2,7 +2,7 @@
"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": "3.0.0", "version": "3.0.2",
"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>",
+16 -7
View File
@@ -1,4 +1,4 @@
import { delimiter, dirname, normalize, join } from 'path' import { delimiter, resolve, normalize, join } from 'path'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import { createReadStream, ReadStream } from 'fs' import { createReadStream, ReadStream } from 'fs'
import { spawn } from 'child_process' import { spawn } from 'child_process'
@@ -15,9 +15,6 @@ import {
} from './util' } from './util'
import { NexeOptions, version } from './options' import { NexeOptions, version } from './options'
import { NexeTarget } from './target' import { NexeTarget } from './target'
import download = require('download')
import { getLatestGitRelease } from './releases'
import { IncomingMessage } from 'http'
import combineStreams = require('multistream') import combineStreams = require('multistream')
import { Bundle, toStream } from './fs/bundle' import { Bundle, toStream } from './fs/bundle'
@@ -101,11 +98,22 @@ export class NexeCompiler {
* The file path of node binary * The file path of node binary
*/ */
public nodeSrcBinPath: string public nodeSrcBinPath: string
/**
* Remote asset path if available
*/
public remoteAsset: string
constructor(public options: NexeOptions) { constructor(public options: NexeOptions) {
const { python } = (this.options = options) const { python } = (this.options = options)
//SOMEDAY iterate over multiple targets with `--outDir`
this.targets = options.targets as NexeTarget[] this.targets = options.targets as NexeTarget[]
this.target = this.targets[0] this.target = this.targets[0]
if (options.asset && options.asset.startsWith('http')) {
this.remoteAsset = options.asset
} else {
this.remoteAsset =
'https://github.com/nexe/nexe/releases/download/v3.0.0/' + this.target.toString()
}
this.src = join(this.options.temp, this.target.version) this.src = join(this.options.temp, this.target.version)
this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '') this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '')
this.nodeSrcBinPath = isWindows this.nodeSrcBinPath = isWindows
@@ -190,6 +198,9 @@ export class NexeCompiler {
} }
public getNodeExecutableLocation(target?: NexeTarget) { public getNodeExecutableLocation(target?: NexeTarget) {
if (this.options.asset && !this.options.asset.startsWith('http')) {
return resolve(this.options.cwd, this.options.asset)
}
if (target) { if (target) {
return join(this.options.temp, target.toString()) return join(this.options.temp, target.toString())
} }
@@ -251,7 +262,7 @@ export class NexeCompiler {
binary: NodeJS.ReadableStream | null, binary: NodeJS.ReadableStream | null,
location: string | undefined location: string | undefined
) { ) {
//TODO combine make/configure/vcBuild/and modified times of included files //SOMEDAY combine make/configure/vcBuild/and modified times of included files
const { snapshot, build } = this.options const { snapshot, build } = this.options
if (!binary) { if (!binary) {
@@ -261,8 +272,6 @@ export class NexeCompiler {
if (build && snapshot != null && (await pathExistsAsync(snapshot))) { if (build && snapshot != null && (await pathExistsAsync(snapshot))) {
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs const snapshotLastModified = (await statAsync(snapshot)).mtimeMs
const binaryLastModified = (await statAsync(location)).mtimeMs const binaryLastModified = (await statAsync(location)).mtimeMs
// if build was requested and there's a snapshot to embed in the binary,
// we need to rebuild if the snapshot was just modified.
return snapshotLastModified > binaryLastModified return snapshotLastModified > binaryLastModified
} }
+20 -19
View File
@@ -21,6 +21,7 @@ export interface NexeOptions {
output: string output: string
targets: (string | NexeTarget)[] targets: (string | NexeTarget)[]
name: string name: string
asset: string
cwd: string cwd: string
flags: string[] flags: string[]
configure: string[] configure: string[]
@@ -35,11 +36,6 @@ export interface NexeOptions {
patches: (string | NexePatch)[] patches: (string | NexePatch)[]
plugins: (string | NexePatch)[] plugins: (string | NexePatch)[]
native: any native: any
/**
* @deprecated
* Use mangle: false instead
*/
empty: boolean
mangle: boolean mangle: boolean
ghToken: string ghToken: string
sourceUrl?: string sourceUrl?: string
@@ -53,7 +49,6 @@ export interface NexeOptions {
ico?: string ico?: string
debugBundle?: boolean debugBundle?: boolean
warmup?: string warmup?: string
compress?: boolean
clean?: boolean clean?: boolean
/** /**
* Api Only * Api Only
@@ -65,11 +60,11 @@ const defaults = {
flags: [], flags: [],
cwd: process.cwd(), cwd: process.cwd(),
configure: [], configure: [],
mangle: true,
make: [], make: [],
targets: [], targets: [],
vcBuild: isWindows ? ['nosign', 'release'] : [], vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: false, enableNodeCli: false,
compress: false,
build: false, build: false,
bundle: true, bundle: true,
patches: [], patches: [],
@@ -104,6 +99,7 @@ ${c.bold('nexe <entry-file> [options]')}
-t --target -- node version description -t --target -- node version description
-n --name -- main app module name -n --name -- main app module name
-r --resource -- *embed files (glob) within the binary -r --resource -- *embed files (glob) within the binary
-a --asset -- alternate asset path, file or url pointing to a base (nexe) binary
--plugin -- extend nexe runtime behavior --plugin -- extend nexe runtime behavior
${c.underline.bold('Building from source:')} ${c.underline.bold('Building from source:')}
@@ -113,7 +109,8 @@ ${c.bold('nexe <entry-file> [options]')}
-f --flag -- *v8 flags to include during compilation -f --flag -- *v8 flags to include during compilation
-c --configure -- *arguments to the configure step -c --configure -- *arguments to the configure step
-m --make -- *arguments to the make/build step -m --make -- *arguments to the make/build step
--no-mangle -- used when generating base binaries, or when pathing _third_party_main manually. --patch -- module with middelware default export for adding a build patch
--no-mangle -- used when generating base binaries, or when patching _third_party_main manually.
--snapshot -- path to a warmup snapshot --snapshot -- path to a warmup snapshot
--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)
@@ -268,6 +265,21 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
: `${options.output || options.name}` : `${options.output || options.name}`
options.output = resolve(cwd, options.output) options.output = resolve(cwd, options.output)
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.mangle = 'mangle' in opts ? opts.mangle : true
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
if ((!options.mangle && !options.bundle) || options.patches.length) {
options.build = true
}
if (options.build) { if (options.build) {
const { arch } = options.targets[0] as NexeTarget const { arch } = options.targets[0] as NexeTarget
if (isWindows) { if (isWindows) {
@@ -277,17 +289,6 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
} }
} }
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.mangle = 'mangle' in opts ? opts.mangle : !opts.empty
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
Object.keys(alias) Object.keys(alias)
.filter(k => k !== 'rc') .filter(k => k !== 'rc')
.forEach(x => delete opts[x]) .forEach(x => delete opts[x])
+8 -5
View File
@@ -1,17 +1,20 @@
const fs = require('fs'), const fs = require('fs'),
fd = fs.openSync(process.execPath, 'r'), fd = fs.openSync(process.execPath, 'r'),
stat = fs.statSync(process.execPath), stat = fs.statSync(process.execPath),
footer = Buffer.alloc(32, 0) tailSize = Math.min(stat.size, 16000),
tailWindow = Buffer.from(Array(tailSize))
fs.readSync(fd, footer, 0, 32, stat.size - 32) fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) { const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
if (footerPosition == -1) {
throw 'Invalid Nexe binary' throw 'Invalid Nexe binary'
} }
const contentSize = footer.readDoubleLE(16), const footer = tailWindow.slice(footerPosition, footerPosition + 32),
contentSize = footer.readDoubleLE(16),
resourceSize = footer.readDoubleLE(24), resourceSize = footer.readDoubleLE(24),
contentStart = stat.size - 32 - resourceSize - contentSize, contentStart = stat.size - tailSize + footerPosition - resourceSize - contentSize,
resourceStart = contentStart + contentSize resourceStart = contentStart + contentSize
Object.defineProperty( Object.defineProperty(
+20 -11
View File
@@ -1,8 +1,9 @@
import { normalize, relative } from 'path' import { dirname, normalize, relative } from 'path'
import { createWriteStream, chmodSync, statSync } from 'fs' import { createWriteStream, chmodSync, statSync } from 'fs'
import { NexeCompiler } from '../compiler' import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target' import { NexeTarget } from '../target'
import { STDIN_FLAG } from '../util' import { STDIN_FLAG } from '../util'
import mkdirp = require('mkdirp')
/** /**
* The "cli" step detects the appropriate input. If no input options are passed, * The "cli" step detects the appropriate input. If no input options are passed,
@@ -16,28 +17,36 @@ import { STDIN_FLAG } from '../util'
*/ */
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) { export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
await next() await next()
const { log } = compiler const { log } = compiler,
const target = compiler.options.targets.shift() as NexeTarget target = compiler.options.targets.shift() as NexeTarget,
const deliverable = await compiler.compileAsync(target) deliverable = await compiler.compileAsync(target),
output = normalize(compiler.output!)
mkdirp.sync(dirname(output))
return new Promise((resolve, reject) => { 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(output))
.on('error', reject) .on('error', reject)
.once('close', (e: Error) => { .once('close', (e: Error) => {
if (e) { if (e) {
reject(e) reject(e)
} else if (compiler.output) { } else if (compiler.output) {
const output = compiler.output const output = compiler.output,
const mode = statSync(output).mode | 0o111 mode = statSync(output).mode | 0o111,
inputFileLogOutput = relative(process.cwd(), compiler.options.input),
outputFileLogOutput = relative(process.cwd(), output)
chmodSync(output, mode.toString(8).slice(-3)) chmodSync(output, mode.toString(8).slice(-3))
const inputFile = relative(process.cwd(), compiler.options.input)
const outputFile = relative(process.cwd(), output)
step.log( step.log(
`Entry: '${ `Entry: '${
compiler.stdinUsed ? (compiler.options.mangle ? STDIN_FLAG : '[none]') : inputFile compiler.stdinUsed
}' written to: ${outputFile}` ? compiler.options.mangle
? STDIN_FLAG
: '[none]'
: inputFileLogOutput
}' written to: ${outputFileLogOutput}`
) )
resolve(compiler.quit()) resolve(compiler.quit())
} }
+26 -36
View File
@@ -3,7 +3,6 @@ import { pathExistsAsync } from '../util'
import { LogStep } from '../logger' import { LogStep } from '../logger'
import { IncomingMessage } from 'http' import { IncomingMessage } from 'http'
import { NexeCompiler, NexeError } from '../compiler' import { NexeCompiler, NexeError } from '../compiler'
import { getLatestGitRelease } from '../releases'
import { dirname } from 'path' import { dirname } from 'path'
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) { function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
@@ -24,37 +23,28 @@ function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options
} }
async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) { async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
let downloadOptions = compiler.options.downloadOptions const { target, remoteAsset } = compiler,
const target = compiler.target filename = compiler.getNodeExecutableLocation(target)
if (compiler.options.ghToken) { try {
downloadOptions = Object.assign({}, downloadOptions) await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on(
downloadOptions.headers = Object.assign({}, downloadOptions.headers, { 'response',
Authorization: 'token ' + compiler.options.ghToken (res: IncomingMessage) => {
}) const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
})
}
)
} catch (e) {
if (e.statusCode === 404) {
throw new NexeError(`${remoteAsset} is not available, create it using the --build flag`)
} else {
throw new NexeError('Error downloading prebuilt binary: ' + e)
}
} }
const githubRelease = await getLatestGitRelease(downloadOptions)
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
if (!asset) {
throw new NexeError(`${assetName} not available, create it using the --build flag`)
}
const filename = compiler.getNodeExecutableLocation(target)
await download(
asset.browser_download_url,
dirname(filename),
compiler.options.downloadOptions
).on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
})
})
} }
/** /**
@@ -66,12 +56,12 @@ export default async function downloadNode(compiler: NexeCompiler, next: () => P
const { src, log, target } = compiler, const { src, log, target } = compiler,
{ version } = target, { version } = target,
{ sourceUrl, downloadOptions, build } = compiler.options, { sourceUrl, downloadOptions, build } = compiler.options,
url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz` url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`,
const step = log.step( step = log.step(
`Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}` `Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}`
) ),
const exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target) exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target),
const downloadExists = await pathExistsAsync(build ? src : exeLocation) downloadExists = await pathExistsAsync(build ? src : exeLocation)
if (downloadExists) { if (downloadExists) {
step.log('Already downloaded...') step.log('Already downloaded...')
+4 -4
View File
@@ -9,7 +9,7 @@ function alpine(target: NexeTarget) {
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4 FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
ENV NODE_VERSION=${target.version} ENV NODE_VERSION=${target.version}
ENV NEXE_VERSION=beta ENV NEXE_VERSION=latest
WORKDIR / WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \ RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
@@ -21,18 +21,18 @@ RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.t
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \ RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
npm install -g nexe@\${NEXE_VERSION} && \ npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out --enableStdIn=false nexe --build --no-mangle --temp /nexe_temp -c="--fully-static" -o out --enableStdIn=false
`.trim() `.trim()
} }
function arm(target: NexeTarget) { function arm(target: NexeTarget) {
return ` return `
FROM hypriot/rpi-node FROM hypriot/rpi-node
ENV NEXE_VERSION=beta ENV NEXE_VERSION=latest
WORKDIR / WORKDIR /
RUN yarn global add nexe@\${NEXE_VERSION} && \ RUN yarn global add nexe@\${NEXE_VERSION} && \
nexe --build --empty -o out -t ${target.version} nexe --build --no-mangle -o out -t ${target.version}
`.trim() `.trim()
} }