diff --git a/.gitignore b/.gitignore index 580e70a..57b1cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ coverage node_modules *.log *.exe +!winsw.exe .idea .nexe lib diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fa95257..c076e13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nexe", - "version": "2.0.0-rc.5", + "version": "2.0.0-rc.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -146,12 +146,6 @@ "json-stable-stringify": "1.0.1" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true - }, "ansi": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", @@ -532,9 +526,9 @@ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", - "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { "version": "0.3.1", @@ -770,7 +764,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.18" + "iconv-lite": "0.4.19" } }, "end-of-stream": { @@ -792,37 +786,26 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", + "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==", "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", + "esprima": "3.1.3", + "estraverse": "4.2.0", "esutils": "2.0.2", "optionator": "0.8.2", - "source-map": "0.2.0" - }, - "dependencies": { - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "optional": true, - "requires": { - "amdefine": "1.0.1" - } - } + "source-map": "0.5.7" } }, "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" }, "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" }, "esutils": { "version": "2.0.2", @@ -830,9 +813,9 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", - "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "exec-sh": { "version": "0.2.1", @@ -866,20 +849,20 @@ "accepts": "1.3.4", "array-flatten": "1.1.1", "content-disposition": "0.5.2", - "content-type": "1.0.2", + "content-type": "1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.8", "depd": "1.1.1", "encodeurl": "1.0.1", "escape-html": "1.0.3", - "etag": "1.8.0", + "etag": "1.8.1", "finalhandler": "1.0.4", "fresh": "0.5.0", "merge-descriptors": "1.0.1", "methods": "1.1.2", "on-finished": "2.3.0", - "parseurl": "1.3.1", + "parseurl": "1.3.2", "path-to-regexp": "0.1.7", "proxy-addr": "1.1.5", "qs": "6.5.0", @@ -920,7 +903,7 @@ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", "requires": { - "iconv-lite": "0.4.18", + "iconv-lite": "0.4.19", "jschardet": "1.5.1", "tmp": "0.0.31" } @@ -1022,7 +1005,7 @@ "encodeurl": "1.0.1", "escape-html": "1.0.3", "on-finished": "2.3.0", - "parseurl": "1.3.1", + "parseurl": "1.3.2", "statuses": "1.3.1", "unpipe": "1.0.0" } @@ -1064,9 +1047,9 @@ } }, "forwarded": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", - "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.1.tgz", + "integrity": "sha1-ik4wxkCwU5U5mjVJxzAldygEiWE=" }, "fresh": { "version": "0.5.0", @@ -1102,7 +1085,7 @@ "base64-js": "1.2.1", "chokidar": "1.7.0", "concat-with-sourcemaps": "1.0.4", - "escodegen": "1.8.1", + "escodegen": "1.9.0", "express": "4.15.4", "fliplog": "0.3.13", "fs-extra": "2.1.2", @@ -1333,9 +1316,9 @@ } }, "iconv-lite": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "ieee754": { "version": "1.1.8", @@ -2139,9 +2122,9 @@ } }, "parseurl": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, "path-is-absolute": { "version": "1.0.1", @@ -2280,7 +2263,7 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", "requires": { - "forwarded": "0.1.0", + "forwarded": "0.1.1", "ipaddr.js": "1.4.0" } }, @@ -2507,7 +2490,7 @@ "destroy": "1.0.4", "encodeurl": "1.0.1", "escape-html": "1.0.3", - "etag": "1.8.0", + "etag": "1.8.1", "fresh": "0.5.0", "http-errors": "1.6.2", "mime": "1.3.4", @@ -2524,7 +2507,7 @@ "requires": { "encodeurl": "1.0.1", "escape-html": "1.0.3", - "parseurl": "1.3.1", + "parseurl": "1.3.2", "send": "0.15.4" } }, diff --git a/plugins/nexe-daemon/.npmrc b/plugins/nexe-daemon/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/plugins/nexe-daemon/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/plugins/nexe-daemon/index.ts b/plugins/nexe-daemon/index.ts new file mode 100644 index 0000000..eea8fd5 --- /dev/null +++ b/plugins/nexe-daemon/index.ts @@ -0,0 +1,5 @@ +import { NexeCompiler } from 'nexe' + +export default function daemon (compiler: NexeCompiler ) { + compiler.addResource('') +} diff --git a/plugins/nexe-daemon/package.json b/plugins/nexe-daemon/package.json new file mode 100644 index 0000000..023e387 --- /dev/null +++ b/plugins/nexe-daemon/package.json @@ -0,0 +1,12 @@ +{ + "name": "nexe-daemon", + "version": "1.0.0", + "description": "Plugin for nexe to create installable services", + "main": "index.js", + "scripts": {}, + "author": "Caleb Boyd", + "license": "MIT", + "devDependencies": { + "nexe": "^2.0.0-rc.6" + } +} diff --git a/plugins/nexe-daemon/winsw-config.xml.tmpl b/plugins/nexe-daemon/winsw-config.xml.tmpl new file mode 100644 index 0000000..fd1ce1c --- /dev/null +++ b/plugins/nexe-daemon/winsw-config.xml.tmpl @@ -0,0 +1,6 @@ + + {{id}} + {{name}} + {{description}} + %BASE%\{{executable}}.exe + \ No newline at end of file diff --git a/plugins/nexe-daemon/winsw.exe b/plugins/nexe-daemon/winsw.exe new file mode 100644 index 0000000..3f1f724 Binary files /dev/null and b/plugins/nexe-daemon/winsw.exe differ diff --git a/src/compiler.ts b/src/compiler.ts index fae5cb0..8a2747b 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -5,7 +5,7 @@ import { createReadStream } from 'fs' import { Readable } from 'stream' import { spawn } from 'child_process' import { Logger } from './logger' -import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows } from './util' +import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util' import { NexeOptions, nexeVersion } from './options' import { NexeTarget } from './target' import download = require('download') @@ -34,19 +34,16 @@ export class NexeCompiler { public log = new Logger(this.options.loglevel) public src: string public files: NexeFile[] = [] + public shims: string[] = [] public input: string public bundledInput?: string public output: string | null public targets: NexeTarget[] public target: NexeTarget - public resources: { bundle: string; index: { [key: string]: number[] } } = { + public resources: { bundle: Buffer; index: { [key: string]: number[] } } = { index: {}, - bundle: '' + bundle: Buffer.from('') } - public readFileAsync: (file: string) => Promise - public writeFileAsync: (file: string, contents: Buffer | string) => Promise - public replaceInFileAsync: (file: string, replacer: any, replaceValue: string) => Promise - public setFileContentsAsync: (file: string, contents: string | Buffer) => Promise private nodeSrcBinPath: string constructor(public options: NexeOptions) { @@ -65,32 +62,50 @@ export class NexeCompiler { this.env.PYTHON = python } } + } - this.readFileAsync = async (file: string) => { - let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file)) - if (!cachedFile) { - const absPath = join(this.src, file) - cachedFile = { - absPath, - filename: file, - contents: await readFileAsync(absPath, 'utf-8').catch(x => { - if (x.code === 'ENOENT') return '' - throw x - }) - } - this.files.push(cachedFile) + @bound + addResource(file: string, contents: Buffer) { + const { resources } = this + resources.index[file] = [resources.bundle.byteLength, contents.byteLength] + resources.bundle = Buffer.concat([resources.bundle, contents]) + } + + @bound + async readFileAsync(file: string) { + this.assertBuild() + let cachedFile = this.files.find(x => normalize(x.filename) === normalize(file)) + if (!cachedFile) { + const absPath = join(this.src, file) + cachedFile = { + absPath, + filename: file, + contents: await readFileAsync(absPath, 'utf-8').catch(x => { + if (x.code === 'ENOENT') return '' + throw x + }) } - return cachedFile - } - this.writeFileAsync = (file, contents) => writeFileAsync(join(this.src, file), contents) - this.replaceInFileAsync = async (file, replace: string | RegExp, value: string) => { - const entry = await this.readFileAsync(file) - entry.contents = entry.contents.replace(replace, value) - } - this.setFileContentsAsync = async (file: string, contents: string) => { - const entry = await this.readFileAsync(file) - entry.contents = contents + this.files.push(cachedFile) } + return cachedFile + } + + @bound + writeFileAsync(file: string, contents: string | Buffer) { + this.assertBuild() + return writeFileAsync(join(this.src, file), contents) + } + + @bound + async replaceInFileAsync(file: string, replace: string | RegExp, value: string) { + const entry = await this.readFileAsync(file) + entry.contents = entry.contents.replace(replace, value) + } + + @bound + async setFileContentsAsync(file: string, contents: string) { + const entry = await this.readFileAsync(file) + entry.contents = contents } quit() { @@ -99,6 +114,12 @@ export class NexeCompiler { return this.log.flush() } + assertBuild() { + if (!this.options.build) { + throw new Error('This feature is only available with `--build`') + } + } + public getNodeExecutableLocation(target?: NexeTarget) { if (target) { return join(this.options.temp, target.toString()) @@ -217,12 +238,13 @@ export class NexeCompiler { artifact.push(chunk) }) binary.on('close', () => { - const content = this._serializeHeader(header) + this.input + const content = this._serializeHeader(header) + this.shims.join(';') + ';' + this.input + artifact.push(content) artifact.push(this.resources.bundle) const lengths = Buffer.from(Array(16)) lengths.writeDoubleLE(Buffer.byteLength(content), 0) - lengths.writeDoubleLE(Buffer.byteLength(this.resources.bundle), 8) + lengths.writeDoubleLE(this.resources.bundle.byteLength, 8) artifact.push(Buffer.concat([Buffer.from(''), lengths])) artifact.push(null) }) diff --git a/src/options.ts b/src/options.ts index f9ed512..af64223 100644 --- a/src/options.ts +++ b/src/options.ts @@ -148,10 +148,6 @@ function tryResolveMainFileName(cwd: string) { filename = basename(file).replace(extname(file), '') } catch (_) {} - if (filename === 'index' && basename(cwd)) { - return basename(cwd) - } - return filename ? filename : 'nexe_' + Date.now() } @@ -162,12 +158,23 @@ function extractLogLevel(options: NexeOptions) { return 'info' } +function isName(name: string) { + return name && name !== 'index' +} + function extractName(options: NexeOptions) { let name = options.name - if (!name && typeof options.input === 'string') { + if (!isName(name) && typeof options.input === 'string') { name = basename(options.input).replace(extname(options.input), '') } - name = name === 'index' ? tryResolveMainFileName(options.cwd) : name + + if (!isName(name)) { + name = tryResolveMainFileName(options.cwd) + } + + if (!isName(name) && basename(options.cwd)) { + name = basename(options.cwd) + } return name.replace(/\.exe$/, '') } diff --git a/src/steps/resource.ts b/src/steps/resource.ts index 5cdb88a..1822f84 100644 --- a/src/steps/resource.ts +++ b/src/steps/resource.ts @@ -18,15 +18,8 @@ export default async function resource(compiler: NexeCompiler, next: () => Promi count++ step.log(`Including file: ${file}`) const contents = await readFileAsync(file) - const commentSafeContents = contents.toString('base64') - resources.index[file] = [ - Buffer.byteLength(resources.bundle), - Buffer.byteLength(commentSafeContents) - ] - resources.bundle += commentSafeContents + compiler.addResource(file, contents) }) - step.log( - `Included ${count} file(s). ${(Buffer.byteLength(resources.bundle) / 1e6).toFixed(3)} MB` - ) + step.log(`Included ${count} file(s). ${(resources.bundle.byteLength / 1e6).toFixed(3)} MB`) return next() } diff --git a/src/util.ts b/src/util.ts index 8f2c57d..00182a8 100644 --- a/src/util.ts +++ b/src/util.ts @@ -26,6 +26,22 @@ function padRight(str: string, l: number) { return (str + ' '.repeat(l)).substr(0, l) } +const bound: MethodDecorator = function bound( + target: Object, + propertyKey: string, + descriptor: TypedPropertyDescriptor +) { + const configurable = true + return { + configurable, + get(this: T) { + const value = (descriptor.value as any).bind(this) + Object.defineProperty(this, propertyKey, { configurable, value, writable: true }) + return value + } + } +} + function dequote(input: string) { input = input.trim() const singleQuote = input.startsWith("'") && input.endsWith("'") @@ -62,6 +78,7 @@ function isDirectoryAsync(path: string) { export { dequote, padRight, + bound, isWindows, rimrafAsync, statAsync, diff --git a/tsconfig.json b/tsconfig.json index 57c70f2..d0bf374 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "target":"es5", + "experimentalDecorators": true, "lib": ["es2017"], "module": "commonjs", "outDir": "./lib",