UNPKG

nexe

Version:

Create a single executable out of your Node.js application

290 lines (289 loc) 12 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NexeCompiler = exports.NexeError = void 0; const path_1 = require("path"); const buffer_1 = require("buffer"); const fs_1 = require("fs"); const child_process_1 = require("child_process"); const logger_1 = require("./logger"); const util_1 = require("./util"); const options_1 = require("./options"); const stream_1 = require("stream"); const MultiStream = require("multistream"); const bundle_1 = require("./fs/bundle"); const isBsd = Boolean(~process.platform.indexOf('bsd')); const make = util_1.isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'; const configure = util_1.isWindows ? 'configure' : './configure'; class NexeError extends Error { constructor(m) { super(m); Object.setPrototypeOf(this, NexeError.prototype); } } exports.NexeError = NexeError; class NexeCompiler { constructor(options) { this.options = options; /** * Epoch of when compilation started */ this.start = Date.now(); this.log = new logger_1.Logger(this.options.loglevel); /** * Copy of process.env */ this.env = Object.assign({}, process.env); /** * In memory files that are being manipulated by the compiler */ this.files = []; /** * Standalone pieces of code run before the application entrypoint */ this.shims = []; /** * The last shim (defaults to "require('module').runMain()") */ this.startup = ''; /** * Output filename (-o myapp.exe) */ this.output = this.options.output; /** * Flag to indicate whether or notstdin was used for input */ this.stdinUsed = false; const { python } = (this.options = options); //SOMEDAY iterate over multiple targets with `--outDir` this.targets = options.targets; this.target = this.targets[0]; if (!/https?\:\/\//.test(options.remote)) { throw new NexeError(`Invalid remote URI scheme (must be http or https): ${options.remote}`); } this.remoteAsset = options.remote + this.target.toString(); this.src = (0, path_1.join)(this.options.temp, this.target.version); this.configureScript = configure + ((0, util_1.semverGt)(this.target.version, '10.10.0') ? '.py' : ''); this.nodeSrcBinPath = util_1.isWindows ? (0, path_1.join)(this.src, 'Release', 'node.exe') : (0, path_1.join)(this.src, 'out', 'Release', 'node'); this.log.step('nexe ' + options_1.version, 'info'); this.bundle = new bundle_1.Bundle(options); if (util_1.isWindows) { const originalPath = process.env.PATH; delete process.env.PATH; this.env = Object.assign({}, process.env); this.env.PATH = python ? (this.env.PATH = (0, util_1.dequote)((0, path_1.normalize)(python)) + path_1.delimiter + originalPath) : originalPath; process.env.PATH = originalPath; } else { this.env = Object.assign({}, process.env); python && (this.env.PYTHON = python); } } addResource(absoluteFileName, content) { return this.bundle.addResource(absoluteFileName, content); } readFileAsync(file) { return __awaiter(this, void 0, void 0, function* () { this.assertBuild(); let cachedFile = this.files.find((x) => (0, path_1.normalize)(x.filename) === (0, path_1.normalize)(file)); if (!cachedFile) { const absPath = (0, path_1.join)(this.src, file); cachedFile = { absPath, filename: file, contents: yield (0, util_1.readFileAsync)(absPath, 'utf-8').catch((x) => { if (x.code === 'ENOENT') return ''; throw x; }), }; this.files.push(cachedFile); } return cachedFile; }); } writeFileAsync(file, contents) { this.assertBuild(); return (0, util_1.writeFileAsync)((0, path_1.join)(this.src, file), contents); } replaceInFileAsync(file, replace, value) { return __awaiter(this, void 0, void 0, function* () { const entry = yield this.readFileAsync(file); entry.contents = entry.contents.toString().replace(replace, value); }); } setFileContentsAsync(file, contents) { return __awaiter(this, void 0, void 0, function* () { const entry = yield this.readFileAsync(file); entry.contents = contents; }); } quit(error) { const time = Date.now() - this.start; this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green'); return this.log.flush(); } assertBuild() { if (!this.options.build) { throw new NexeError('This feature is only available with `--build`'); } } getNodeExecutableLocation(target) { if (this.options.asset) { return (0, path_1.resolve)(this.options.cwd, this.options.asset); } if (target) { return (0, path_1.join)(this.options.temp, target.toString()); } return this.nodeSrcBinPath; } _runBuildCommandAsync(command, args) { if (this.log.verbose) { this.compileStep.pause(); } return new Promise((resolve, reject) => { (0, child_process_1.spawn)(command, args, { cwd: this.src, env: this.env, shell: true, stdio: this.log.verbose ? 'inherit' : 'ignore', }) .once('error', (e) => { if (this.log.verbose) { this.compileStep.resume(); } reject(e); }) .once('close', (code) => { if (this.log.verbose) { this.compileStep.resume(); } if (code != 0) { const error = `${command} ${args.join(' ')} exited with code: ${code}`; reject(new NexeError(error)); } resolve(); }); }); } _configureAsync() { if (util_1.isWindows && (0, util_1.semverGt)(this.target.version, '10.15.99')) { return Promise.resolve(); } return this._runBuildCommandAsync(this.env.PYTHON || 'python', [ this.configureScript, ...this.options.configure, ]); } build() { return __awaiter(this, void 0, void 0, function* () { this.compileStep.log(`Configuring node build${this.options.configure.length ? ': ' + this.options.configure : '...'}`); yield this._configureAsync(); const buildOptions = this.options.make; this.compileStep.log(`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`); yield this._runBuildCommandAsync(make, buildOptions); return (0, fs_1.createReadStream)(this.getNodeExecutableLocation()); }); } _shouldCompileBinaryAsync(binary, location) { return __awaiter(this, void 0, void 0, function* () { //SOMEDAY combine make/configure/vcBuild/and modified times of included files const { snapshot, build } = this.options; if (!binary) { return true; } if (build && snapshot != null && (yield (0, util_1.pathExistsAsync)(snapshot))) { const snapshotLastModified = (yield (0, util_1.statAsync)(snapshot)).mtimeMs; const binaryLastModified = (yield (0, util_1.statAsync)(location)).mtimeMs; return snapshotLastModified > binaryLastModified; } return false; }); } compileAsync(target) { return __awaiter(this, void 0, void 0, function* () { const step = (this.compileStep = this.log.step('Compiling result')); const build = this.options.build; const location = this.getNodeExecutableLocation(build ? undefined : target); let binary = (yield (0, util_1.pathExistsAsync)(location)) ? (0, fs_1.createReadStream)(location) : null; if (yield this._shouldCompileBinaryAsync(binary, location)) { binary = yield this.build(); step.log('Node binary compiled'); } return this._assembleDeliverable(binary); }); } code() { return [this.shims.join(''), this.startup].join(';'); } _assembleDeliverable(binary) { return __awaiter(this, void 0, void 0, function* () { if (!this.options.mangle) { return binary; } const launchCode = this.code(); const codeSize = buffer_1.Buffer.byteLength(launchCode); const sentinel = buffer_1.Buffer.from('<nexe~~sentinel>'); let vfsSize = 0; const streams = [ binary, (0, bundle_1.toStream)(launchCode), this.bundle.toStream().pipe(new stream_1.Transform({ transform: (chunk, _, cb) => { vfsSize || this.bundle.finalize(); chunk && (vfsSize += chunk.length); cb(null, chunk); }, })), ]; let done = false; return new MultiStream((cb) => { if (done) cb(null, null); else if (streams.length) cb(null, streams.shift()); else { done = true; const trailers = buffer_1.Buffer.alloc(16); trailers.writeDoubleLE(codeSize, 0); trailers.writeDoubleLE(vfsSize, 8); cb(null, (0, bundle_1.toStream)(buffer_1.Buffer.concat([sentinel, trailers]))); } }); }); } } __decorate([ util_1.bound ], NexeCompiler.prototype, "addResource", null); __decorate([ util_1.bound ], NexeCompiler.prototype, "readFileAsync", null); __decorate([ util_1.bound ], NexeCompiler.prototype, "writeFileAsync", null); __decorate([ util_1.bound ], NexeCompiler.prototype, "replaceInFileAsync", null); __decorate([ util_1.bound ], NexeCompiler.prototype, "setFileContentsAsync", null); exports.NexeCompiler = NexeCompiler;