pandora
Version:
A powerful and lightweight application manager for Node.js applications powered by TypeScript.
174 lines (158 loc) • 4.62 kB
text/typescript
;
import Messenger, {MessengerClient} from 'pandora-messenger';
import {consoleLogger, getDaemonStdoutLogPath} from '../universal/LoggerBroker';
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import {spawn} from 'child_process';
import {DAEMON_MESSENGER, DAEMON_READY, SEND_DAEMON_MESSAGE} from '../const';
import {dirname} from 'path';
import mkdirp = require('mkdirp');
const is = require('is-type-of');
const tmpDir = os.tmpdir();
const pidFile = path.join(tmpDir, 'nodejs_pandora.pid');
const pathDaemonBootstrap = require.resolve('./DaemonBootstrap');
let preparedClient: MessengerClient = null;
/**
* Get messenger client of the daemon
* @return {Client}
*/
function getMessengerClient() {
return new Messenger.Client({
name: DAEMON_MESSENGER,
});
}
/**
* Determine the daemon is running
* @return {Promise<boolean>}
*/
export function isDaemonRunning(): Promise<boolean> {
return new Promise((resolve, reject) => {
const client = getMessengerClient();
client.once('connect', () => {
resolve(true);
client.close();
});
client.once('error', err => {
resolve(false);
});
});
};
/**
* Ensure the daemon is running
* @param options
* @return {Promise<any>}
*/
export async function barrierDaemon(options?) {
if (!await isDaemonRunning()) {
return new Promise((resolve, reject) => {
startDaemonReal(options, resolve, reject);
});
}
return Promise.resolve();
}
export function getDaemonClient(args?: { extensionPath }): Promise<MessengerClient> {
if (preparedClient) return Promise.resolve(preparedClient);
return new Promise((resolve, reject) => {
const client = Messenger.getClient({
name: DAEMON_MESSENGER,
});
client.on('connect', () => {
preparedClient = client;
resolve(client);
client.removeListener('error', onError);
});
function onError(err) {
reject(err);
}
client.once('error', onError);
});
}
/**
* Send a message to the daemon, it will start the daemon automatically if that is't running.
* @param command
* @param args
* @param callback
* @param timeout
* @return {Promise<void>}
*/
export async function send(command, args, callback, timeout?): Promise<void> {
timeout = timeout || 300 * 1000;
try {
await barrierDaemon();
const daemonClient = await getDaemonClient({extensionPath: args.extensionPath});
daemonClient.send(SEND_DAEMON_MESSAGE,
{command, args}, (err, data) => {
if (err) {
return callback(err, data);
}
if (data && data.error) {
return callback(data.error, data.data);
}
return callback(null, data.data);
}, timeout);
} catch (err) {
callback(err);
}
}
/**
* Exit the daemon automatically if there not remind any application.
* @param code
*/
export function clearCliExit(code) {
code = code != null ? code : 0;
send('list', {}, (err, data) => {
if (data && data.length === 0) {
consoleLogger.info('No application remind, exit the pandora daemon automatically');
preparedClient.once('error', () => {
process.exit(code);
});
send('exit', {}, (err, data) => {
if (err) {
consoleLogger.error(data);
process.exit(code);
return;
}
consoleLogger.info(data);
process.exit(code);
});
} else {
process.exit(code);
}
});
}
function startDaemonReal(options, resolve, reject) {
options = options || {};
if (is.function(options)) {
resolve = options;
options = {};
}
const args = [];
if (/\.ts$/.test(module.filename)) {
args.push('-r', 'ts-node/register', '-r', 'nyc-ts-patch');
}
args.push(pathDaemonBootstrap);
const daemonStdoutPath = getDaemonStdoutLogPath();
const daemonStdoutDir = dirname(daemonStdoutPath);
mkdirp.sync(daemonStdoutDir);
const stdout = fs.openSync(daemonStdoutPath, 'a');
const daemon = spawn(process.execPath, args, <any> {
stdio: ['ipc', stdout, stdout],
env: Object.assign(process.env, {
extensionPath: options.extensionPath
}),
detached: true,
});
daemon.on('exit', function (code, signal) {
const err = new Error(`daemon[${daemon.pid}] died unexpectedly with exit code ${code} , signal ${signal}`);
reject(err);
});
daemon.on('message', function (message) {
consoleLogger.info(`pandora[${daemon.pid}] daemon started successfully.`);
if (message === DAEMON_READY) {
fs.writeFileSync(pidFile, daemon.pid);
resolve();
}
});
return daemon;
}