diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c9286e5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "chenxsan.vscode-standardjs" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..86a91a6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "standard.enable": true +} diff --git a/index.js b/index.js index b257609..ec5c63a 100644 --- a/index.js +++ b/index.js @@ -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)) }) } diff --git a/package.json b/package.json index 5cc6b7f..adf9d19 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/bundle.js b/src/bundle.js index 4e43340..2a5a122 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -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' } ] } } diff --git a/src/cli.js b/src/cli.js index ac17c60..52da971 100644 --- a/src/cli.js +++ b/src/cli.js @@ -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) + } + }) } }) } diff --git a/src/compiler.js b/src/compiler.js index 9103c33..bd65b74 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -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('').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 + } } diff --git a/src/logger.js b/src/logger.js index bdac6c4..a8edcdf 100644 --- a/src/logger.js +++ b/src/logger.js @@ -43,11 +43,4 @@ const logger = logLevels.reduce((logMethods, info) => { } }) -const { info, error, verbose, setLevel } = logger - -export { - setLevel, - info, - error, - verbose -} +export default logger diff --git a/src/nexe.js b/src/nexe.js index 1404f68..5872a69 100644 --- a/src/nexe.js +++ b/src/nexe.js @@ -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)) }) } diff --git a/src/options.js b/src/options.js index bff7f23..635ae21 100644 --- a/src/options.js +++ b/src/options.js @@ -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]) diff --git a/src/patches/content.js b/src/patches/content.js deleted file mode 100644 index 34d792b..0000000 --- a/src/patches/content.js +++ /dev/null @@ -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 -} diff --git a/src/patches/index.js b/src/patches/index.js index 3e38adc..9749fc6 100644 --- a/src/patches/index.js +++ b/src/patches/index.js @@ -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, diff --git a/src/patches/third-party-main.js b/src/patches/third-party-main.js index eefb77b..d8c0311 100644 --- a/src/patches/third-party-main.js +++ b/src/patches/third-party-main.js @@ -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() } diff --git a/src/resource.js b/src/resource.js new file mode 100644 index 0000000..c109d56 --- /dev/null +++ b/src/resource.js @@ -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 + }) +} diff --git a/src/util.js b/src/util.js index daad89b..c52013f 100644 --- a/src/util.js +++ b/src/util.js @@ -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 } diff --git a/test.js b/test.js new file mode 100644 index 0000000..ed07694 --- /dev/null +++ b/test.js @@ -0,0 +1,2 @@ +console.log("HELLO WORLD") +console.log("HELLO WORLD")