fix: admin/plugins/updates: empty list if HFS is restarted while the page is open

Massimo Melina committed Dec 2, 2025 at 11:25 UTC a2dcfc33b3acafff28f58c0b5c036383edf8f1fb
2 files changed +8 -5
src/api.plugins.ts
+2 -1
@@ -2,7 +2,7 @@
2
3 import {
4 InactivePlugin, enablePlugins, getInactivePlugins, getPluginConfigFields, mapPlugins, Plugin, pluginsConfig,
5 - PATH as PLUGINS_PATH, enablePlugin, getPluginInfo, setPluginConfig, isPluginRunning,
5 + PATH as PLUGINS_PATH, enablePlugin, getPluginInfo, setPluginConfig, isPluginRunning, pluginsScanned,
6 stopPlugin, startPlugin, CommonPluginInterface, getMissingDependencies, findPluginByRepo, suspendPlugins,
7 } from './plugins'
8 import _ from 'lodash'
@@ -43,6 +43,7 @@ const apis: ApiHandlers = {
43 list.update({ id }, { updated: true })
44 }
45 })
46 + await pluginsScanned
47 await Promise.allSettled(_.map(getFolder2repo(), async (repo, folder) => {
48 try {
49 if (!repo) return
src/plugins.ts
+6 -4
@@ -9,7 +9,7 @@ import {
9 import * as Const from './const'
10 import Koa from 'koa'
11 import {
12 - adjustStaticPathForGlob, callable, Callback, CFG, debounceAsync, Dict, objSameKeys, onlyTruthy, prefix,
12 + escapeGlobPath, callable, Callback, CFG, debounceAsync, Dict, objSameKeys, onlyTruthy, prefix,
13 PendingPromise, pendingPromise, Promisable, same, tryJson, wait, waitFor, wantArray, watchDir, objFromKeys, patchKey
14 } from './misc'
15 import * as misc from './misc'
@@ -39,6 +39,7 @@ export const PATH = 'plugins'
39 export const DISABLING_SUFFIX = '-disabled'
40 export const DELETE_ME_SUFFIX = '-delete_me' + DISABLING_SUFFIX
41 export const STORAGE_FOLDER = 'storage'
42 +export const pluginsScanned = pendingPromise()
43
44 setTimeout(async () => { // delete leftovers, if any
45 for (const x of await readdir(PATH))
@@ -415,20 +416,21 @@ export async function rescan() {
416 const patterns = [PATH + '/*']
417 if (APP_PATH !== process.cwd())
418 patterns.unshift(escapeGlobPath(APP_PATH) + '/' + patterns[0]) // first search bundled plugins, because otherwise they won't be loaded because of the folders with same name in .hfs/plugins (used for storage)
418 - const met = []
419 + const existing = []
420 for (const { path, dirent } of await glob(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
421 if (!dirent.isDirectory() || path.endsWith(DISABLING_SUFFIX)) continue
422 const id = path.split('/').slice(-1)[0]!
422 - met.push(id)
423 + existing.push(id)
424 if (!pluginWatchers.has(id))
425 pluginWatchers.set(id, watchPlugin(id, join(path, PLUGIN_MAIN_FILE)))
426 }
427 for (const [id, cancelWatcher] of pluginWatchers.entries())
427 - if (!met.includes(id)) {
428 + if (!existing.includes(id)) {
429 enablePlugin(id, false)
430 cancelWatcher()
431 pluginWatchers.delete(id)
432 }
433 + pluginsScanned.resolve()
434 }
435
436 function watchPlugin(id: string, path: string) {