fix: lint errors and deleting plugin error on windows

This commit is contained in:
viper151
2026-03-06 15:44:32 +01:00
parent 38accf6505
commit 24430fa343
10 changed files with 176 additions and 156 deletions

View File

@@ -249,7 +249,7 @@ router.all('/:name/rpc/*', async (req, res) => {
});
// DELETE /:name — Uninstall plugin (stops server first)
router.delete('/:name', (req, res) => {
router.delete('/:name', async (req, res) => {
try {
const pluginName = req.params.name;
@@ -258,12 +258,12 @@ router.delete('/:name', (req, res) => {
return res.status(400).json({ error: 'Invalid plugin name' });
}
// Stop server if running
// Stop server and wait for the process to fully exit before deleting files
if (isPluginRunning(pluginName)) {
stopPluginServer(pluginName);
await stopPluginServer(pluginName);
}
uninstallPlugin(pluginName);
await uninstallPlugin(pluginName);
res.json({ success: true, name: pluginName });
} catch (err) {
res.status(400).json({ error: 'Failed to uninstall plugin', details: err.message });

View File

@@ -326,13 +326,28 @@ export function updatePluginFromGit(name) {
});
}
export function uninstallPlugin(name) {
export async function uninstallPlugin(name) {
const pluginDir = getPluginDir(name);
if (!pluginDir) {
throw new Error(`Plugin "${name}" not found`);
}
fs.rmSync(pluginDir, { recursive: true, force: true });
// On Windows, file handles may be released slightly after process exit.
// Retry a few times with a short delay before giving up.
const MAX_RETRIES = 5;
const RETRY_DELAY_MS = 500;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
fs.rmSync(pluginDir, { recursive: true, force: true });
break;
} catch (err) {
if (err.code === 'EBUSY' && attempt < MAX_RETRIES) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
} else {
throw err;
}
}
}
// Remove from config
const config = getPluginsConfig();

View File

@@ -93,26 +93,33 @@ export function startPluginServer(name, pluginDir, serverEntry) {
/**
* Stop a plugin's server subprocess.
* Returns a Promise that resolves when the process has fully exited.
*/
export function stopPluginServer(name) {
const entry = runningPlugins.get(name);
if (!entry) return;
if (!entry) return Promise.resolve();
entry.process.kill('SIGTERM');
// Force kill after 5 seconds if still running
const forceKillTimer = setTimeout(() => {
if (runningPlugins.has(name)) {
entry.process.kill('SIGKILL');
return new Promise((resolve) => {
const cleanup = () => {
clearTimeout(forceKillTimer);
runningPlugins.delete(name);
}
}, 5000);
resolve();
};
entry.process.on('exit', () => {
clearTimeout(forceKillTimer);
entry.process.once('exit', cleanup);
entry.process.kill('SIGTERM');
// Force kill after 5 seconds if still running
const forceKillTimer = setTimeout(() => {
if (runningPlugins.has(name)) {
entry.process.kill('SIGKILL');
cleanup();
}
}, 5000);
console.log(`[Plugins] Server stopped for "${name}"`);
});
console.log(`[Plugins] Server stopped for "${name}"`);
}
/**
@@ -133,9 +140,11 @@ export function isPluginRunning(name) {
* Stop all running plugin servers (called on host shutdown).
*/
export function stopAllPlugins() {
const stops = [];
for (const [name] of runningPlugins) {
stopPluginServer(name);
stops.push(stopPluginServer(name));
}
return Promise.all(stops);
}
/**