Spaces:
Running
Running
/** | |
* Copyright (c) 2023 MERCENARIES.AI PTE. LTD. | |
* All rights reserved. | |
*/ | |
const { fork, spawn, exec, execSync } = require('node:child_process'); | |
const os = require('node:os'); | |
const path = require('node:path'); | |
const { assert } = require('console'); | |
const { ensure } = require('./pocketdbutils.js'); | |
const { sleep, checkGitForUpdates, omniCwd } = require('./utils.js'); | |
const { ensureDirSync } = require('fs-extra'); | |
const { update_build } = require('./updater.js'); | |
const readline = require('node:readline'); | |
const fs = require('node:fs'); | |
const { copyFileSync, existsSync } = require('node:fs'); | |
let args = process.argv.slice(2); | |
let server_entry = null; | |
const packagejson = JSON.parse(fs.readFileSync(path.join(omniCwd(), 'package.json'), { encoding: 'utf-8' })); | |
const { argv0 } = require('node:process'); | |
const depjson = packagejson.dependenciesBin; | |
const platform = os.platform(); | |
const arch = os.arch(); | |
const IPC_CMD = 'cmd'; | |
const IPC_CMD_restart = 'restart'; | |
// move out of /setup/launcher.js where the mercs.yaml file exists | |
const project_abs_root = omniCwd(); | |
const pocketbaseInstallPath = path.join(project_abs_root, depjson.root_dir, 'pocketbase'); | |
let server_process = null; | |
let pretty_process_pid = []; | |
let child_processes = new Map(); | |
function start_server(implicit_args) { | |
assert(server_process === null); | |
assert(server_entry !== null); | |
// IMPORTANT: omnitool need to run from /mercs as root and launcher in setup/launcher.js | |
const server_wd = path.join(project_abs_root, 'packages/omni-server'); | |
// derive the server entry file using absolute paths from launcher.js | |
// this ensures we can also resolve whether clicking directy on .exe or launching from terminal | |
const entrypath = path.join(server_wd, server_entry); | |
console.log(`Server entry point resolved to ${entrypath}`); | |
server_process = fork(entrypath, implicit_args.concat(args), { cwd: server_wd, execArgv: ['--inspect'] }); | |
pretty_process_pid.push(`|--OMNITOOL - Server PID ${server_process.pid}`); | |
child_processes.set(server_process.pid, server_process); | |
server_process.on('exit', (code) => { | |
console.log(`Server exited with code ${code}`); | |
process.exit(1); | |
}); | |
server_process.on('error', (err) => { | |
console.error(err); | |
process.abort(); | |
}); | |
server_process.on('message', (message) => { | |
switch (message[IPC_CMD]) { | |
case IPC_CMD_restart: | |
console.log('Server Restarting...'); | |
server_process.removeAllListeners('exit'); | |
server_process.kill(); | |
child_processes.delete(server_process.pid); | |
server_process = null; | |
// no need to re-open browser on restarts | |
implicit_args = implicit_args.filter((e) => e !== '--openBrowser'); | |
args = args.filter((e) => e !== '--openBrowser'); | |
start_server(implicit_args); | |
break; | |
} | |
}); | |
} | |
function verifyConfig() { | |
// check if dep version is defined | |
if (packagejson.engines['pocketbase'] === undefined) { | |
console.error(`Missing version definition for pocketbase DB in package.json engines`); | |
return false; | |
} | |
// check if dep is defined | |
if (depjson['pocketbase'] === undefined) { | |
console.error(`Unable to find installer definition for pocketbase DB`); | |
return false; | |
} | |
// check if there's an installer for the platform | |
try { | |
let zippath = depjson['pocketbase'].zipfile[platform][arch]; | |
if (zippath === undefined) { | |
console.error(`Unable to find installer download URL for pocketbase DB`); | |
return false; | |
} | |
} catch (e) { | |
console.error(`Unable to find pocketbase installer definition for [${platform}][${arch}]`); | |
return false; | |
} | |
return true; | |
} | |
function start_pocketbase() { | |
const pocketDBProcess = spawn(path.join(pocketbaseInstallPath, 'pocketbase'), ['serve'], { stdio: 'inherit' }); | |
setup_listeners(pocketDBProcess, 'pocketbase'); | |
pretty_process_pid.push(`|--OMNITOOL - PocketBaseDB PID ${pocketDBProcess.pid}`); | |
child_processes.set(pocketDBProcess.pid, pocketDBProcess); | |
} | |
function start_vite() { | |
const viteProcess = exec('yarn frontend'); | |
viteProcess.stderr.pipe(process.stderr); | |
viteProcess.stdout.pipe(process.stdout); | |
setup_listeners(viteProcess, 'vite debugger'); | |
pretty_process_pid.push(`|--OMNITOOL - Vite PID ${viteProcess.pid}`); | |
child_processes.set(viteProcess.pid, viteProcess); | |
} | |
async function user_confirmation(prompt) { | |
const rl = readline.createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
return new Promise((resolve) => | |
rl.question(prompt, (ans) => { | |
rl.close(); | |
resolve(ans); | |
}) | |
); | |
} | |
function setup_listeners(proc, name) { | |
proc.on('uncaughtException', (err) => { | |
console.err(err); | |
user_confirmation('Oops something went wrong. Press any key to exit...').then(() => process.exit(1)); | |
}); | |
// terminate parent for whatever reason | |
proc.on('exit', (code) => { | |
console.log(`${name} ${proc.pid} exited with code ${code}`); | |
process.exit(1); | |
}); | |
} | |
async function check_for_updates() { | |
console.log('Checking for updates...'); | |
const result = await checkGitForUpdates(); | |
if (result.hasUpdates) { | |
const input = await user_confirmation( | |
`You are currently on ${result.local}.\nThere's a new version ${result.remote} available. Would you like to pull and update with git? [y/n]:` | |
); | |
if (input === 'y') { | |
try { | |
console.log('\nCalling git pull...'); | |
execSync('git pull', { cwd: omniCwd(), stdio: 'inherit' }); | |
console.log('Calling yarn install...'); | |
execSync('yarn', { cwd: omniCwd(), stdio: 'inherit' }); | |
console.log('\nUpdate complete. Please re-run the application with: yarn start'); | |
process.exit(0); | |
} | |
catch(e) { | |
console.error(e); | |
console.warn(`Update failed. Please try running [yarn start] again.`); | |
process.exit(1); | |
} | |
} else { | |
console.log(`Continuing with current build ${result.local}...`); | |
} | |
} | |
else{ | |
console.log(`You are currently on the latest version available ${result.local}.`); | |
} | |
} | |
async function run_development(server_args) { | |
const pocketready = await ensure(pocketbaseInstallPath); | |
if (!pocketready) { | |
console.error('Fatal error setting up PocketBase DB'); | |
process.exit(1); | |
} | |
start_vite(); | |
await sleep(1000); | |
start_pocketbase(); | |
await sleep(1000); | |
start_server(server_args); | |
log_processes(); | |
} | |
async function run_production(server_args) { | |
const pocketready = await ensure(pocketbaseInstallPath); | |
if (!pocketready) { | |
console.error('Fatal error setting up PocketBase DB'); | |
process.exit(1); | |
} | |
start_pocketbase(); | |
await sleep(1000); | |
start_server(server_args); | |
log_processes(); | |
} | |
async function run_runtime_executable() { | |
const didupdate = await update_build(); | |
if (didupdate) { | |
console.log('Omnitool has been patched with an update and needed to close.'); | |
console.log('Please re-run the application!'); | |
console.log('Press any key to exit...'); | |
process.stdin.setRawMode(true); | |
process.stdin.resume(); | |
process.stdin.on('data', process.exit.bind(process, 0)); | |
} else { | |
await run_production(['-l', '127.0.0.1', '--openBrowser']); | |
} | |
} | |
function log_processes() { | |
pretty_process_pid.forEach((e) => console.log(e)); | |
} | |
async function ensure_wasm() { | |
const { NodeProcessEnv } = await import('omni-shared'); | |
// --- Copy wasm models | |
let wasmDir = null; | |
switch (process.env.NODE_ENV) { | |
case NodeProcessEnv.development: | |
wasmDir = path.join(omniCwd(), 'packages/omni-server', 'config.local', 'wasm'); | |
break; | |
case NodeProcessEnv.production: | |
wasmDir = path.join(omniCwd(), 'packages/omni-server', 'dist'); | |
break; | |
} | |
assert(wasmDir !== null); | |
ensureDirSync(wasmDir); | |
if (!existsSync(path.join(wasmDir, 'tfjs-backend-wasm.wasm'))) { | |
console.log('Installing WASM modules... nsfwjs/threaded/wasm'); | |
copyFileSync( | |
path.join(omniCwd(), 'node_modules', '@tensorflow', 'tfjs-backend-wasm', 'dist', 'tfjs-backend-wasm.wasm'), | |
path.join(wasmDir, 'tfjs-backend-wasm.wasm') | |
); | |
} | |
if (!existsSync(path.join(wasmDir, 'tfjs-backend-wasm-simd.wasm'))) { | |
console.log('Installing WASM modules... nsfwjs/threaded/simd '); | |
copyFileSync( | |
path.join(omniCwd(), 'node_modules', '@tensorflow', 'tfjs-backend-wasm', 'dist', 'tfjs-backend-wasm-simd.wasm'), | |
path.join(wasmDir, 'tfjs-backend-wasm-simd.wasm') | |
); | |
} | |
} | |
if (!verifyConfig()) { | |
process.exit(1); | |
} | |
process.on('exit', (code) => { | |
child_processes.forEach((p) => p.kill()); | |
child_processes.clear(); | |
}); | |
process.on('uncaughtException', (err) => { | |
console.error(err); | |
user_confirmation( | |
`Oops something went terribly wrong: \n\t ${err}\n\nPlease let us know!\n\nPress any key to exit...` | |
).then(() => process.exit(1)); | |
}); | |
pretty_process_pid.push(`OMNITOOL - Launcher PID ${process.pid}`); | |
// launched from omnitool executable - always production + updates | |
async function run() { | |
const { NodeProcessEnv } = await import('omni-shared'); | |
const omnitool_exec = 'omnitool'; | |
if (argv0.includes(omnitool_exec)) { | |
console.log('Runtime mode detected..launching'); | |
server_entry = 'dist/server.cjs'; | |
process.env.NODE_ENV ??= 'production'; | |
ensure_wasm(); | |
run_runtime_executable(); | |
} else { | |
// detect help and just show that then exit | |
if (args.includes('--help') || args.includes('-h')) { | |
execSync('node dist/run.js --help', { cwd: 'packages/omni-server', stdio: 'inherit' }); | |
process.exit(0); | |
} | |
console.log(`OMNITOOL Environment ${process.env.NODE_ENV}`); | |
switch (process.env.NODE_ENV) { | |
case NodeProcessEnv.development: | |
server_entry = `dist/run.js`; | |
ensure_wasm(); | |
run_development([]); | |
break; | |
case NodeProcessEnv.production: | |
server_entry = 'dist/run.js'; | |
await check_for_updates(); | |
ensure_wasm(); | |
run_production([]); | |
break; | |
default: | |
throw new Error('Unhandled environment - ' + process.env.NODE_ENV); | |
} | |
} | |
} | |
run(); | |