Restore the v2.1.19 application baseline and retain only the focused import preflight summary with duplicate, unavailable, destination, job, and size-limit visibility.
This commit is contained in:
+66
-192
@@ -6,101 +6,19 @@ const chokidar = require('chokidar');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const electron = require('electron');
|
||||
const lockPath = path.join(root, '.dev-runner.lock');
|
||||
const watched = [
|
||||
'main.js',
|
||||
'preload.js',
|
||||
'preload-drop-target.js',
|
||||
path.join(root, 'lib'),
|
||||
path.join(root, 'renderer')
|
||||
].map(target => path.isAbsolute(target) ? target : path.join(root, target));
|
||||
|
||||
let child = null;
|
||||
let restartTimer = null;
|
||||
let stopping = false;
|
||||
let lockHandle = null;
|
||||
|
||||
function createWatchedPaths(projectRoot) {
|
||||
return [
|
||||
'main.js',
|
||||
'preload.js',
|
||||
'preload-drop-target.js',
|
||||
'lib',
|
||||
'renderer'
|
||||
].map(target => path.join(projectRoot, target));
|
||||
}
|
||||
|
||||
function formatChangeMessage(file) {
|
||||
return `[hotdev] change detected: ${file}\n`;
|
||||
}
|
||||
|
||||
function createRestartController({ startChild, stopChild, onUnexpectedExit = () => {} }) {
|
||||
let child = null;
|
||||
let terminatingChild = null;
|
||||
let restartScheduled = false;
|
||||
let restartWaiters = [];
|
||||
let cycleDone = Promise.resolve();
|
||||
let stopping = false;
|
||||
let shutdownPromise = null;
|
||||
|
||||
function start() {
|
||||
if (stopping || child) return child;
|
||||
const startedChild = startChild();
|
||||
child = startedChild;
|
||||
startedChild.once('exit', (code, signal) => {
|
||||
if (child !== startedChild) return;
|
||||
const expectedExit = stopping || terminatingChild === startedChild;
|
||||
child = null;
|
||||
if (!expectedExit && code !== 0 && signal !== 'SIGTERM') onUnexpectedExit(code, signal);
|
||||
});
|
||||
return startedChild;
|
||||
}
|
||||
|
||||
async function stopCurrentChild() {
|
||||
const target = child;
|
||||
if (!target || !target.pid) return;
|
||||
terminatingChild = target;
|
||||
try {
|
||||
await stopChild(target);
|
||||
} catch (error) {
|
||||
if (child === target) throw error;
|
||||
} finally {
|
||||
if (terminatingChild === target) terminatingChild = null;
|
||||
}
|
||||
if (child === target) child = null;
|
||||
}
|
||||
|
||||
async function runRestartCycle() {
|
||||
const waiters = restartWaiters;
|
||||
restartWaiters = [];
|
||||
try {
|
||||
await stopCurrentChild();
|
||||
waiters.push(...restartWaiters);
|
||||
restartWaiters = [];
|
||||
if (!stopping) start();
|
||||
for (const waiter of waiters) waiter.resolve();
|
||||
} catch (error) {
|
||||
waiters.push(...restartWaiters);
|
||||
restartWaiters = [];
|
||||
for (const waiter of waiters) waiter.reject(error);
|
||||
} finally {
|
||||
restartScheduled = false;
|
||||
if (restartWaiters.length > 0 && !stopping) beginRestartCycle();
|
||||
}
|
||||
}
|
||||
|
||||
function beginRestartCycle() {
|
||||
restartScheduled = true;
|
||||
cycleDone = runRestartCycle();
|
||||
}
|
||||
|
||||
function restart() {
|
||||
if (stopping) return shutdownPromise || Promise.resolve();
|
||||
const requested = new Promise((resolve, reject) => {
|
||||
restartWaiters.push({ resolve, reject });
|
||||
});
|
||||
if (!restartScheduled) beginRestartCycle();
|
||||
return requested;
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
if (shutdownPromise) return shutdownPromise;
|
||||
stopping = true;
|
||||
shutdownPromise = cycleDone.then(stopCurrentChild);
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
return { restart, shutdown, start };
|
||||
}
|
||||
|
||||
function processExists(pid) {
|
||||
if (!Number.isInteger(pid) || pid <= 0) return false;
|
||||
try {
|
||||
@@ -138,115 +56,71 @@ function releaseLock() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function startElectron() {
|
||||
return spawn(electron, ['.', '--dev'], {
|
||||
function startApp() {
|
||||
const startedChild = spawn(electron, ['.', '--dev'], {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
windowsHide: false
|
||||
});
|
||||
}
|
||||
|
||||
function stopProcessTree(child) {
|
||||
if (process.platform === 'win32') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], {
|
||||
stdio: 'ignore',
|
||||
windowsHide: true
|
||||
});
|
||||
killer.once('error', reject);
|
||||
killer.once('close', (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
reject(new Error(`taskkill failed for Electron PID ${child.pid}: ${code ?? signal ?? 'unknown'}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const handleExit = () => resolve();
|
||||
child.once('exit', handleExit);
|
||||
try {
|
||||
process.kill(child.pid, 'SIGTERM');
|
||||
} catch (error) {
|
||||
child.removeListener('exit', handleExit);
|
||||
reject(error);
|
||||
}
|
||||
child = startedChild;
|
||||
startedChild.once('exit', (code, signal) => {
|
||||
if (child !== startedChild) return;
|
||||
child = null;
|
||||
if (!stopping && code !== 0 && signal !== 'SIGTERM') process.exitCode = code || 1;
|
||||
});
|
||||
}
|
||||
|
||||
function runDevRunner() {
|
||||
if (!acquireLock()) {
|
||||
process.stderr.write('A Multi-Hoster hot-dev runner is already active.\n');
|
||||
process.exit(0);
|
||||
function stopApp(done) {
|
||||
if (!child || !child.pid) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = createRestartController({
|
||||
startChild: startElectron,
|
||||
stopChild: stopProcessTree,
|
||||
onUnexpectedExit(code) {
|
||||
process.exitCode = code || 1;
|
||||
}
|
||||
});
|
||||
const watcher = chokidar.watch(createWatchedPaths(root), {
|
||||
ignoreInitial: true,
|
||||
usePolling: true,
|
||||
interval: 100,
|
||||
awaitWriteFinish: { stabilityThreshold: 250, pollInterval: 50 }
|
||||
});
|
||||
let restartTimer = null;
|
||||
let shutdownPromise = null;
|
||||
|
||||
function reportRestartFailure(error) {
|
||||
process.stderr.write(`[hotdev] restart failed: ${error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
const pid = child.pid;
|
||||
child = null;
|
||||
if (process.platform === 'win32') {
|
||||
const killer = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore', windowsHide: true });
|
||||
killer.once('close', done);
|
||||
return;
|
||||
}
|
||||
|
||||
function scheduleRestart() {
|
||||
clearTimeout(restartTimer);
|
||||
restartTimer = setTimeout(() => {
|
||||
restartTimer = null;
|
||||
controller.restart().catch(reportRestartFailure);
|
||||
}, 180);
|
||||
}
|
||||
|
||||
watcher.on('all', (_event, file) => {
|
||||
process.stdout.write(formatChangeMessage(file));
|
||||
scheduleRestart();
|
||||
});
|
||||
|
||||
function shutdown() {
|
||||
if (shutdownPromise) return shutdownPromise;
|
||||
clearTimeout(restartTimer);
|
||||
const appShutdown = controller.shutdown();
|
||||
shutdownPromise = Promise.allSettled([
|
||||
Promise.resolve().then(() => watcher.close()),
|
||||
appShutdown
|
||||
]).then(results => {
|
||||
const failure = results.find(result => result.status === 'rejected');
|
||||
releaseLock();
|
||||
if (failure) {
|
||||
process.stderr.write(`[hotdev] shutdown failed: ${failure.reason.message}\n`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
process.once('SIGINT', shutdown);
|
||||
process.once('SIGTERM', shutdown);
|
||||
process.once('exit', () => {
|
||||
clearTimeout(restartTimer);
|
||||
controller.shutdown().catch(() => {});
|
||||
releaseLock();
|
||||
});
|
||||
|
||||
controller.start();
|
||||
process.kill(pid, 'SIGTERM');
|
||||
done();
|
||||
}
|
||||
|
||||
module.exports = { createRestartController, createWatchedPaths, formatChangeMessage };
|
||||
function restartApp() {
|
||||
if (stopping) return;
|
||||
stopApp(startApp);
|
||||
}
|
||||
|
||||
if (require.main === module) runDevRunner();
|
||||
function scheduleRestart() {
|
||||
clearTimeout(restartTimer);
|
||||
restartTimer = setTimeout(restartApp, 180);
|
||||
}
|
||||
|
||||
if (!acquireLock()) {
|
||||
process.stderr.write('A Multi-Hoster hot-dev runner is already active.\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const watcher = chokidar.watch(watched, {
|
||||
ignoreInitial: true,
|
||||
usePolling: true,
|
||||
interval: 100,
|
||||
awaitWriteFinish: { stabilityThreshold: 250, pollInterval: 50 }
|
||||
});
|
||||
watcher.on('all', (_event, file) => {
|
||||
process.stdout.write(`[hotdev] renderer change detected: ${file}\n`);
|
||||
scheduleRestart();
|
||||
});
|
||||
|
||||
function shutdown() {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
clearTimeout(restartTimer);
|
||||
watcher.close().finally(() => stopApp(() => { releaseLock(); process.exit(0); }));
|
||||
}
|
||||
|
||||
process.once('SIGINT', shutdown);
|
||||
process.once('SIGTERM', shutdown);
|
||||
process.once('exit', () => { stopping = true; releaseLock(); });
|
||||
|
||||
startApp();
|
||||
|
||||
Reference in New Issue
Block a user