Files
DndGamePlayer/scripts/dev.mjs
T
Ivan Fontosh f4c0ac1438 feat(npcs): add groups, storyline bindings, and Foundry import
Nested NPC groups with color, graph filter, and scene/storyline binding; Foundry worlds/modules import actors into groups; storyline merge asks on NPC name conflicts and reports NPC counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 13:19:22 +08:00

187 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { context } from 'esbuild';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawn } from 'node:child_process';
import http from 'node:http';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = path.resolve(__dirname, '..');
const electronEnv = {
...process.env,
NODE_ENV: 'development',
DND_SKIP_LICENSE: process.env.DND_SKIP_LICENSE ?? '1',
// Совпадает с vite.config.ts `server.host` (не localhost: на Windows он часто → ::1).
VITE_DEV_SERVER_URL: 'http://127.0.0.1:5173/',
};
function spawnShell(command, opts = {}) {
const child = spawn(command, {
cwd: root,
stdio: 'inherit',
shell: true,
...opts,
});
child.on('exit', (code) => {
if (code !== 0 && code !== null) {
process.exitCode = code;
}
});
return child;
}
/** Убивает дерево процессов (на Windows `child.kill()` часто не гасит Vite на 5173). */
function killTree(child) {
if (!child || child.killed) return;
if (typeof child.exitCode === 'number' && child.exitCode !== null) return;
if (process.platform === 'win32' && child.pid) {
spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], {
stdio: 'ignore',
windowsHide: true,
detached: true,
});
} else {
try {
child.kill('SIGTERM');
} catch {
/* ignore */
}
}
}
function waitForVite(url = 'http://127.0.0.1:5173/editor.html', timeoutMs = 60000) {
const started = Date.now();
return new Promise((resolve, reject) => {
const tick = () => {
const req = http.get(url, (res) => {
res.resume();
resolve();
});
req.on('error', () => {
if (Date.now() - started > timeoutMs) {
reject(new Error(`Timed out waiting for Vite at ${url}`));
return;
}
setTimeout(tick, 300);
});
req.setTimeout(2000, () => {
req.destroy();
});
};
tick();
});
}
let shuttingDown = false;
let electron = null;
let vite = null;
let dispose = async () => {};
let restartTimer = null;
let electronStarted = false;
let restartingElectron = false;
async function shutdown() {
if (shuttingDown) return;
shuttingDown = true;
if (restartTimer) clearTimeout(restartTimer);
killTree(vite);
killTree(electron);
await dispose();
process.exit(0);
}
function startElectron() {
if (shuttingDown) return;
restartingElectron = false;
if (electron) killTree(electron);
electron = spawnShell('npx electron .', { env: electronEnv });
electron.once('exit', () => {
if (restartingElectron || shuttingDown) return;
void shutdown();
});
electronStarted = true;
}
function scheduleElectronRestart() {
if (shuttingDown || !electronStarted) return;
if (restartTimer) clearTimeout(restartTimer);
restartTimer = setTimeout(() => {
restartTimer = null;
console.log('[dev] main/preload rebuilt — restarting Electron…');
restartingElectron = true;
startElectron();
}, 300);
}
function createRestartPlugin() {
return {
name: 'restart-electron-on-rebuild',
setup(build) {
build.onEnd((result) => {
if (result.errors.length === 0) scheduleElectronRestart();
});
},
};
}
async function watchMainAndPreload() {
const restartPlugin = createRestartPlugin();
const main = await context({
entryPoints: [path.join(root, 'app/main/index.ts')],
outfile: path.join(root, 'dist/main/index.cjs'),
platform: 'node',
target: 'node22',
format: 'cjs',
bundle: true,
sourcemap: true,
external: ['electron', 'electron-updater', 'sharp', 'ffmpeg-static', 'classic-level'],
define: { 'process.env.NODE_ENV': JSON.stringify('development') },
plugins: [restartPlugin],
});
await main.rebuild();
await main.watch();
const preload = await context({
entryPoints: [path.join(root, 'app/preload/index.ts')],
outfile: path.join(root, 'dist/preload/index.cjs'),
platform: 'node',
target: 'node22',
format: 'cjs',
bundle: true,
sourcemap: true,
external: ['electron'],
define: { 'process.env.NODE_ENV': JSON.stringify('development') },
plugins: [restartPlugin],
});
await preload.rebuild();
await preload.watch();
return async () => {
await Promise.all([main.dispose(), preload.dispose()]);
};
}
dispose = await watchMainAndPreload();
vite = spawnShell('npx vite dev --strictPort', {
env: electronEnv,
});
try {
await waitForVite();
} catch (err) {
console.error('[dev] Vite did not start in time:', err);
}
startElectron();
process.on('SIGINT', () => void shutdown());
process.on('SIGTERM', () => void shutdown());
vite.once('exit', (code) => {
if (code !== 0 && code !== null && !shuttingDown) {
void shutdown();
}
});