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:
210210 cachingAtDepth: -1
211211# -- SERVER PLUGIN CONFIGURATION --
212212enableServerPlugins: false
213+# Attempt to automatically update server plugins on startup
214+enableServerPluginsAutoUpdate: true
plugins.js+7 -0
@@ -48,6 +48,13 @@ async function updatePlugins() {
4848 console.log(`Updating plugin ${color.green(directory)}...`);
4949 const pluginPath = path.join(pluginsPath, directory);
5050 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+
5158 await pluginRepo.fetch();
5259 const commitHash = await pluginRepo.revparse(['HEAD']);
5360 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
src/plugin-loader.js+74 -2
@@ -3,8 +3,12 @@ import path from 'node:path';
33import url from 'node:url';
44
55import express from 'express';
66import { getConfigValuedefault as git } from './util.jssimple-git';
7-const enableServerPlugins = getConfigValue('enableServerPlugins', false);
7+import { sync as commandExistsSync } from 'command-exists';
8+import { getConfigValue, color } from './util.js';
9+
10+const enableServerPlugins = !!getConfigValue('enableServerPlugins', false);
11+const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true);
812
913/**
1014 * Map of loaded plugins.
@@ -54,6 +58,8 @@ export async function loadPlugins(app, pluginsPath) {
5458 return emptyFn;
5559 }
5660
61+ await updatePlugins(pluginsPath);
62+
5763 for (const file of files) {
5864 const pluginFilePath = path.join(pluginsPath, file);
5965
@@ -70,6 +76,10 @@ export async function loadPlugins(app, pluginsPath) {
7076 await loadFromFile(app, pluginFilePath, exitHooks);
7177 }
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+
7383 // Call all plugin "exit" functions at once and wait for them to finish
7484 return () => Promise.all(exitHooks.map(exitFn => exitFn()));
7585}
@@ -214,3 +224,65 @@ async function initPlugin(app, plugin, exitHooks) {
214224
215225 return true;
216226}
227+
228+/**
229+ * Automatically update all git plugins in the ./plugins directory
230+ * @param {string} pluginsPath Path to plugins directory
231+ */
232+async 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+}