dev: separate process management from DevWatcherService

This commit is contained in:
KernelDeimos
2025-01-22 10:08:06 -05:00
parent 44c6f57c7a
commit 4c72e9d285
4 changed files with 117 additions and 60 deletions
+2 -1
View File
@@ -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,
@@ -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,
};
@@ -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;
@@ -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;