Plugins: Add auto-update functionality (#3487) * Plugins: Add auto-update functionality * Check if directory is a git repo * Display message if any plugins were loaded

362470da1865e00119cfd5bcb9d55a82f5a6da72

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +83 -2Ignore whitespace
default/config.yaml+2 -0
@@ -210,3 +210,5 @@ claude:
210 cachingAtDepth: -1210 cachingAtDepth: -1
211# -- SERVER PLUGIN CONFIGURATION --211# -- SERVER PLUGIN CONFIGURATION --
212enableServerPlugins: false212enableServerPlugins: false
213# Attempt to automatically update server plugins on startup
214enableServerPluginsAutoUpdate: true
plugins.js+7 -0
@@ -48,6 +48,13 @@ async function updatePlugins() {
48 console.log(`Updating plugin ${color.green(directory)}...`);48 console.log(`Updating plugin ${color.green(directory)}...`);
49 const pluginPath = path.join(pluginsPath, directory);49 const pluginPath = path.join(pluginsPath, directory);
50 const pluginRepo = git(pluginPath);50 const pluginRepo = git(pluginPath);
51
52 const isRepo = await pluginRepo.checkIsRepo();
53 if (!isRepo) {
54 console.log(`Directory ${color.yellow(directory)} is not a Git repository`);
55 continue;
56 }
57
51 await pluginRepo.fetch();58 await pluginRepo.fetch();
52 const commitHash = await pluginRepo.revparse(['HEAD']);59 const commitHash = await pluginRepo.revparse(['HEAD']);
53 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);60 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
src/plugin-loader.js+74 -2
@@ -3,8 +3,12 @@ import path from 'node:path';
3import url from 'node:url';3import url from 'node:url';
44
5import express from 'express';5import express from 'express';
6import { getConfigValue } from './util.js';6import { default as git } from 'simple-git';
7const enableServerPlugins = getConfigValue('enableServerPlugins', false);7import { sync as commandExistsSync } from 'command-exists';
8import { getConfigValue, color } from './util.js';
9
10const enableServerPlugins = !!getConfigValue('enableServerPlugins', false);
11const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true);
812
9/**13/**
10 * Map of loaded plugins.14 * Map of loaded plugins.
@@ -54,6 +58,8 @@ export async function loadPlugins(app, pluginsPath) {
54 return emptyFn;58 return emptyFn;
55 }59 }
5660
61 await updatePlugins(pluginsPath);
62
57 for (const file of files) {63 for (const file of files) {
58 const pluginFilePath = path.join(pluginsPath, file);64 const pluginFilePath = path.join(pluginsPath, file);
5965
@@ -70,6 +76,10 @@ export async function loadPlugins(app, pluginsPath) {
70 await loadFromFile(app, pluginFilePath, exitHooks);76 await loadFromFile(app, pluginFilePath, exitHooks);
71 }77 }
7278
79 if (loadedPlugins.size > 0) {
80 console.log(`${loadedPlugins.size} server plugin(s) are currently loaded. Make sure you know exactly what they do, and only install plugins from trusted sources!`);
81 }
82
73 // Call all plugin "exit" functions at once and wait for them to finish83 // Call all plugin "exit" functions at once and wait for them to finish
74 return () => Promise.all(exitHooks.map(exitFn => exitFn()));84 return () => Promise.all(exitHooks.map(exitFn => exitFn()));
75}85}
@@ -214,3 +224,65 @@ async function initPlugin(app, plugin, exitHooks) {
214224
215 return true;225 return true;
216}226}
227
228/**
229 * Automatically update all git plugins in the ./plugins directory
230 * @param {string} pluginsPath Path to plugins directory
231 */
232async function updatePlugins(pluginsPath) {
233 if (!enableServerPluginsAutoUpdate) {
234 return;
235 }
236
237 const directories = fs.readdirSync(pluginsPath)
238 .filter(file => !file.startsWith('.'))
239 .filter(file => fs.statSync(path.join(pluginsPath, file)).isDirectory());
240
241 if (directories.length === 0) {
242 return;
243 }
244
245 console.log(color.blue('Auto-updating server plugins... Set'), color.yellow('enableServerPluginsAutoUpdate: false'), color.blue('in config.yaml to disable this feature.'));
246
247 if (!commandExistsSync('git')) {
248 console.error(color.red('Git is not installed. Please install Git to enable auto-updating of server plugins.'));
249 return;
250 }
251
252 let pluginsToUpdate = 0;
253
254 for (const directory of directories) {
255 try {
256 const pluginPath = path.join(pluginsPath, directory);
257 const pluginRepo = git(pluginPath);
258
259 const isRepo = await pluginRepo.checkIsRepo();
260 if (!isRepo) {
261 continue;
262 }
263
264 await pluginRepo.fetch();
265 const commitHash = await pluginRepo.revparse(['HEAD']);
266 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
267 const log = await pluginRepo.log({
268 from: commitHash,
269 to: trackingBranch,
270 });
271
272 if (log.total === 0) {
273 continue;
274 }
275
276 pluginsToUpdate++;
277 await pluginRepo.pull();
278 const latestCommit = await pluginRepo.revparse(['HEAD']);
279 console.log(`Plugin ${color.green(directory)} updated to commit ${color.cyan(latestCommit)}`);
280 } catch (error) {
281 console.error(color.red(`Failed to update plugin ${directory}: ${error.message}`));
282 }
283 }
284
285 if (pluginsToUpdate === 0) {
286 console.log('All plugins are up to date.');
287 }
288}