nexe
Version:
Create a single executable out of your Node.js application
290 lines (289 loc) • 12 kB
JavaScript
"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;