From 4c72e9d2858a4b9c04fa62e420fd0874931cbd4f Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Wed, 22 Jan 2025 10:08:06 -0500 Subject: [PATCH] dev: separate process management from DevWatcherService --- src/backend/exports.js | 3 +- .../src/modules/hostos/HostOSModule.js | 14 +++ .../src/modules/hostos/ProcessService.js | 97 +++++++++++++++++++ .../modules/selfhosted/DevWatcherService.js | 63 +----------- 4 files changed, 117 insertions(+), 60 deletions(-) create mode 100644 src/backend/src/modules/hostos/HostOSModule.js create mode 100644 src/backend/src/modules/hostos/ProcessService.js diff --git a/src/backend/exports.js b/src/backend/exports.js index 2ff8500d8..0d4ef9c06 100644 --- a/src/backend/exports.js +++ b/src/backend/exports.js @@ -34,7 +34,7 @@ const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js"); const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js"); const { AppsModule } = require("./src/modules/apps/AppsModule.js"); const { DevelopmentModule } = require("./src/modules/development/DevelopmentModule.js"); - +const { HostOSModule } = require("./src/modules/hostos/HostOSModule.js"); module.exports = { helloworld: () => { @@ -52,6 +52,7 @@ module.exports = { EssentialModules: [ Core2Module, PuterFSModule, + HostOSModule, CoreModule, WebModule, TemplateModule, diff --git a/src/backend/src/modules/hostos/HostOSModule.js b/src/backend/src/modules/hostos/HostOSModule.js new file mode 100644 index 000000000..c7e50439c --- /dev/null +++ b/src/backend/src/modules/hostos/HostOSModule.js @@ -0,0 +1,14 @@ +const { AdvancedBase } = require("@heyputer/putility"); + +class HostOSModule extends AdvancedBase { + async install (context) { + const services = context.get('services'); + + const ProcessService = require('./ProcessService'); + services.registerService('process', ProcessService); + } +} + +module.exports = { + HostOSModule, +}; diff --git a/src/backend/src/modules/hostos/ProcessService.js b/src/backend/src/modules/hostos/ProcessService.js new file mode 100644 index 000000000..b0f930a07 --- /dev/null +++ b/src/backend/src/modules/hostos/ProcessService.js @@ -0,0 +1,97 @@ +const BaseService = require("../../services/BaseService"); + +class ProxyLogger { + constructor (log) { + this.log = log; + } + attach (stream) { + let buffer = ''; + stream.on('data', (chunk) => { + buffer += chunk.toString(); + let lineEndIndex = buffer.indexOf('\n'); + while (lineEndIndex !== -1) { + const line = buffer.substring(0, lineEndIndex); + this.log(line); + buffer = buffer.substring(lineEndIndex + 1); + lineEndIndex = buffer.indexOf('\n'); + } + }); + + stream.on('end', () => { + if (buffer.length) { + this.log(buffer); + } + }); + } +} + +class ProcessService extends BaseService { + static MODULES = { + path: require('path'), + spawn: require('child_process').spawn, + }; + + _construct () { + this.instances = []; + } + + async _init (args) { + this.args = args; + + process.on('exit', () => { + this.exit_all_(); + }) + } + + log_ (name, isErr, line) { + let txt = `[${name}:`; + txt += isErr + ? `\x1B[34;1m2\x1B[0m` + : `\x1B[32;1m1\x1B[0m`; + txt += '] ' + line; + this.log.info(txt); + } + + async exit_all_ () { + for ( const { proc } of this.instances ) { + proc.kill(); + } + } + + async start ({ name, fullpath, command, args, env }) { + this.log.info(`Starting ${name} in ${fullpath}`); + const env_processed = { ...(env ?? {}) }; + for ( const k in env_processed ) { + if ( typeof env_processed[k] !== 'function' ) continue; + env_processed[k] = env_processed[k]({ + global_config: this.global_config + }); + } + console.log( + 'command', + command, + ...args + ) + const proc = this.modules.spawn(command, args, { + shell: true, + env: { + ...process.env, + ...env_processed, + }, + cwd: fullpath, + }); + this.instances.push({ + name, proc, + }); + const out = new ProxyLogger((line) => this.log_(name, false, line)); + out.attach(proc.stdout); + const err = new ProxyLogger((line) => this.log_(name, true, line)); + err.attach(proc.stderr); + proc.on('exit', () => { + this.log.info(`[${name}:exit] Process exited (${proc.exitCode})`); + this.instances = this.instances.filter((inst) => inst.proc !== proc); + }) + } +} + +module.exports = ProcessService; diff --git a/src/backend/src/modules/selfhosted/DevWatcherService.js b/src/backend/src/modules/selfhosted/DevWatcherService.js index 2fc2cc3db..440202b0e 100644 --- a/src/backend/src/modules/selfhosted/DevWatcherService.js +++ b/src/backend/src/modules/selfhosted/DevWatcherService.js @@ -53,16 +53,8 @@ class DevWatcherService extends BaseService { spawn: require('child_process').spawn, }; - _construct () { - this.instances = []; - } - async _init (args) { this.args = args; - - process.on('exit', () => { - this.exit_all_(); - }) } // Oh geez we need to wait for the web server to initialize @@ -71,13 +63,16 @@ class DevWatcherService extends BaseService { // this was to debug the first time, like Ahhhhhh!! // but hey at least we have this convenient event listener. async ['__on_ready.webserver'] () { + const svc_process = this.services.get('process'); + const { root, commands } = this.args; let promises = []; for ( const entry of commands ) { const { directory } = entry; const fullpath = this.modules.path.join( root, directory); - promises.push(this.start_({ ...entry, fullpath })); + // promises.push(this.start_({ ...entry, fullpath })); + promises.push(svc_process.start({ ...entry, fullpath })); } await Promise.all(promises); @@ -85,56 +80,6 @@ class DevWatcherService extends BaseService { // run so we just wait a bit before we say we're ready. await new Promise((resolve) => setTimeout(resolve, 5000)); } - - log_ (name, isErr, line) { - let txt = `[${name}:`; - txt += isErr - ? `\x1B[34;1m2\x1B[0m` - : `\x1B[32;1m1\x1B[0m`; - txt += '] ' + line; - this.log.info(txt); - } - - async start_ ({ name, fullpath, command, args, env }) { - this.log.info(`Starting ${name} in ${fullpath}`); - const env_processed = { ...(env ?? {}) }; - for ( const k in env_processed ) { - if ( typeof env_processed[k] !== 'function' ) continue; - env_processed[k] = env_processed[k]({ - global_config: this.global_config - }); - } - console.log( - 'command', - command, - ...args - ) - const proc = this.modules.spawn(command, args, { - shell: true, - env: { - ...process.env, - ...env_processed, - }, - cwd: fullpath, - }); - this.instances.push({ - name, proc, - }); - const out = new ProxyLogger((line) => this.log_(name, false, line)); - out.attach(proc.stdout); - const err = new ProxyLogger((line) => this.log_(name, true, line)); - err.attach(proc.stderr); - proc.on('exit', () => { - this.log.info(`[${name}:exit] Process exited (${proc.exitCode})`); - this.instances = this.instances.filter((inst) => inst.proc !== proc); - }) - } - - async exit_all_ () { - for ( const { proc } of this.instances ) { - proc.kill(); - } - } }; module.exports = DevWatcherService;