From be48e5a2f3a143bc27b392952efd1acd4088a1be Mon Sep 17 00:00:00 2001 From: calebboyd Date: Thu, 31 Aug 2017 11:43:08 -0500 Subject: [PATCH] docs: update readme with new usage --- README.md | 59 +++++++++++++++++--------------- circle.yml | 11 ++++++ package-lock.json | 6 ++-- package.json | 2 +- src/bundling/bindings-rewrite.ts | 12 +++---- src/compiler.ts | 9 +++-- src/nexe.ts | 12 ++++--- src/options.ts | 6 ++-- src/patches/gyp.ts | 5 ++- src/releases.ts | 17 ++++----- src/target.ts | 25 ++++++++------ src/util.ts | 8 +++-- tasks/build.ts | 24 +++++++------ tasks/ci.ts | 30 +++++++++------- 14 files changed, 134 insertions(+), 92 deletions(-) create mode 100644 circle.yml diff --git a/README.md b/README.md index 409df6d..539b285 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ ## Motivation and Features -- Supports production ready, ([securable](#security)) builds +- Supports production ready builds +- Self contained applications - Ability to run multiple applications with *different* node.js runtimes. - Distribute binaries without needing node / npm. - Idempotent builds @@ -25,13 +26,11 @@ - Flexible build pipeline - Cross platform builds -## Usage (Beta) - -*Note: V2 API is still subject to change. * For v1 see [V1-EOL](https://github.com/nexe/nexe/tree/V1-EOL) +## Usage - Existing application bundle: - `nexe -i ./my-app-bundle.js -o ./my-app.exe` + `nexe my-app-bundle.js -o my-app` - stdin interface @@ -45,7 +44,7 @@ Additional resources can be added to the binary by passing `-r glob/pattern/**/* ## Compiling Node -By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. These options are supported out of the box, and subsequent builds with compatible options will result in instant build times. +By default `nexe` will attempt to download a pre-built executable. However, some users may want to customize the way node is built, either by changing the flags, providing a different icon, or different executable details. To build node, use the `build` option/flag Nexe also exposes its patching pipeline to the user. This allows the application of simple patching of node sources prior to compilation. @@ -57,11 +56,12 @@ Using Nexe Programatically ```javascript -const nexe = require('nexe') +const { compile } = require('nexe') -nexe.compile({ - input: './my-app-bundle.js' - output: './my-app.exe' +compile({ + input: './my-app.js', + output: './my-app.exe', + build: true, //builds node patches: [ async (compiler, next) => { await compiler.setFileContentsAsync( @@ -89,13 +89,22 @@ nexe.compile({ - #### `target: string` - Dash seperated platform-architecture-version. e.g. `'win32-ia32-6.10.3'` - default: `[process.platform, process.arch, process.version.slice(1)].join('-')` -- #### `bundle: string` - - Path to a custom bundling function. When executed it should either return a fusebox producer, or a string. - - If it is a string it will be required and the export `nexeBundle` will be used - - Defaults to the internal fuse-box configuration +- #### `bundle: string | boolean` + - If a string is provided it must be a valid relative module path + and should provide an export with the following signature: + ```typescript + export function createBundle ( + filename: string, + options: { name: string, minify: any, cwd: string } + ): Promise` + ``` + - default: true, uses the internal fuse-box configuration - #### `name: string` - Module friendly name of the application - default: basename of the input file, or `nexe_${Date.now()}` + - #### `cwd: string` + - Directory nexe will operate on as though it is the cwd + - default: process.cwd() - #### `version: string` - The Node version you're building for - default: `process.version.slice(1)` @@ -112,7 +121,7 @@ nexe.compile({ - Example: `['--with-dtrace', '--dest-cpu=x64']` - default: `[]` - #### `make: Array` - - Array of arguments for the node build make step, on windows this step recieves optiosn for vcBuild.bat + - Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat - default: `[]` or `['nosign', 'release']` for non windows systems - #### `make: Array` - Alias for `make` option @@ -143,10 +152,6 @@ nexe.compile({ - #### `loglevel: string` - Set the loglevel, info, silent, or verbose - default: `'info'` - - #### `padding` - - Advanced option for controlling the size available in the executable. - - It must be larger than the bundle + resources in bytes - - default: 3, 6, 9, 16, 25, or 40 MB sizes are selected automatically. - #### `patches: Array` - Userland patches for patching or modifying node source - default: `[]` @@ -163,26 +168,24 @@ For examples, see the built in patches: [src/patches](src/patches) - Quickly set a file's contents within the downloaded Node.js source. - `replaceInFileAsync(filename: string, ...replaceArgs): Promise` - Quickly perform a replace in a file within the downloaded Node.js source. The rest arguments are passed along to `String.prototype.replace` - - `readFileAsync(filename: string): Promise` + - `readFileAsync(filename: string): Promise` - Access (or create) a file within the downloaded Node.js source. - - `files: Array` - - The cache of currently read, modified, or created files within the downloaded Node.js source. + - `files: Array` + - The cache of the currently read, modified, or created files within the downloaded Node.js source. -#### `SourceFile` +#### `NexeFile` - `contents: string` + - `absPath: string` - `filename: string` -Any modifications made to `SourceFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`. +Any modifications made to `NexeFile#contents` will be maintained in the cache _without_ the need to explicitly write them back out, e.g. using `NexeCompiler#setFileContentsAsync`. ### Native Modules Nexe has a plugin built for use with [fuse-box](http://fuse-box.org) > 2.2.1. This plugin currently supports modules that require `.node` files and those that use the `bindings` module. Take a look at the [example](examples/native-build/build.js) -Future: Implement support `node-pre-gyp#find`. - -## Security -A common use case for Nexe is production deployment. When distributing executables it is important to [sign](https://en.wikipedia.org/wiki/Code_signing) them before distributing. Nexe was designed specifically to not mangle the binary it produces, this allows the checksum and signature of the size and location offsets to be maintained through the code signing process. +- [ ] Implement support `node-pre-gyp#find`. ## Maintainers diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..6dc1ead --- /dev/null +++ b/circle.yml @@ -0,0 +1,11 @@ +# machine: +# environment: +# NODE_VERSION: 6 +test: + override: + - npm run nexe-build +# deployment: +# target: +# branch: bundle-wip +# - npm run nexe-build + diff --git a/package-lock.json b/package-lock.json index e49175b..43e072f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2968,9 +2968,9 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, "prettier": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.5.3.tgz", - "integrity": "sha1-WdrcaDNF7GuI+IuU7Urn4do5S/4=", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.6.1.tgz", + "integrity": "sha512-f85qBoQiqiFM/sCmJaN4Lagj9bqMcv38vCftqp4GfVessAqq3Ns6g+3gd8UXReStLLE/DGEdwiZXoFKxphKqwg==", "dev": true }, "pretty-time": { diff --git a/package.json b/package.json index 5b8a54d..f6eb2a2 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@types/rimraf": "0.0.28", "got": "^7.1.0", "mocha": "^3.2.0", - "prettier": "^1.4.4", + "prettier": "^1.6.1", "ts-node": "3.3.0", "typescript": "^2.4.1" } diff --git a/src/bundling/bindings-rewrite.ts b/src/bundling/bindings-rewrite.ts index 4da9435..933de7a 100644 --- a/src/bundling/bindings-rewrite.ts +++ b/src/bundling/bindings-rewrite.ts @@ -4,15 +4,15 @@ import * as child from 'child_process' import { createHash } from 'crypto' export interface ExtractNodeModuleOptions { - [key: string]: - | { - additionalFiles: string[] - } - | true + [key: string]: { additionalFiles: string[] } | true } function hashName(name: string | Buffer) { - return createHash('md5').update(name).digest('hex').toString().slice(0, 8) + return createHash('md5') + .update(name) + .digest('hex') + .toString() + .slice(0, 8) } export function embedDotNode( diff --git a/src/compiler.ts b/src/compiler.ts index 6abdbab..ca310e2 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -150,11 +150,16 @@ export class NexeCompiler { private _generateHeader() { const version = ['configure', 'vcBuild', 'make'].reduce((a, c) => { - return (a += (this.options as any)[c].slice().sort().join()) + return (a += (this.options as any)[c] + .slice() + .sort() + .join()) }, '') + this.options.enableNodeCli const header = { resources: this.resources.index, - version: createHash('md5').update(version).digest('hex') + version: createHash('md5') + .update(version) + .digest('hex') } const serializedHeader = this._serializeHeader(header) return header diff --git a/src/nexe.ts b/src/nexe.ts index fc6182e..a0ca53f 100644 --- a/src/nexe.ts +++ b/src/nexe.ts @@ -30,11 +30,13 @@ async function compile( : [] const nexe = compose(resource, bundle, cli, buildSteps) return callback - ? void nexe(compiler).then(() => callback && callback(null)).catch((e: Error) => { - if (callback) { - callback(e) - } else throw e - }) + ? void nexe(compiler).then( + () => callback && callback(null), + (e: Error) => { + if (callback) callback(e) + else throw e + } + ) : nexe(compiler) } diff --git a/src/options.ts b/src/options.ts index f8ee5c0..6f8d478 100644 --- a/src/options.ts +++ b/src/options.ts @@ -15,7 +15,6 @@ export interface NexeOptions { build: boolean input: string output: string - compress: boolean targets: (string | NexeTarget)[] name: string cwd: string @@ -40,6 +39,7 @@ export interface NexeOptions { info?: boolean ico?: string warmup?: string + compress?: boolean clean?: boolean /** * Api Only @@ -216,7 +216,9 @@ function normalizeOptionsAsync(input?: Partial): Promise k !== 'rc').forEach(x => delete opts[x]) + Object.keys(alias) + .filter(k => k !== 'rc') + .forEach(x => delete opts[x]) return Promise.resolve(options) } diff --git a/src/patches/gyp.ts b/src/patches/gyp.ts index 431c86b..10e1a5f 100644 --- a/src/patches/gyp.ts +++ b/src/patches/gyp.ts @@ -12,7 +12,10 @@ export default async function nodeGyp( nodeGypMarker, ` ${nodeGypMarker} - ${files.filter(x => x.filename.startsWith('lib')).map(x => `'${x.filename}'`).toString()}, + ${files + .filter(x => x.filename.startsWith('lib')) + .map(x => `'${x.filename}'`) + .toString()}, `.trim() ) } diff --git a/src/releases.ts b/src/releases.ts index 1aaf960..334b7fd 100644 --- a/src/releases.ts +++ b/src/releases.ts @@ -28,8 +28,8 @@ interface NodeRelease { version: string } -async function getJson(url: string) { - return JSON.parse((await got(url)).body) as T +async function getJson(url: string, options?: any) { + return JSON.parse((await got(url, options)).body) as T } //TODO only build the latest of each major...? @@ -38,19 +38,20 @@ function isBuildableVersion(version: string) { return !~[0, 1, 2, 3, 4, 5, 7].indexOf(major) || version === '4.8.4' } -export function getLatestGitRelease() { - return getJson('https://api.github.com/repos/nexe/nexe/releases/latest') +export function getLatestGitRelease(options?: any) { + return getJson('https://api.github.com/repos/nexe/nexe/releases/latest', options) } -export async function storeAsset(asset: GitAsset, dest: string) { - await download(asset.browser_download_url, dest) +export async function storeAsset(asset: GitAsset, dest: string, headers?: any) { + const options = headers ? { headers } : undefined + await download(asset.browser_download_url, dest, options as any) } -export async function getUnBuiltReleases() { +export async function getUnBuiltReleases(options?: any) { const nodeReleases = await getJson( 'https://nodejs.org/download/release/index.json' ) - const existingVersions = (await getLatestGitRelease()).assets.map(x => getTarget(x.name)) + const existingVersions = (await getLatestGitRelease(options)).assets.map(x => getTarget(x.name)) const versionMap: { [key: string]: true } = {} return nodeReleases diff --git a/src/target.ts b/src/target.ts index a4cf4e9..4be95b4 100644 --- a/src/target.ts +++ b/src/target.ts @@ -70,17 +70,20 @@ export function getTarget(target: string | NexeTarget = ''): NexeTarget { 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] - } - }) + 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) } diff --git a/src/util.ts b/src/util.ts index d5364df..017d6dd 100644 --- a/src/util.ts +++ b/src/util.ts @@ -44,11 +44,15 @@ const execFileAsync = pify(execFile) const isWindows = process.platform === 'win32' function pathExistsAsync(path: string) { - return statAsync(path).then(x => true).catch(falseOnEnoent) + return statAsync(path) + .then(x => true) + .catch(falseOnEnoent) } function isDirectoryAsync(path: string) { - return statAsync(path).then(x => x.isDirectory()).catch(falseOnEnoent) + return statAsync(path) + .then(x => x.isDirectory()) + .catch(falseOnEnoent) } export { diff --git a/tasks/build.ts b/tasks/build.ts index f71c293..41ac791 100644 --- a/tasks/build.ts +++ b/tasks/build.ts @@ -1,7 +1,7 @@ import { compile } from '../src/nexe' -import { - getUnBuiltReleases, - getLatestGitRelease +import { + getUnBuiltReleases, + getLatestGitRelease } from '../src/releases' import * as ci from './ci' import { getTarget } from '../src/target' @@ -9,16 +9,17 @@ import { pathExistsAsync, statAsync, readFileAsync, execFileAsync } from '../src import got = require('got') const env = process.env, - branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH, + branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || 'master', 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) + isPullRequest = Boolean(env.CIRCLE_PR_NUMBER) || Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER), + headers = { 'Authorization': 'token ' + env.GITHUB_TOKEN } async function build () { if (isScheduled) { - const releases = await getUnBuiltReleases() + const releases = await getUnBuiltReleases({ headers }) if (!releases.length) { return } @@ -30,7 +31,7 @@ async function build () { await ci.triggerDockerBuild(linuxOrAlpine) } if (macBuild) { - await ci.triggerMacBuild(macBuild) + await ci.triggerMacBuild(macBuild, branchName) } if (windowsBuild) { await ci.triggerWindowsBuild(windowsBuild) @@ -38,12 +39,13 @@ async function build () { } if (env.NEXE_VERSION) { - const + const target = getTarget(env.NEXE_VERSION), output = isWindows ? './out.exe' : './out', options = { empty: true, build: true, + configure: [], make: [target.arch], version: target.version, output @@ -53,14 +55,16 @@ async function build () { } if (isMac) { - await compile(options) + const timeout = setInterval(() => console.log('Building...'), 300 * 1000) + await compile({...options, make: [], configure: ['--dest-cpu=x64']}) + clearInterval(timeout) } if (isLinux) {} if (await pathExistsAsync(output)) { await assertNexeBinary(output) - const gitRelease = await getLatestGitRelease() + const gitRelease = await getLatestGitRelease({ headers }) await got(gitRelease.upload_url.split('{')[0], { query: { name: target.toString() }, body: await readFileAsync(output), diff --git a/tasks/ci.ts b/tasks/ci.ts index 85ac877..fd6c817 100644 --- a/tasks/ci.ts +++ b/tasks/ci.ts @@ -27,20 +27,24 @@ export function triggerDockerBuild(release: NexeTarget) { // }) } -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 triggerMacBuild (release: NexeTarget, branch: string) { + assert.ok(env.CIRCLE_TOKEN) + const circle = `https://circleci.com/api/v1.1/project/github/nexe/nexe/tree/${branch}?circle-token=${env.CIRCLE_TOKEN}` + return got(circle, { + json: true, + body: { + build_parameters: { + NEXE_VERSION: release.toString(), + GITHUB_TOKEN: env.GITHUB_TOKEN + } + } + }) } export function triggerWindowsBuild (release: NexeTarget) { - return Promise.resolve(env.NEXE_VERSION = release.toString()) + const hasVersion = 'NEXE_VERSION' in env + env.NEXE_VERSION = hasVersion + ? env.NEXE_VERSION!.trim() + : release.toString() + return Promise.resolve() }