fix: same-id plugin could be loading more than once at a time, causing "plugin error: unload first"
Massimo Melina committed
Mar 27, 2023 at 10:28 UTC
544c93fa8172ee1743d7224f17d0ec8897a80cca
1 file changed
+63
-59
src/plugins.ts
+63
-59
@@ -206,7 +206,7 @@ export const pluginsConfig = defineConfig('plugins_config', {} as Record<string,
206
207
export async function rescan() {
208
console.debug('scanning plugins')
209
- const found = []
209
+ const found: string[] = []
210
const foundDisabled: typeof availablePlugins = {}
211
const MASK = PATH + '/*/plugin.js' // be sure to not use path.join as fast-glob doesn't work with \
212
const pluginSources = [MASK]
@@ -223,65 +223,10 @@ export async function rescan() {
223
catch {}
224
continue
225
}
226
- found.push(id)
227
- if (plugins[id]) // already loaded
226
+ if (found.includes(id)) // not twice
227
continue
229
- const module = resolve(f)
230
- const { unwatch } = watchLoad(f, async () => {
231
- try {
232
- const alreadyRunning = plugins[id]
233
- console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id)
234
- const { init, ...data } = await import(module)
235
- delete data.default
236
- deleteModule(require.resolve(module)) // avoid caching at next import
237
- calculateBadApi(data)
238
- if (data.badApi)
239
- console.log("plugin", id, data.badApi)
240
-
241
- await alreadyRunning?.unload(true)
242
- console.debug("starting plugin", id)
243
- const res = await init?.call(null, {
244
- srcDir: __dirname,
245
- const: Const,
246
- require,
247
- getConnections,
248
- events,
249
- log(...args: any[]) {
250
- console.log('plugin', id, ':', ...args)
251
- },
252
- getConfig: (cfgKey: string) =>
253
- pluginsConfig.get()?.[id]?.[cfgKey] ?? data.config?.[cfgKey]?.defaultValue,
254
- setConfig: (cfgKey: string, value: any) =>
255
- setPluginConfig(id, { [cfgKey]: value }),
256
- subscribeConfig(cfgKey: string, cb: Callback<any>) {
257
- let last = this.getConfig(cfgKey)
258
- cb(last)
259
- return pluginsConfig.sub(() => {
260
- const now = this.getConfig(cfgKey)
261
- if (same(now, last)) return
262
- try { cb(last = now) }
263
- catch(e){
264
- console.log('plugin', id, String(e))
265
- }
266
- })
267
- },
268
- getHfsConfig: getConfig,
269
- })
270
- Object.assign(data, res)
271
- const plugin = new Plugin(id, dirname(module), data, unwatch)
272
- if (alreadyRunning)
273
- events.emit('pluginUpdated', Object.assign(_.pick(plugin, 'started'), getPluginInfo(id)))
274
- else {
275
- const wasInstalled = availablePlugins[id]
276
- if (wasInstalled)
277
- delete availablePlugins[id]
278
- events.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin)
279
- }
280
-
281
- } catch (e) {
282
- console.log("plugin error:", e)
283
- }
284
- })
228
+ found.push(id)
229
+ loadPlugin(id, f)
230
}
231
for (const [id,p] of Object.entries(foundDisabled)) {
232
const a = availablePlugins[id]
@@ -302,6 +247,65 @@ export async function rescan() {
247
await p.unload()
248
}
249
250
+function loadPlugin(id: string, path: string) {
251
+ const module = resolve(path)
252
+ const { unwatch } = watchLoad(path, async () => {
253
+ try {
254
+ const alreadyRunning = plugins[id]
255
+ console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id)
256
+ const { init, ...data } = await import(module)
257
+ delete data.default
258
+ deleteModule(require.resolve(module)) // avoid caching at next import
259
+ calculateBadApi(data)
260
+ if (data.badApi)
261
+ console.log("plugin", id, data.badApi)
262
+
263
+ await alreadyRunning?.unload(true)
264
+ console.debug("starting plugin", id)
265
+ const res = await init?.call(null, {
266
+ srcDir: __dirname,
267
+ const: Const,
268
+ require,
269
+ getConnections,
270
+ events,
271
+ log(...args: any[]) {
272
+ console.log('plugin', id, ':', ...args)
273
+ },
274
+ getConfig: (cfgKey: string) =>
275
+ pluginsConfig.get()?.[id]?.[cfgKey] ?? data.config?.[cfgKey]?.defaultValue,
276
+ setConfig: (cfgKey: string, value: any) =>
277
+ setPluginConfig(id, { [cfgKey]: value }),
278
+ subscribeConfig(cfgKey: string, cb: Callback<any>) {
279
+ let last = this.getConfig(cfgKey)
280
+ cb(last)
281
+ return pluginsConfig.sub(() => {
282
+ const now = this.getConfig(cfgKey)
283
+ if (same(now, last)) return
284
+ try { cb(last = now) }
285
+ catch(e){
286
+ console.log('plugin', id, String(e))
287
+ }
288
+ })
289
+ },
290
+ getHfsConfig: getConfig,
291
+ })
292
+ Object.assign(data, res)
293
+ const plugin = new Plugin(id, dirname(module), data, unwatch)
294
+ if (alreadyRunning)
295
+ events.emit('pluginUpdated', Object.assign(_.pick(plugin, 'started'), getPluginInfo(id)))
296
+ else {
297
+ const wasInstalled = availablePlugins[id]
298
+ if (wasInstalled)
299
+ delete availablePlugins[id]
300
+ events.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin)
301
+ }
302
+
303
+ } catch (e) {
304
+ console.log("plugin error:", e)
305
+ }
306
+ })
307
+}
308
+
309
function deleteModule(id: string) {
310
const { cache } = require
311
// build reversed map of dependencies