| |
|
|
| import fs, { existsSync, watch } from 'fs' |
| import { join, resolve } from 'path' |
| import * as os from 'os' |
| import syntaxerror from 'syntax-error' |
| import importFile from './import.js' |
| import Helper from './helper.js' |
|
|
| const __dirname = Helper.__dirname(import.meta) |
| const rootDirectory = Helper.__dirname(join(__dirname, '../')) |
| const pluginFolder = Helper.__dirname(join(__dirname, '../plugins')) |
| const pluginFilter = filename => /\.(mc)?js$/.test(filename) |
|
|
|
|
| let watcher = {}, |
| plugins = {}, |
| pluginFolders = [] |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function loadPluginFiles( |
| pluginFolder = pluginFolder, |
| pluginFilter = pluginFilter, |
| opts = { recursiveRead: false }) { |
|
|
| const folder = resolve(pluginFolder) |
| if (folder in watcher) return |
| pluginFolders.push(folder) |
|
|
| const paths = await fs.promises.readdir(pluginFolder) |
| await Promise.all(paths.map(async path => { |
| const resolved = join(folder, path) |
| |
| const dirname = Helper.__filename(resolved, true) |
| const formatedFilename = formatFilename(resolved) |
| try { |
| const stats = await fs.promises.lstat(dirname) |
| |
| if (!stats.isFile()) { |
| |
| if (opts.recursiveRead) await loadPluginFiles(dirname, pluginFilter, opts) |
| |
| return |
| } |
|
|
| |
| const filename = Helper.__filename(resolved) |
| const isValidFile = pluginFilter(filename) |
| if (!isValidFile) return |
| const module = await importFile(filename) |
| if (module) plugins[formatedFilename] = module |
| } catch (e) { |
| opts.logger?.error(e, `error while requiring ${formatedFilename}`) |
| delete plugins[formatedFilename] |
| } |
| })) |
|
|
|
|
| const watching = watch(folder, reload.bind(null, { |
| logger: opts.logger, |
| pluginFolder, |
| pluginFilter |
| })) |
| watching.on('close', () => deletePluginFolder(folder, true)) |
| watcher[folder] = watching |
|
|
| return plugins = sortedPlugins(plugins) |
| } |
|
|
| |
| |
| |
| |
| |
| function deletePluginFolder(folder, isAlreadyClosed = false) { |
| const resolved = resolve(folder) |
| if (!(resolved in watcher)) return |
| if (!isAlreadyClosed) watcher[resolved].close() |
| delete watcher[resolved] |
| pluginFolders.splice(pluginFolders.indexOf(resolved), 1) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function reload({ |
| logger, |
| pluginFolder = pluginFolder, |
| pluginFilter = pluginFilter |
| }, _ev, filename) { |
| if (pluginFilter(filename)) { |
| |
| const file = Helper.__filename(join(pluginFolder, filename), true) |
| const formatedFilename = formatFilename(file) |
| if (formatedFilename in plugins) { |
| if (existsSync(file)) logger?.info(`updated plugin - '${formatedFilename}'`) |
| else { |
| logger?.warn(`deleted plugin - '${formatedFilename}'`) |
| return delete plugins[formatedFilename] |
| } |
| } else logger?.info(`new plugin - '${formatedFilename}'`) |
| const src = await fs.promises.readFile(file) |
| |
| let err = syntaxerror(src, filename, { |
| sourceType: 'module', |
| allowAwaitOutsideFunction: true |
| }) |
| if (err) logger?.error(err, `syntax error while loading '${formatedFilename}'`) |
| else try { |
| const module = await importFile(file) |
| if (module) plugins[formatedFilename] = module |
| } catch (e) { |
| logger?.error(e, `error require plugin '${formatedFilename}'`) |
| delete plugins[formatedFilename] |
| } finally { |
| plugins = sortedPlugins(plugins) |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function formatFilename(filename) { |
| let dir = join(rootDirectory, './') |
| |
| if (os.platform() === 'win32') dir = dir.replace(/\\/g, '\\\\') |
| |
| const regex = new RegExp(`^${dir}`) |
| const formated = filename.replace(regex, '') |
| return formated |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function sortedPlugins(plugins) { |
| return Object.fromEntries(Object.entries(plugins).sort(([a], [b]) => a.localeCompare(b))) |
| } |
|
|
| export { |
| pluginFolder, |
| pluginFilter, |
|
|
| plugins, |
| watcher, |
| pluginFolders, |
|
|
| loadPluginFiles, |
| deletePluginFolder, |
| reload |
| } |
|
|