diff --git a/app/src/plugins.ts b/app/src/plugins.ts index c313d5af73..dfc644707d 100644 --- a/app/src/plugins.ts +++ b/app/src/plugins.ts @@ -92,25 +92,13 @@ export function initModuleLookup (userPluginsPath: string): void { }) } -export async function findPlugins (): Promise { - const paths = nodeModule.globalPaths - let foundPlugins: PluginInfo[] = [] - const candidateLocations: { pluginDir: string, packageName: string }[] = [] - const PREFIX = 'tabby-' - const LEGACY_PREFIX = 'terminus-' +const PLUGIN_PREFIX = 'tabby-' +const LEGACY_PLUGIN_PREFIX = 'terminus-' - const processedPaths = [] - - for (let pluginDir of paths) { - if (processedPaths.includes(pluginDir)) { - continue - } - processedPaths.push(pluginDir) +async function getCandidateLocationsInPluginDir (pluginDir: any): Promise<{ pluginDir: string, packageName: string }[]> { + const candidateLocations: { pluginDir: string, packageName: string }[] = [] - pluginDir = normalizePath(pluginDir) - if (!await fs.exists(pluginDir)) { - continue - } + if (await fs.exists(pluginDir)) { const pluginNames = await fs.readdir(pluginDir) if (await fs.exists(path.join(pluginDir, 'package.json'))) { candidateLocations.push({ @@ -118,59 +106,117 @@ export async function findPlugins (): Promise { packageName: path.basename(pluginDir), }) } + + const promises = [] + for (const packageName of pluginNames) { - if ((packageName.startsWith(PREFIX) || packageName.startsWith(LEGACY_PREFIX)) && !PLUGIN_BLACKLIST.includes(packageName)) { - candidateLocations.push({ pluginDir, packageName }) + if ((packageName.startsWith(PLUGIN_PREFIX) || packageName.startsWith(LEGACY_PLUGIN_PREFIX)) && !PLUGIN_BLACKLIST.includes(packageName)) { + const pluginPath = path.join(pluginDir, packageName) + const infoPath = path.join(pluginPath, 'package.json') + promises.push(fs.exists(infoPath).then(result => { + if (result) { + candidateLocations.push({ pluginDir, packageName }) + } + })) } } + + await Promise.all(promises) } - for (const { pluginDir, packageName } of candidateLocations) { - const pluginPath = path.join(pluginDir, packageName) - const infoPath = path.join(pluginPath, 'package.json') - if (!await fs.exists(infoPath)) { + return candidateLocations +} + +async function getPluginCandidateLocation (paths: any): Promise<{ pluginDir: string, packageName: string }[]> { + const candidateLocationsPromises: Promise<{ pluginDir: string, packageName: string }[]>[] = [] + + const processedPaths = [] + + for (let pluginDir of paths) { + if (processedPaths.includes(pluginDir)) { continue } + processedPaths.push(pluginDir) - const name = packageName.startsWith(PREFIX) ? packageName.substring(PREFIX.length) : packageName.substring(LEGACY_PREFIX.length) + pluginDir = normalizePath(pluginDir) - if (builtinModules.includes(packageName) && pluginDir !== builtinPluginsPath) { - continue + candidateLocationsPromises.push(getCandidateLocationsInPluginDir(pluginDir)) + + } + + const candidateLocations: { pluginDir: string, packageName: string }[] = [] + for (const pluginCandidateLocations of await Promise.all(candidateLocationsPromises)) { + candidateLocations.push(...pluginCandidateLocations) + } + + return candidateLocations +} + +async function parsePluginInfo (pluginDir: string, packageName: string): Promise { + const pluginPath = path.join(pluginDir, packageName) + const infoPath = path.join(pluginPath, 'package.json') + + const name = packageName.startsWith(PLUGIN_PREFIX) ? packageName.substring(PLUGIN_PREFIX.length) : packageName.substring(LEGACY_PLUGIN_PREFIX.length) + + try { + const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' })) + + if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin') || info.keywords.includes('tabby-plugin') || info.keywords.includes('tabby-builtin-plugin'))) { + return null } + let author = info.author + author = author.name || author + console.log(`Found ${name} in ${pluginDir}`) - const existing = foundPlugins.find(x => x.name === name) - if (existing) { - if (existing.isLegacy) { - console.info(`Plugin ${packageName} already exists, overriding`) - foundPlugins = foundPlugins.filter(x => x.name !== name) - } else { - console.info(`Plugin ${packageName} already exists, skipping`) - continue - } + return { + name: name, + packageName: packageName, + isBuiltin: pluginDir === builtinPluginsPath, + isLegacy: info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'), + version: info.version, + description: info.description, + author, + path: pluginPath, + info, } + } catch (error) { + console.error('Cannot load package info for', packageName) + return null + } +} - try { - const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' })) - if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin') || info.keywords.includes('tabby-plugin') || info.keywords.includes('tabby-builtin-plugin'))) { - continue +export async function findPlugins (): Promise { + const paths = nodeModule.globalPaths + let foundPlugins: PluginInfo[] = [] + + const candidateLocations: { pluginDir: string, packageName: string }[] = await getPluginCandidateLocation(paths) + + const foundPluginsPromises: Promise[] = [] + for (const { pluginDir, packageName } of candidateLocations) { + + if (builtinModules.includes(packageName) && pluginDir !== builtinPluginsPath) { + continue + } + + foundPluginsPromises.push(parsePluginInfo(pluginDir, packageName)) + } + + for (const pluginInfo of await Promise.all(foundPluginsPromises)) { + if (pluginInfo) { + const existing = foundPlugins.find(x => x.name === pluginInfo.name) + if (existing) { + if (existing.isLegacy) { + console.info(`Plugin ${pluginInfo.packageName} already exists, overriding`) + foundPlugins = foundPlugins.filter(x => x.name !== pluginInfo.name) + } else { + console.info(`Plugin ${pluginInfo.packageName} already exists, skipping`) + continue + } } - let author = info.author - author = author.name || author - foundPlugins.push({ - name: name, - packageName: packageName, - isBuiltin: pluginDir === builtinPluginsPath, - isLegacy: info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'), - version: info.version, - description: info.description, - author, - path: pluginPath, - info, - }) - } catch (error) { - console.error('Cannot load package info for', packageName) + + foundPlugins.push(pluginInfo) } } @@ -181,26 +227,36 @@ export async function findPlugins (): Promise { export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise { const plugins: any[] = [] - progress(0, 1) + const pluginsPromises: Promise[] = [] + let index = 0 - for (const foundPlugin of foundPlugins) { - console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`) + const setProgress = function () { + index++ progress(index, foundPlugins.length) - try { - const packageModule = nodeRequire(foundPlugin.path) - if (foundPlugin.packageName.startsWith('tabby-')) { - cachedBuiltinModules[foundPlugin.packageName.replace('tabby-', 'terminus-')] = packageModule + } + + progress(0, 1) + for (const foundPlugin of foundPlugins) { + pluginsPromises.push(new Promise(x => { + console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`) + try { + const packageModule = nodeRequire(foundPlugin.path) + if (foundPlugin.packageName.startsWith('tabby-')) { + cachedBuiltinModules[foundPlugin.packageName.replace('tabby-', 'terminus-')] = packageModule + } + const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default + pluginModule.pluginName = foundPlugin.name + pluginModule.bootstrap = packageModule.bootstrap + plugins.push(pluginModule) + setTimeout(x, 50) + } catch (error) { + console.error(`Could not load ${foundPlugin.name}:`, error) } - const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default - pluginModule.pluginName = foundPlugin.name - pluginModule.bootstrap = packageModule.bootstrap - plugins.push(pluginModule) - await new Promise(x => setTimeout(x, 50)) - } catch (error) { - console.error(`Could not load ${foundPlugin.name}:`, error) - } - index++ + setProgress() + })) } progress(1, 1) + + await Promise.all(pluginsPromises) return plugins }