Files
botsu.app/scripts/dev.mjs

93 lines
2.9 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',
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;
}
});
}