feat: initial build

This commit is contained in:
calebboyd
2017-08-28 00:06:46 -05:00
parent 5df6840972
commit c6d057ac43
26 changed files with 1355 additions and 350 deletions
+2 -1
View File
@@ -4,5 +4,6 @@ end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace=true
#https://github.com/Microsoft/vscode/issues/23983#event-1087502108
#trim_trailing_whitespace = true
insert_final_newline = true
+13 -1
View File
@@ -2,5 +2,17 @@
"typescript.tsdk": "./node_modules/typescript/lib",
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.typescriptEnable": ["typescript"]
"prettier.typescriptEnable": ["typescript"],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"search.exclude": {
"lib/**/*": true,
"**/node_modules": true,
"**/bower_components": true
}
}
+3 -3
View File
@@ -5,9 +5,9 @@
Nexe 2.0 is a rewrite to enable some new features. These include:
* Quick Builds!
* Userland build patches
* New Resource access
* stdin interfaces
* Opt-in bundling
* Resource storage/access rewrite
* stdin interface
* Optional, pluggable bundling
### Breaking Changes
* New options -- Please see the [readme](README.md#options)
+4 -5
View File
@@ -112,11 +112,10 @@ nexe.compile({
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: Array<string>`
- Array of arguments for the node build make step
- default: `[]`
- #### `vcBuild: Array<string>`
- Array of arguments for the node build step on windows
- default: `['nosign', 'release']`
- Array of arguments for the node build make step, on windows this step recieves optiosn for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- #### `make: Array<string>`
- Alias for `make` option
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
+9 -13
View File
@@ -1,25 +1,21 @@
environment:
node_version: "6"
CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: IwsFkKeSwogeIs7gbEhpIkRbrziWdGq0eCSMkAVCgBY=
GITHUB_TOKEN:
secure: Sx2cy8b/djm/3wSYMSKK0fxbZ0mnFI09rLCHXqjjzwhunfEkug7xILEl14wVrHUS
branches:
except:
- master
install:
- ps: Install-Product node $env:node_version
- ps: Install-Product node 6
- npm i
build: off
test: off
before_build:
- npm run log-node-version
build_script:
- npm run task-build-windows
- npm run nexe-build
artifacts:
- path: .nexe\6.11.2\Release\node.exe
- path: .nexe\**\node.exe
cache:
- node_modules
+829 -106
View File
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -9,8 +9,7 @@
"Caleb Boyd <caleb.boyd@hotmail.com>"
],
"scripts": {
"task-build": "ts-node tasks/build",
"log-node-version": "node -e \"console.log([process.platform, process.arch, process.version.slice(1)].join('-'))\"",
"nexe-build": "ts-node tasks/build",
"prebuild": "rimraf lib && npm run lint",
"prepublish": "npm run build",
"lint": "prettier --parser typescript --no-semi --print-width 100 --single-quote --write \"src/**/*.ts\"",
@@ -33,18 +32,16 @@
},
"dependencies": {
"app-builder": "^5.1.0",
"bluebird": "^3.5.0",
"chalk": "^1.1.3",
"download": "^6.2.0",
"fuse-box": "^2.2.1",
"globby": "^6.1.0",
"memory-fs": "^0.4.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nigel": "^2.0.2",
"ora": "^1.2.0",
"pifi": "1.0.0",
"rimraf": "^2.6.1",
"tar": "^3.1.3"
"uglify-js": "3.0.28"
},
"devDependencies": {
"@types/bluebird": "^3.5.8",
@@ -53,7 +50,9 @@
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.3.29",
"@types/ora": "^0.3.31",
"@types/pify": "0.0.28",
"@types/rimraf": "0.0.28",
"got": "^7.1.0",
"mocha": "^3.2.0",
"prettier": "^1.4.4",
"ts-node": "3.3.0",
-62
View File
@@ -1,62 +0,0 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from './util'
import { promisify, map } from 'bluebird'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from './compiler'
const mkdirpAsync = promisify(mkdirp)
const unlinkAsync = (promisify(unlink) as any) as (path: string) => PromiseLike<void>
const readdirAsync = promisify(readdir)
function readDirAsync(dir: string): PromiseLike<string[]> {
return readdirAsync(dir)
.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then((x: boolean) => (x ? readDirAsync(path) : path as any))
})
.reduce((a: string[], b: string[] | string) => a.concat(b), [])
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await map(tmpFiles, async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
await next()
await map(tmpFiles, x => unlinkAsync(x))
return map(compiler.files, async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
}
+18 -6
View File
@@ -1,9 +1,18 @@
import { NexeCompiler } from '../compiler'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, SourceMapPlainJsPlugin } from 'fuse-box'
import { FuseBox, JSONPlugin, CSSPlugin, HTMLPlugin, QuantumPlugin } from 'fuse-box'
//import NativeModulePlugin from './fuse-native-module-plugin'
function bundleProducer(filename: string, name: string) {
console.error(process.cwd())
function bundleProducer(filename: string, options: { name: string; minify: any }) {
const plugins: any = [JSONPlugin(), CSSPlugin(), HTMLPlugin()]
if (options.minify) {
plugins.push(
QuantumPlugin({
target: 'server',
uglify: true,
bakeApiIntoBundle: options.name
})
)
}
const fuse = FuseBox.init({
cache: false,
log: Boolean(process.env.NEXE_BUNDLE_DEBUG) || false,
@@ -12,9 +21,9 @@ function bundleProducer(filename: string, name: string) {
writeBundles: false,
output: '$name.js',
target: 'server',
plugins: [JSONPlugin(), CSSPlugin(), HTMLPlugin(), SourceMapPlainJsPlugin()]
plugins
})
fuse.bundle(name).instructions(`> ${filename}`)
fuse.bundle(options.name).instructions(`> ${filename}`)
return fuse.run().then(x => {
let output = ''
x.bundles.forEach(y => (output = y.context.output.lastPrimaryOutput.content!.toString()))
@@ -32,6 +41,9 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
producer = require(compiler.options.bundle).bundleProducer
}
compiler.input = await producer(compiler.options.input, compiler.options.name)
compiler.input = await producer(compiler.options.input, {
name: compiler.options.name,
minify: compiler.options.compress
})
return next()
}
+14 -15
View File
@@ -1,14 +1,13 @@
import { normalize, join } from 'path'
import * as Bluebird from 'bluebird'
import { Buffer } from 'buffer'
import { createHash } from 'crypto'
import { createReadStream } from 'fs'
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 { NexeOptions } from './options'
import { NexeOptions, nexeVersion } from './options'
import { NexeTarget } from './target'
const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
@@ -51,7 +50,7 @@ export class NexeCompiler {
constructor(public options: NexeOptions) {
const { python } = (this.options = options)
this.log.step('nexe ' + nexeVersion, 'info')
if (python) {
if (isWindows) {
this.env.PATH = '"' + dequote(normalize(python)) + '";' + this.env.PATH
@@ -67,7 +66,10 @@ export class NexeCompiler {
cachedFile = {
absPath,
filename: file,
contents: await readFileAsync(absPath, 'utf-8').catch({ code: 'ENOENT' }, () => '')
contents: await readFileAsync(absPath, 'utf-8').catch(x => {
if (x.code === 'ENOENT') return ''
throw x
})
}
this.files.push(cachedFile)
}
@@ -84,21 +86,21 @@ export class NexeCompiler {
}
}
quit(code = 0) {
quit() {
const time = Date.now() - this.start
this.log.write(`Finsihed in ${time / 1000}s`)
return this.log.flush().then(x => process.exit(code))
return this.log.flush()
}
private _getNodeExecutableLocation(target?: string | null) {
private _getNodeExecutableLocation(target?: NexeTarget) {
if (target) {
return join(this.options.temp, target)
return join(this.options.temp, target.toString())
}
return this.nodeSrcBinPath
}
private _runBuildCommandAsync(command: string, args: string[]) {
return new Bluebird((resolve, reject) => {
return new Promise((resolve, reject) => {
spawn(command, args, {
cwd: this.src,
env: this.env,
@@ -123,7 +125,7 @@ export class NexeCompiler {
: '...'}`
)
await this._configureAsync()
const buildOptions = isWindows ? this.options.vcBuild : this.options.make
const buildOptions = this.options.make
this.compileStep.log(
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
)
@@ -152,16 +154,13 @@ export class NexeCompiler {
return `process.__nexe=${JSON.stringify(header)};`
}
async compileAsync() {
async compileAsync(target: NexeTarget) {
const step = (this.compileStep = this.log.step('Compiling result'))
let target = this.options.targets.slice().shift()
const location = this._getNodeExecutableLocation(target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
const header = this._generateHeader()
step.log(`Scanning existing binary...`)
if (target && !binary) {
//throw new Error('\nNot Implemented, use --build during beta\n')
binary = await this._fetchPrebuiltBinaryAsync()
}
+5 -2
View File
@@ -46,14 +46,17 @@ export class Logger {
this.ora.color = color
}
step(text: string): LogStep {
step(text: string, method: string = 'succeed'): LogStep {
if (this.silent) {
return { modify() {}, log() {} }
}
if (!this.ora.id) {
this.ora.start().text = text
if (method !== 'succeed') {
this.ora[method]()
}
} else {
this.ora.succeed().text = text
this.ora[method]().text = text
this.ora.start()
}
+19 -11
View File
@@ -1,18 +1,18 @@
import { compose, PromiseConfig, Middleware } from 'app-builder'
import resource from './resource'
import resource from './steps/resource'
import { NexeCompiler } from './compiler'
import { argv, normalizeOptionsAsync, NexeOptions } from './options'
import cli from './cli'
import { argv, normalizeOptionsAsync, NexeOptions, NexePatch } from './options'
import cli from './steps/cli'
import bundle from './bundling/fuse'
import download from './download'
import artifacts from './artifacts'
import download from './steps/download'
import artifacts from './steps/artifacts'
import patches from './patches'
import { rimrafAsync } from './util'
import * as Bluebird from 'bluebird'
PromiseConfig.constructor = Bluebird
async function compile(compilerOptions: NexeOptions, callback?: (err: Error | null) => void) {
async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = await normalizeOptionsAsync(compilerOptions)
const compiler = new NexeCompiler(options)
const build = compiler.options.build
@@ -25,9 +25,17 @@ async function compile(compilerOptions: NexeOptions, callback?: (err: Error | nu
return compiler.quit()
}
const buildSteps = build ? [download, artifacts, ...patches, ...options.patches] : []
const buildSteps = build
? [download, artifacts, ...patches, ...(options.patches as NexePatch[])]
: []
const nexe = compose(resource, bundle, cli, buildSteps)
return nexe(compiler).asCallback(callback)
return callback
? void nexe(compiler).then(() => callback && callback(null)).catch((e: Error) => {
if (callback) {
callback(e)
} else throw e
})
: nexe(compiler)
}
export { argv, compile }
+39 -57
View File
@@ -1,72 +1,49 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { basename, extname, join } from 'path'
import * as Bluebird from 'bluebird'
import { isWindows } from './util'
import { basename, extname, join, isAbsolute } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL } from 'os'
export const nexeVersion = '2.0.0-rc.1'
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
*/
compress: boolean
targets: (string | NexeTarget)[]
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[]
make: 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
sourceMaps?: boolean
rc: { [key: string]: string }
/**
* Causes nexe to remove all temporary files for current configuration
*/
clean: boolean
enableNodeCli: boolean
sourceUrl?: string
bundle: boolean | string
patches: (string | NexePatch)[]
empty: boolean
sourceUrl?: string
python?: string
loglevel: 'info' | 'silent' | 'verbose'
silent?: boolean
verbose?: boolean
info?: boolean
patches: NexePatch[]
empty: boolean
ico?: string
warmup?: string
downloadOptions?: any
clean?: boolean
/**
* Api Only
*/
downloadOptions: any
}
function padRight(str: string, l: number) {
@@ -80,10 +57,11 @@ const defaults = {
configure: [],
make: [],
targets: [],
vcBuild: ['nosign', 'release', process.arch],
vcBuild: isWindows ? ['nosign', 'release', process.arch] : [],
enableNodeCli: false,
compress: false,
build: false,
bundle: true,
build: true,
patches: []
}
const alias = {
@@ -98,9 +76,7 @@ const alias = {
f: 'flag',
c: 'configure',
m: 'make',
vc: 'vcBuild',
s: 'snapshot',
cli: 'enableNodeCli',
h: 'help',
l: 'loglevel'
}
@@ -117,9 +93,8 @@ nexe --help CLI OPTIONS
-v --version =${padRight(process.version.slice(1), 23)}-- node version
-p --python =/path/to/python2 -- python executable
-f --flag ="--expose-gc" -- *v8 flags to include during compilation
-c --configure ="--with-dtrace" -- *pass arguments to the configure command
-m --make ="--loglevel" -- *pass arguments to the make command
-vc --vcBuild =x64 -- *pass arguments to vcbuild.bat
-c --configure ="--with-dtrace" -- *pass arguments to the configure step
-m --make ="--loglevel" -- *pass arguments to the make/build step
-s --snapshot =/path/to/snapshot -- build with warmup snapshot
-r --resource =./paths/**/* -- *embed file bytes within the binary
--bundle =./path/to/config -- pass a module path that exports nexeBundle
@@ -182,20 +157,20 @@ function extractName(options: NexeOptions) {
return name.replace(/\.exe$/, '')
}
function normalizeOptionsAsync(input: Partial<NexeOptions>) {
if (argv.help || argv._.some((x: string) => x === 'version')) {
process.stderr.write(argv.help ? help : '2.0.0-rc.1' + EOL, () => process.exit(0))
function normalizeOptionsAsync(input?: Partial<NexeOptions>): Promise<NexeOptions | never> {
if (argv.help || argv._.some((x: string) => x === 'version') || argv.version === true) {
return new Promise(() => {
process.stderr.write(argv.help ? help : nexeVersion + EOL, () => process.exit(0))
})
}
const options = Object.assign({}, defaults, input) as NexeOptions
const opts = options as any
delete opts._
options.loglevel = extractLogLevel(options)
options.name = extractName(options)
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.make = flattenFilter(options.vcBuild, options.make)
options.configure = flattenFilter(options.configure)
options.resources = flattenFilter(opts.resource, options.resources)
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
@@ -204,10 +179,17 @@ function normalizeOptionsAsync(input: Partial<NexeOptions>) {
options.targets = []
options.build = true
} else if (!options.targets.length) {
const defaultTarget = [process.platform, process.arch, options.version].join('-')
options.targets = [defaultTarget]
options.targets = [getTarget()]
}
options.targets = options.targets.map(getTarget)
options.patches = options.patches.map(x => {
if (typeof x === 'string') {
return require(x).default
}
return x
})
Object.keys(alias).filter(k => k !== 'rc').forEach(x => delete opts[x])
return Promise.resolve(options)
+1 -7
View File
@@ -3,11 +3,6 @@ import { readFileSync } from 'fs'
import { join } from 'path'
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
let sourceMapSupport = ''
if (true /*compiler.options.sourceMaps*/) {
sourceMapSupport = readFileSync(join(__dirname, '../../source-map-support.js')).toString()
}
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
`
@@ -21,7 +16,7 @@ const footer = Buffer.from(Array(32))
fs.readSync(fd, footer, 0, 32, size - 32)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
throw new Error('Invalid Nexe binary')
throw 'Invalid Nexe binary'
}
const contentSize = footer.readDoubleLE(16)
@@ -102,7 +97,6 @@ if (resourceSize) {
const contentBuffer = Buffer.from(Array(contentSize));
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
fs.closeSync(fd);
${sourceMapSupport}
const Module = require('module');
process.mainModule = new Module(process.execPath, null);
process.mainModule.loaded = true;
+70
View File
@@ -0,0 +1,70 @@
import { join, dirname } from 'path'
import { readdir, unlink } from 'fs'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
import * as mkdirp from 'mkdirp'
import { NexeCompiler } from '../compiler'
import pify = require('pify')
const mkdirpAsync = pify(mkdirp)
const unlinkAsync = pify(unlink)
const readdirAsync = pify(readdir)
function readDirAsync(dir: string): Promise<string[]> {
return readdirAsync(dir).then(paths => {
return Promise.all(
paths.map((file: string) => {
const path = join(dir, file)
return isDirectoryAsync(path).then(x => (x ? readDirAsync(path) : path as any))
})
).then(result => {
return [].concat(...(result as any))
})
})
}
function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
return ''
}
throw e
})
}
/**
* The artifacts step is where source patches are committed, or written as "artifacts"
* Steps:
* - A temporary directory is created in the downloaded source
* - On start, any files in that directory are restored into the source tree
* - After the patch functions have run, the temporary directory is emptied
* - Original versions of sources to be patched are written to the temporary directory
* - Finally, The patched files are written into source.
*
*/
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler
const temp = join(src, 'nexe')
await mkdirpAsync(temp)
const tmpFiles = await readDirAsync(temp)
await Promise.all(
tmpFiles.map(async path => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
})
)
await next()
await Promise.all(tmpFiles.map(x => unlinkAsync(x)))
return Promise.all(
compiler.files.map(async file => {
const sourceFile = join(src, file.filename)
const tempFile = join(temp, file.filename)
const fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile))
await writeFileAsync(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents)
})
)
}
+11 -7
View File
@@ -1,12 +1,12 @@
import { normalize } from 'path'
import * as Bluebird from 'bluebird'
import { Readable } from 'stream'
import { createWriteStream, chmodSync } from 'fs'
import { readFileAsync, dequote, isWindows } from './util'
import { NexeCompiler } from './compiler'
import { readFileAsync, dequote, isWindows } from '../util'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function readStreamAsync(stream: NodeJS.ReadableStream): PromiseLike<string> {
return new Bluebird(resolve => {
return new Promise(resolve => {
let input = ''
stream.setEncoding('utf-8')
stream.on('data', (x: string) => {
@@ -56,10 +56,14 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
await next()
}
compiler.output = output || `${compiler.options.name}${isWindows ? '.exe' : ''}`
const deliverable = await compiler.compileAsync()
compiler.output = isWindows
? `${(output || compiler.options.name).replace(/\.exe$/, '')}.exe`
: `${output || compiler.options.name}`
return new Bluebird((resolve, reject) => {
const target = compiler.options.targets.shift() as NexeTarget
const deliverable = await compiler.compileAsync(target)
return new Promise((resolve, reject) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(normalize(compiler.output!)))
+3 -3
View File
@@ -1,8 +1,8 @@
import download = require('download')
import { isDirectoryAsync } from './util'
import { LogStep } from './logger'
import { isDirectoryAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from './compiler'
import { NexeCompiler } from '../compiler'
function fetchNodeSourceAsync(cwd: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
+2 -3
View File
@@ -1,8 +1,7 @@
import { each } from 'bluebird'
import { readFileAsync, isDirectoryAsync } from './util'
import { readFileAsync, isDirectoryAsync, each } from '../util'
import { Buffer } from 'buffer'
import * as globs from 'globby'
import { NexeCompiler } from './compiler'
import { NexeCompiler } from '../compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<void>) {
const resources = compiler.resources
+86
View File
@@ -0,0 +1,86 @@
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
export type NodeArch = 'x86' | 'x64'
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
architectures: NodeArch[] = ['x86', 'x64']
export { platforms, architectures }
export interface NexeTarget {
version: string
platform: NodePlatform
arch: NodeArch
}
//TODO bsd
const prettyPlatform: { [key: string]: NodePlatform } = {
win32: 'windows',
windows: 'windows',
darwin: 'mac',
macos: 'mac',
mac: 'mac',
linux: 'linux',
static: 'alpine',
alpine: 'alpine'
}
//TODO arm
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86',
ia32: 'x86',
x32: 'x86',
x64: 'x64'
}
function isVersion(x: string) {
if (!x) {
return false
}
return /\d/.test(x.replace(/v|\./g, ''))
}
function isPlatform(x: string): x is NodePlatform {
return x in prettyPlatform
}
function isArch(x: string): x is NodeArch {
return x in prettyArch
}
class Target implements NexeTarget {
constructor(public arch: NodeArch, public platform: NodePlatform, public version: string) {}
toJSON() {
return this.toString()
}
toString() {
return `${this.platform}-${this.arch}-${this.version}`
}
}
export function targetsEqual(a: NexeTarget, b: NexeTarget) {
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
}
export function getTarget(target: string | NexeTarget = ''): NexeTarget {
let arch = process.arch as NodeArch,
platform = prettyPlatform[process.platform],
version = process.version.slice(1)
if (typeof target !== 'string') {
target = `${target.platform}-${target.arch}-${target.version}`
}
target.toLowerCase().split('-').forEach(x => {
if (isVersion(x)) {
version = x
}
if (isPlatform(x)) {
platform = prettyPlatform[x]
}
if (isArch(x)) {
arch = prettyArch[x]
}
})
return new Target(arch, platform, version)
}
+3 -19
View File
@@ -1,23 +1,7 @@
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 'got' {
function got(url: string, options?: any): Promise<{ body: string }>
export = got
}
declare module 'download' {
import { Duplex } from 'stream'
interface DownloadOptions {
+30 -13
View File
@@ -1,9 +1,26 @@
import { readFile, writeFile, stat } from 'fs'
import * as Bluebird from 'bluebird'
import { execFile } from 'child_process'
import pify = require('pify')
import rimraf = require('rimraf')
const { promisify } = Bluebird
const rimrafAsync = (promisify(rimraf) as any) as (path: string) => Bluebird<void>
const rimrafAsync = pify(rimraf)
export async function each<T>(
list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any>
) {
const l = await list
for (let i = 0; i < l.length; i++) {
await action(l[i], i, l)
}
}
function falseOnEnoent(e: any) {
if (e.code === 'ENOENT') {
return false
}
throw e
}
function dequote(input: string) {
input = input.trim()
@@ -16,30 +33,30 @@ function dequote(input: string) {
}
export interface ReadFileAsync {
(path: string): Bluebird<Buffer>
(path: string, encoding: string): Bluebird<string>
(path: string): Promise<Buffer>
(path: string, encoding: string): Promise<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 readFileAsync = pify(readFile)
const writeFileAsync = pify(writeFile)
const statAsync = pify(stat)
const execFileAsync = pify(execFile)
const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) {
return statAsync(path).then(x => true).catch({ code: 'ENOENT' }, () => false)
return statAsync(path).then(x => true).catch(falseOnEnoent)
}
function isDirectoryAsync(path: string) {
return statAsync(path).then(x => x.isDirectory()).catch({ code: 'ENOENT' }, () => false)
return statAsync(path).then(x => x.isDirectory()).catch(falseOnEnoent)
}
export {
dequote,
isWindows,
rimrafAsync,
statAsync,
execFileAsync,
readFileAsync,
pathExistsAsync,
isDirectoryAsync,
+80 -8
View File
@@ -1,19 +1,91 @@
import { compile } from '../'
import { compile } from '../src/nexe'
import {
getUnBuiltReleases,
getLatestGitRelease
} from './releases'
import * as ci from './ci'
import { getTarget } from '../src/target'
import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH,
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD),
isWindows = branchName.includes('win32') && Boolean(env.APPVEYOR),
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isLinux = Boolean(env.TRAVIS),
isWindows = Boolean(env.APPVEYOR),
isMac = Boolean(env.CIRCLECI),
isPullRequest = Boolean(env.CIRCLE_PR_NUMBER) || Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER)
async function build () {
if (isScheduled) {
const releases = await
const releases = await getUnBuiltReleases()
if (!releases.length) {
return
}
const windowsBuild = releases.find(x => x.platform === 'windows')
const macBuild = releases.find(x => x.platform === 'mac')
const linuxOrAlpine = releases.find(x => x.platform === 'linux' || x.platform === 'alpine')
if (linuxOrAlpine) {
await ci.triggerDockerBuild(linuxOrAlpine)
}
if (macBuild) {
await ci.triggerMacBuild(macBuild)
}
if (windowsBuild) {
await ci.triggerWindowsBuild(windowsBuild)
}
}
if (env.NEXE_VERSION) {
const
target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
build: true,
make: [target.arch],
version: target.version,
output
}
if (isWindows) {
await compile(options)
}
if (isMac) {
await compile(options)
}
if (isLinux) {}
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease()
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
'Authorization': 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream'
}
})
console.log(target + ' uploaded.')
}
}
}
build().catch(x => {
console.error(x)
process.exit(1)
})
function assertNexeBinary (file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
if (require.main === module) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
View File
+46
View File
@@ -0,0 +1,46 @@
import { NexeTarget } from './releases'
import got = require('got')
import * as assert from 'assert'
const { env } = process
export function triggerDockerBuild(release: NexeTarget) {
return Promise.resolve()
// assert.ok(env.TRAVIS_TOKEN)
// const travis = `https://api.travis-ci.org/repo/nexe/nexe/requests`
// return got(travis, {
// json: true,
// body: {
// request: {
// branch: 'master',
// config: {
// merge_mode: 'deep_merge',
// env: {
// NEXE_VERSION: release
// }
// }
// }
// },
// headers: {
// 'Travis-API-Version': '3',
// 'Authorization': `token ${env.TRAVIS_TOKEN}`
// }
// })
}
export function triggerMacBuild (release: NexeTarget) {
return Promise.resolve()
// assert.ok(env.CIRCLE_TOKEN)
// const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/master?circle-token=${env.CIRCLE_TOKEN}`
// return got(circle, {
// json: true,
// body: {
// build_parameters: {
// NEXE_VERSION: release
// }
// }
// })
}
export function triggerWindowsBuild (release: NexeTarget) {
return Promise.resolve(env.NEXE_VERSION = release.toString())
}
+62
View File
@@ -0,0 +1,62 @@
import got = require('got')
import {
NodePlatform,
NodeArch,
platforms,
architectures,
NexeTarget,
getTarget,
targetsEqual,
} from '../src/target'
export { NexeTarget }
export interface GitRelease {
tag_name: string
assets_url: string
upload_url: string
assets: Array<{ name: string }>
}
interface NodeRelease {
version: string
}
async function getJson<T> (url: string) {
return JSON.parse((await got(url)).body) as T
}
//TODO only build the latest of each major...?
function isBuildableVersion (version: string) {
const major = +version.split('.')[0]
return ![0, 1, 2, 3, 4, 5, 7].includes(major)
|| version === '4.8.4'
}
export function getLatestGitRelease () {
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest')
}
export async function getUnBuiltReleases () {
const nodeReleases = (await getJson<NodeRelease[]>('https://nodejs.org/download/release/index.json'))
const existingVersions = (await getLatestGitRelease())
.assets.map(x => getTarget(x.name))
const versionMap: { [key:string]: true } = {}
return nodeReleases.reduce((versions: NexeTarget[], { version }) => {
version = version.replace('v', '').trim()
if (!isBuildableVersion(version) || versionMap[version]) {
return versions
}
versionMap[version] = true
platforms.forEach(platform => {
architectures.forEach(arch => {
if (arch === 'x86' && platform === 'mac') return
versions.push(getTarget({ platform, arch, version }))
})
})
return versions
}, [])
.filter(
x => !existingVersions.some(t => targetsEqual(t, x))
)
}
+1 -2
View File
@@ -7,7 +7,6 @@
"strict": true
},
"include":[
"src/**/*.ts",
"src/patches/sourcemaps.js"
"src/**/*.ts"
]
}