rawx
Version:
process daemon with utilities
666 lines • 34.4 kB
JavaScript
"use strict";
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());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
/***
* license.kind
* Original: https://github.com/lilzeta
* Flux this tag if/whenever you feel like
*/
// module.exports = Server;
// Server <= _Server class inside closure
// External modules/type
const { spawn, fork, exec, execFile } = require("child_process");
const { exists } = require("fs");
const t_kill = require("tree-kill");
const { v4: __id } = require("uuid");
const Ops = require("../ops/ops");
const Server_Constructor = require("./server_construct");
// Server_Facade behaves as would exposed inner _Server
class Server_Facade {
constructor(args) {
Object.defineProperty(this, "set_range", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "die", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
return server_creator(args);
}
}
// Now we expose _Server through Server_Facade as if we created it w/vanilla
const Server = Server_Facade;
const server_creator = (args) => {
// server_creator => return new _Server(args);
// set in constructor
let o;
class Server_Concrete_Class extends Server_Constructor {
constructor(args) {
super(args);
// TypeError: this.set_sigterm is not a function (Typescript bug?)
// on_constructed = (args: s) => {
// if (args.sig !== "handled") this.set_sigterm();
// // Set a new chain_id for our first proc
// this._tubed = __id();
// // prepare first proc, if no .on_watch also run
// this.prerun_checks({ chain_id: this._tubed }).catch();
// // TODO test if we need .catch();
// o.accent(7, "L:79", `constructor completed`);
// }
Object.defineProperty(this, "prerun_checks", {
enumerable: true,
configurable: true,
writable: true,
value: (_a) => { var _b; return __awaiter(this, void 0, void 0, function* () {
var { chain_id, direct_trigger } = _a, opts = __rest(_a, ["chain_id", "direct_trigger"]);
const { trigger_file_path, trigger_index } = opts;
if (!direct_trigger && this._tubed && this._tubed !== chain_id)
return;
let proc_ = this.proc_from_stack({ direct_trigger, trigger_index });
if (proc_ === "wait")
return;
let proc = proc_;
if (!proc) {
if (!((_b = this._step_procs) === null || _b === void 0 ? void 0 : _b.length) && !this.has_trigger()) {
o.forky(2, "L:90", `no procs & no trigger_index -> die()`);
this.die();
}
return;
}
// this.tubed = id;
if (proc.delay)
yield o.wait(proc.delay);
// if another watch has retriggered do nothing/return
if (this._tubed !== chain_id)
return;
o.forky(10, "L:99", "proc:");
o.forky(10, "L:100", o.pretty(proc));
// if proc.trap -> last <Server_Proc> - a one shot, remove watchers -> run command
proc.trap && this.trap(chain_id); // syncronously
// ignoring result, can throw
this.prepare_run({ proc, chain_id, trigger_file_path });
if (proc._conc) {
// const conc_proc = proc.concurrent;
// ignoring result, can throw
this.concurrently(proc._conc, chain_id).catch();
}
}); }
});
Object.defineProperty(this, "proc_from_stack", {
enumerable: true,
configurable: true,
writable: true,
value: ({ direct_trigger = false, trigger_index, }) => {
var _a;
if (direct_trigger) {
if (!o.defi(trigger_index)) {
trigger_index = this.trigger_index;
}
if (o.defi(trigger_index)) {
this.set_range(trigger_index);
}
}
if (!((_a = this._step_procs) === null || _a === void 0 ? void 0 : _a.length)) {
return;
}
if (this._step_procs[0].on_watch && !direct_trigger)
return "wait";
return this._step_procs.shift();
}
});
Object.defineProperty(this, "prepare_run", {
enumerable: true,
configurable: true,
writable: true,
value: ({ proc, chain_id, trigger_file_path }) => __awaiter(this, void 0, void 0, function* () {
o.accent(9, "L:135", `prepare_run, trigger_file_path?:`);
o.accent(9, "L:136", trigger_file_path);
const { chain_exit, _sidecar } = proc;
const { run_if_file_dne, goto_on_file_exists } = proc;
const silence = proc.silence === "all" ? () => 999 : undefined;
try {
o.forky(chain_exit ? 8 : 999, "L:141", `~ TIC ~`); // KICK...
let cool;
// TODO this case has some dupe stuff needs fn/promise
if (run_if_file_dne === null || run_if_file_dne === void 0 ? void 0 : run_if_file_dne.length) {
// run_if_file_dne is a filename if it exists
try {
const f_exists = yield exists(run_if_file_dne);
if (proc.silence !== "all") {
o.forky((silence === null || silence === void 0 ? void 0 : silence()) || 2, `<child-process/> File Exists - ${f_exists} -${_sidecar.label}`);
}
if (o.defi(goto_on_file_exists)) {
this.set_range(goto_on_file_exists);
o.forky((silence === null || silence === void 0 ? void 0 : silence()) || 2, `<child-process/> Setting Next Proc: ${goto_on_file_exists}`);
// assume goto_on_file_exists means chain_next @goto
this.chain_next({
code: 0,
proc,
// juke chain
chain_option: true,
chain_id,
});
return;
}
}
catch (err) {
o.forky((silence === null || silence === void 0 ? void 0 : silence()) || 9, "L:171", `File Exist threw - ${_sidecar.label}`);
}
o.forky((silence === null || silence === void 0 ? void 0 : silence()) || 2, `<child-process> _// start File DNE Exist - ${_sidecar.label}`);
}
else {
o.forky((silence === null || silence === void 0 ? void 0 : silence()) || 2, "L:178", `<child-process> _// start - ${_sidecar.label}`);
}
cool = this.run_wrapper({ proc, trigger_file_path });
if (this._tubed !== chain_id) {
this.terminate_check();
return;
}
if (cool) {
// If run_process returned a process, TODO typesafe
if (cool.pid) {
cool = cool;
this._running.push(cool);
this._proc_util.setup_subproc({
sub_proc: cool,
label: _sidecar.label,
silence: _sidecar.silence,
on_close: (pid) => this.flush_exits(pid),
});
if (chain_exit) {
cool.once("exit", (code) => __awaiter(this, void 0, void 0, function* () {
this.terminate_check();
this.chain_next({
code,
proc,
chain_option: chain_exit,
chain_id,
});
}));
}
else {
cool.once("exit", (_) => __awaiter(this, void 0, void 0, function* () {
this.terminate_check();
}));
}
}
else {
cool = cool;
cool.then((code) => {
this.terminate_check();
this.chain_next({
code,
proc,
chain_option: chain_exit,
chain_id,
});
}).catch((err) => {
o.errata(1, "L:225", `Hook fn through an error: ${err}`);
});
}
}
}
catch (err) {
// TODO? a chaperone timer to prevent a watch_death -> catch (err) -> watch_death loop
if (this.has_trigger()) {
this.kill_all().catch(); // but for now, force all processes to be catch responsible
o.errata((silence === null || silence === void 0 ? void 0 : silence()) || 1, `Server | uncaught error -> chain will restart to trigger_index: ${err}`);
yield o.wait(this.kill_delay);
}
else {
o.errata(1, `Server | uncaught throw -> no trigger_index, exiting: ${err}`);
this.die();
}
}
})
});
// lets keep this syncronous
Object.defineProperty(this, "run_wrapper", {
enumerable: true,
configurable: true,
writable: true,
value: ({ proc, trigger_file_path, // undefined only when chained from constructor
sub_proc = true, }) => {
const { type } = proc;
if (this._proc_util.is_fn_proc(proc)) {
const _proc = proc;
if (type === "fn") {
return this.call_fn({ proc: _proc }).catch();
}
else if (type === "exec_fn") {
return this.call_exec_fn({
proc: _proc,
trigger_file_path,
}).catch();
}
return;
}
else {
const _proc = proc;
if (this._proc_util.is_repeater_proc(_proc)) {
return this.run_repeater_proc(_proc.construct, _proc._sidecar, sub_proc);
}
return this.run_node_proc(_proc.construct, _proc._sidecar, sub_proc);
}
}
});
Object.defineProperty(this, "run_repeater_proc", {
enumerable: true,
configurable: true,
writable: true,
value: (p_args, _sidecar, _subproc) => {
return new Promise((res) => __awaiter(this, void 0, void 0, function* () {
const { type, command, options } = p_args;
let sub_args = p_args.args;
const juke_d = _sidecar.silence === "all" ? 999 : 8;
const rep_args = p_args.args;
let repeater_chain = p_args.repeater_chain;
const subproc = (args) => {
if (type === "execFile") {
o.log(juke_d, `execFile ${command} ${o.pretty(sub_args)} ${o.pretty(options)}`);
return execFile(p_args.command, args, options);
}
if (type === "spawn") {
const p_a = p_args;
o.log(juke_d, `spawn ${command} ${o.pretty(sub_args)} ${o.pretty(p_a.options)}`);
return spawn(command, args, p_a.options);
}
};
const repeater = (i = 0) => __awaiter(this, void 0, void 0, function* () {
// TODO pre_validate, lets assume at least 1, res(at last index or fail)
// if (i >= sub_args.length) {
// return 0;
// }
const c_proc = subproc(rep_args[i]);
this._proc_util.basic_proc_stdio(c_proc, _sidecar.silence);
// o.basic_proc_stdio(c_proc, _sidecar.silence);
// TODO this implementation depends on Every c_proc sending an exit or throwing
// how incorrect/poorly behaved is this assumption?
c_proc === null || c_proc === void 0 ? void 0 : c_proc.on("exit", (code) => __awaiter(this, void 0, void 0, function* () {
if (code) {
if (repeater_chain === "success") {
o.errata(1, "L:321", `repeater chain exitted w/err: ${code}, halting.`);
// Is this correct though?
res(code);
return;
}
else {
o.errata(1, `repeater chain exitted w/err: ${code}, w/no repeater_chain: "success" => next`);
}
}
if (i + 1 >= sub_args.length) {
res(code || 0);
return;
}
yield repeater(i + 1);
}));
});
yield repeater();
}));
}
});
// lets keep this syncronous
Object.defineProperty(this, "run_node_proc", {
enumerable: true,
configurable: true,
writable: true,
value: (p_args, _sidecar, _subproc) => {
// https://nodejs.org/api/child_process.html#child_process
const { type } = p_args;
// shell: true,
// if (process.platform == "win32") {
if (type === "exec") {
o.accent(9, "L:354", `exec(command):`);
o.accent(9, "L:355", p_args.command);
return exec(p_args.command);
}
let sub_args = p_args.args;
if (type === "fork") {
const fork_args = sub_args;
p_args = p_args;
const { module, options } = p_args;
return fork(module, fork_args, options);
}
p_args = p_args;
const { command, options } = p_args;
// TODO put in new function?
if ((sub_args === null || sub_args === void 0 ? void 0 : sub_args[0]) && Array.isArray(sub_args[0])) {
}
else {
sub_args = p_args.args;
if (type === "execFile") {
o.log(8, "L:372", `execFile ${command} ${o.pretty(sub_args)} ${o.pretty(options)}`);
return execFile(p_args.command, sub_args, options);
}
if (type === "spawn") {
const p_a = p_args;
o.log(8, `spawn ${command} ${o.pretty(sub_args)} ${o.pretty(p_a.options)}`);
return spawn(command, sub_args, p_a.options);
}
}
}
});
Object.defineProperty(this, "call_fn", {
enumerable: true,
configurable: true,
writable: true,
value: ({ proc }) => {
const fn = proc.fn;
return new Promise((res, rej) => {
const id = __id();
Object.assign(this._live_functions, { id });
// callback/reject
fn({
callback: (code) => {
o.accent(6, "L:394", `call_fn callback: ${code}`);
if (o.defi(this._live_functions[id])) {
delete this._live_functions[id];
res(code);
}
},
// file_path: this.last_path_triggered,
// next watch trigger will clear live_functions(no cancels)
fail: (err) => {
delete this._live_functions[id];
rej(err);
},
});
});
}
});
Object.defineProperty(this, "call_exec_fn", {
enumerable: true,
configurable: true,
writable: true,
value: ({ proc, trigger_file_path }) => {
return new Promise((res, rej) => {
if (!trigger_file_path) {
o.errata(2, "L:413", `exec_fn is not built to run without on_watch set`);
rej();
}
const fn = proc.fn;
const id = __id();
Object.assign(this._live_functions, { id });
// callback/reject
const on_close = (pid) => {
this.flush_exits(pid);
// WIP
if (o.defi(this._live_functions[id])) {
delete this._live_functions[id];
}
res(0);
};
fn({
callback: ({ command }) => {
const label = `Light exec callback - ${command}`;
const new_child_process = exec(command);
this._proc_util.setup_subproc({
sub_proc: new_child_process,
label,
silence: proc.silence,
on_close,
});
o.accent(5, "L:438", `Light exec callback setup: ${label}`);
},
file_path: trigger_file_path,
// next watch trigger will clear live_functions(no cancels)
// WIP
fail: (err) => {
delete this._live_functions[id];
rej(err);
},
});
});
}
});
Object.defineProperty(this, "chain_next", {
enumerable: true,
configurable: true,
writable: true,
value: ({ code, proc, chain_option, chain_id }) => __awaiter(this, void 0, void 0, function* () {
if (!o.defi(chain_option))
return;
o.forky(8, "L:453", `~ TOC ~`); // SNARE... haha
if (chain_option !== "success" || code === 0) {
// if err & not last
if (code && this._step_procs.length) {
const err = `Consider adding {chain_exit: "success"} to proc: ${proc._sidecar.label} to halt on error`;
o.errata(1, "L:459", err);
}
// short post exit delay, TODO actually necessary for safe log flush?
yield o.wait(this.kill_delay);
if (this._tubed !== chain_id)
return;
if (o.defi(proc.chain_next))
this.set_range(proc.chain_next);
// pass same chain_id to next
yield this.prerun_checks({ chain_id, sub_proc: true });
}
else {
o.errata(2, `proc: ${proc._sidecar.label} did not succeed with code: ${code}, execution stopped`);
}
})
});
// TODO better clearing/upkeep/proc checks
Object.defineProperty(this, "flush_exits", {
enumerable: true,
configurable: true,
writable: true,
value: (pid) => {
// Todo true necessary?
let remove_i = {};
for (let i = 0; i < this._running.length; i++) {
if (this._running[i].pid === pid)
Object.assign(remove_i, { i: true });
else if (this._running[i].killed)
Object.assign(remove_i, { i: true });
}
this._running = this._running.reduce((new_r, dis, i) => {
!remove_i[i] && new_r.push(dis);
return new_r;
}, []);
this.terminate_check();
o.accent(9, "L:492", `flush_clean_exits is completed. `);
}
});
// Not sure if what mixture of clever and dumb this may get us into, when trued
// TODO concurrently with hooks, needs some rumination
Object.defineProperty(this, "concurrently", {
enumerable: true,
configurable: true,
writable: true,
value: (proc_, chain_id) => __awaiter(this, void 0, void 0, function* () {
if (!this._proc_util.is_fn_proc(proc_)) {
const proc = proc_;
if (proc.delay)
yield o.wait(proc.delay);
if (this._tubed !== chain_id)
return;
proc.trap && this.trap(chain_id); // syncronously
// note this is not the proc with _conc, but _conc itself
o.accent(8, "L:505", `Starting _conc proc: ${proc._sidecar.label}`);
const sub_proc = this.run_node_proc(proc.construct, proc._sidecar, true);
this._proc_util.setup_subproc({
sub_proc,
label: proc_._sidecar.label,
silence: proc_._sidecar.silence,
on_close: (pid) => this.flush_exits(pid),
});
// this is yet another _conc
if (proc._conc) {
yield this.concurrently(proc._conc, chain_id);
}
}
})
});
Object.defineProperty(this, "terminate_check", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
var _a;
if (!this.has_trigger() && !((_a = this._step_procs) === null || _a === void 0 ? void 0 : _a.length)) {
this.die();
}
}
});
// `exec` with no follow up proc (good for dev-server w/watch server of it's own)
Object.defineProperty(this, "trap", {
enumerable: true,
configurable: true,
writable: true,
value: (id) => {
var _a;
(_a = this._watch) === null || _a === void 0 ? void 0 : _a.watches_clear();
this.watch = null;
this._tubed = id; // also enforce precidence if another watch simul.
// enforced no triggers after .terminate_check() die()
this.procs = null;
this._step_procs = null;
}
});
// note watchers has a direct link to this restart
Object.defineProperty(this, "restart", {
enumerable: true,
configurable: true,
writable: true,
value: (file_path, trigger_index) => __awaiter(this, void 0, void 0, function* () {
o.errata(9, "L:538", `restart, triggered by file_path: ${file_path}`);
o.errata(9, "L:539", `restart, trigger_index: ${trigger_index}`);
const tubed = (this._tubed = __id());
o.accent(5, "L:541", `restart -> | [_kill_] | -> start`);
yield this.kill_all();
yield o.wait(this.kill_delay);
o.accent(5, "L:544", `restart -> kill -> | [_start_] |`);
yield this.prerun_checks(Object.assign(Object.assign({ chain_id: tubed, direct_trigger: true }, (file_path && { trigger_file_path: file_path })), { trigger_index }));
})
});
Object.defineProperty(this, "kill_all", {
enumerable: true,
configurable: true,
writable: true,
value: () => __awaiter(this, void 0, void 0, function* () {
const inner_promises = [];
const this_running = this._running;
const run_c = this_running.length;
this._live_functions = {};
if (run_c) {
for (let i = 0; i < run_c; i++) {
if (!this_running[i].killed) {
inner_promises.push(new Promise((res_, _rej) => {
// TODO find way to skip dead this.running[i]
t_kill(this_running[i].pid, "SIGKILL", (err) => {
// intentionally set above 10
o.errata(11, "L:566", err); // so noisy
res_(); // no rejecting here on err!
});
}));
}
this._running = [];
}
}
return Promise.all(inner_promises);
})
});
// terminate server, sync
Object.defineProperty(this, "die", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
var _a;
o.forky(6, "L:580", `Server.die() called, terminating any running`);
this._tubed = __id();
// this._tube_lock = true;
this.procs = null;
this.kill_all().catch(); // No await, because no waiting to complete
try {
// if trap didn't clear already
(_a = this._watch) === null || _a === void 0 ? void 0 : _a.watches_clear();
this.watch = null;
}
catch (err) {
o.errata(1, "L:590", `Server.die() | this.watchers?.watches_clear(); | THROWN ERROR `);
}
o.forky(1, "L:592", `Server.die() | Kill done, watches_clear - Exit`);
// TODO test removing POST_KILL_DELAY uses (std_out courtesy for kill())
setTimeout(() => {
// WIP line# flags
// o._l(1, `Server.die() [__L], shutting down complete -> exit`);
o.log(1, "L:597", `Server.die(), shutting down complete -> exit`);
process.exit();
}, this.kill_delay);
}
});
Object.defineProperty(this, "has_trigger", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
var _a;
return o.defi(this.trigger_index) || ((_a = this.trigger_indices) === null || _a === void 0 ? void 0 : _a.length);
}
});
Object.defineProperty(this, "set_sigterm", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
let log = o.forky;
const dis = this;
process.on("SIGINT", function () {
dis.die();
log(1, "L:609", `[L:451], shutting down complete -> exit`);
process.exit();
});
}
});
// An Env Configured Ops rebased for `Server`
o = new Ops({
colors: args.colors,
debug: this.debug,
label: this.label,
});
if (this.watch)
this._watch.set_trigger(this.restart);
if (args.dry_run) {
return;
}
if (args.sig !== "handled")
this.set_sigterm();
// Set a new chain_id for our first proc
this._tubed = __id();
// prepare first proc, if no .on_watch also run
this.prerun_checks({ chain_id: this._tubed }).catch();
// TODO test if we need .catch();
o.accent(7, "L:69", "test", `constructor completed`);
}
}
return new Server_Concrete_Class(args);
};
module.exports = Server;
//# sourceMappingURL=server.js.map