94 lines
3.0 KiB
JavaScript
94 lines
3.0 KiB
JavaScript
import { existsSync, readFileSync } from 'node:fs';
|
|
import { isAbsolute, resolve } from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
|
|
const root = resolve(import.meta.dirname, '..');
|
|
const localEnvPath = resolve(root, '.env.local');
|
|
|
|
const parseEnvFile = (path) => {
|
|
if (!existsSync(path)) return {};
|
|
const result = {};
|
|
for (const rawLine of readFileSync(path, 'utf8').split(/\r?\n/u)) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith('#')) continue;
|
|
const separator = line.indexOf('=');
|
|
if (separator < 1) throw new Error(`Invalid environment line in ${path}: ${rawLine}`);
|
|
const key = line.slice(0, separator).trim();
|
|
let value = line.slice(separator + 1).trim();
|
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
result[key] = value;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const localEnv = parseEnvFile(localEnvPath);
|
|
const pathDefaults = {
|
|
BOTSU_DRAWING_STORE_ROOT: '.data/drawings',
|
|
BOTSU_DOCUMENT_STORE_ROOT: '.data/documents',
|
|
BOTSU_COOKIE_STORE_ROOT: '.data/cookies',
|
|
BOTSU_TCG_STORE_ROOT: '.data/tcg',
|
|
BOTSU_MOODBOARD_STORE_ROOT: '.data/moodboard',
|
|
BOTSU_RADIO_STORE_ROOT: '.data/radio',
|
|
TCG_CATALOG_INDEX_PATH: '.data/tcg/catalog-index.json',
|
|
EMOJI_KITCHEN_ROOT: '.data/emoji-kitchen/stickers',
|
|
};
|
|
|
|
const env = {
|
|
BOTSU_DEV_HOST: '127.0.0.1',
|
|
BOTSU_DEV_PORT: '5173',
|
|
BOTSU_DEV_ALLOWED_HOSTS: 'localhost,127.0.0.1',
|
|
BOTSU_PRESENCE_PROXY_URL: 'http://127.0.0.1:8091',
|
|
HOST: '127.0.0.1',
|
|
PORT: '8091',
|
|
PRESENCE_ALLOWED_ORIGIN: 'http://127.0.0.1:5173,http://localhost:5173',
|
|
MATRIX_HOMESERVER: 'botsu.net',
|
|
...localEnv,
|
|
...process.env,
|
|
};
|
|
|
|
for (const [key, value] of Object.entries(pathDefaults)) {
|
|
const configured = env[key] ?? value;
|
|
env[key] = isAbsolute(configured) ? configured : resolve(root, configured);
|
|
}
|
|
|
|
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
const children = [
|
|
spawn(npm, ['run', 'dev:api'], { cwd: root, env, stdio: 'inherit', detached: process.platform !== 'win32' }),
|
|
spawn(npm, ['run', 'dev:client'], { cwd: root, env, stdio: 'inherit', detached: process.platform !== 'win32' }),
|
|
];
|
|
|
|
let stopping = false;
|
|
const stop = (signal = 'SIGTERM') => {
|
|
if (stopping) return;
|
|
stopping = true;
|
|
for (const child of children) {
|
|
if (!child.pid || child.exitCode !== null) continue;
|
|
try {
|
|
process.kill(process.platform === 'win32' ? child.pid : -child.pid, signal);
|
|
} catch (error) {
|
|
if (error.code !== 'ESRCH') throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
process.once(signal, () => stop(signal));
|
|
}
|
|
|
|
for (const child of children) {
|
|
child.once('error', (error) => {
|
|
console.error(error);
|
|
stop();
|
|
process.exitCode = 1;
|
|
});
|
|
child.once('exit', (code, signal) => {
|
|
if (!stopping) {
|
|
if (code !== 0) console.error(`Local service stopped (${signal ?? `exit ${code}`}).`);
|
|
stop();
|
|
process.exitCode = code ?? 1;
|
|
}
|
|
});
|
|
}
|