UNPKG

rawx

Version:

process daemon with utilities

666 lines 34.4 kB
"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