1
1
mirror of https://github.com/Eugeny/tabby.git synced 2025-01-01 15:11:16 +03:00
tabby/app/src/plugins.ts

205 lines
7.6 KiB
TypeScript
Raw Normal View History

2017-06-28 00:35:19 +03:00
import * as fs from 'mz/fs'
2017-04-15 16:20:18 +03:00
import * as path from 'path'
2021-03-20 19:12:39 +03:00
import * as remote from '@electron/remote'
2021-06-30 00:57:04 +03:00
import { PluginInfo } from '../../tabby-core/src/api/mainProcess'
import { PLUGIN_BLACKLIST } from './pluginBlacklist'
2021-06-19 02:36:25 +03:00
2019-06-15 00:47:48 +03:00
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
2021-06-19 02:36:25 +03:00
const nodeRequire = global['require']
2017-04-17 15:57:22 +03:00
2021-01-02 22:53:34 +03:00
function normalizePath (p: string): string {
2017-04-17 15:57:22 +03:00
const cygwinPrefix = '/cygdrive/'
2021-01-02 22:53:34 +03:00
if (p.startsWith(cygwinPrefix)) {
p = p.substring(cygwinPrefix.length).replace('/', '\\')
p = p[0] + ':' + p.substring(1)
2017-04-17 15:57:22 +03:00
}
2021-01-02 22:53:34 +03:00
return p
2017-05-01 14:35:26 +03:00
}
2017-04-17 15:57:22 +03:00
2021-06-30 00:57:04 +03:00
const builtinPluginsPath = process.env.TABBY_DEV ? path.dirname(remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
2017-06-01 23:23:36 +03:00
2021-07-06 22:22:57 +03:00
const cachedBuiltinModules = {
'@angular/animations': require('@angular/animations'),
2022-02-02 00:47:07 +03:00
'@angular/cdk/drag-drop': require('@angular/cdk/drag-drop'),
'@angular/cdk/clipboard': require('@angular/cdk/clipboard'),
2021-07-06 22:22:57 +03:00
'@angular/common': require('@angular/common'),
'@angular/compiler': require('@angular/compiler'),
'@angular/core': require('@angular/core'),
'@angular/forms': require('@angular/forms'),
'@angular/platform-browser': require('@angular/platform-browser'),
'@angular/platform-browser/animations': require('@angular/platform-browser/animations'),
'@angular/platform-browser-dynamic': require('@angular/platform-browser-dynamic'),
'@ng-bootstrap/ng-bootstrap': require('@ng-bootstrap/ng-bootstrap'),
'ngx-toastr': require('ngx-toastr'),
rxjs: require('rxjs'),
'rxjs/operators': require('rxjs/operators'),
'zone.js/dist/zone.js': require('zone.js/dist/zone.js'),
}
2017-07-01 01:55:21 +03:00
const builtinModules = [
2021-07-06 22:22:57 +03:00
...Object.keys(cachedBuiltinModules),
2021-06-30 00:57:04 +03:00
'tabby-core',
'tabby-local',
'tabby-settings',
'tabby-terminal',
2017-07-01 01:55:21 +03:00
]
2021-07-06 22:22:57 +03:00
const originalRequire = (global as any).require
;(global as any).require = function (query: string) {
if (cachedBuiltinModules[query]) {
return cachedBuiltinModules[query]
}
return originalRequire.apply(this, [query])
}
const originalModuleRequire = nodeModule.prototype.require
nodeModule.prototype.require = function (query: string) {
if (cachedBuiltinModules[query]) {
return cachedBuiltinModules[query]
}
return originalModuleRequire.call(this, query)
}
2021-06-19 02:36:25 +03:00
2021-08-15 15:23:26 +03:00
export type ProgressCallback = (current: number, total: number) => void
2021-06-19 02:36:25 +03:00
export function initModuleLookup (userPluginsPath: string): void {
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
2017-07-01 02:22:01 +03:00
2022-02-02 00:47:07 +03:00
const paths = []
paths.unshift(path.join(userPluginsPath, 'node_modules'))
paths.unshift(path.join(remote.app.getAppPath(), 'node_modules'))
2021-06-30 00:57:04 +03:00
if (process.env.TABBY_DEV) {
2022-02-02 00:47:07 +03:00
paths.unshift(path.dirname(remote.app.getAppPath()))
2021-06-19 02:36:25 +03:00
}
2022-02-02 00:47:07 +03:00
paths.unshift(builtinPluginsPath)
// paths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))
2021-06-30 00:57:04 +03:00
if (process.env.TABBY_PLUGINS) {
2022-02-02 00:47:07 +03:00
process.env.TABBY_PLUGINS.split(':').map(x => paths.push(normalizePath(x)))
2017-07-01 02:22:01 +03:00
}
2022-02-02 00:47:07 +03:00
process.env.NODE_PATH += path.delimiter + paths.join(path.delimiter)
nodeModule._initPaths()
2021-06-19 02:36:25 +03:00
builtinModules.forEach(m => {
2021-07-06 22:22:57 +03:00
if (!cachedBuiltinModules[m]) {
cachedBuiltinModules[m] = nodeRequire(m)
2021-06-19 02:36:25 +03:00
}
2021-07-06 22:22:57 +03:00
})
}
2019-06-15 00:47:48 +03:00
export async function findPlugins (): Promise<PluginInfo[]> {
2019-06-14 18:49:42 +03:00
const paths = nodeModule.globalPaths
2019-06-15 00:47:48 +03:00
let foundPlugins: PluginInfo[] = []
2019-06-14 18:49:42 +03:00
const candidateLocations: { pluginDir: string, packageName: string }[] = []
2021-06-30 00:57:04 +03:00
const PREFIX = 'tabby-'
const LEGACY_PREFIX = 'terminus-'
2017-04-24 01:34:07 +03:00
const processedPaths = []
2017-04-15 16:20:18 +03:00
for (let pluginDir of paths) {
if (processedPaths.includes(pluginDir)) {
continue
}
processedPaths.push(pluginDir)
2017-04-17 15:57:22 +03:00
pluginDir = normalizePath(pluginDir)
2017-04-15 16:20:18 +03:00
if (!await fs.exists(pluginDir)) {
continue
}
2019-06-14 18:49:42 +03:00
const pluginNames = await fs.readdir(pluginDir)
2017-07-01 01:55:21 +03:00
if (await fs.exists(path.join(pluginDir, 'package.json'))) {
candidateLocations.push({
pluginDir: path.dirname(pluginDir),
2019-06-15 00:47:48 +03:00
packageName: path.basename(pluginDir),
2017-07-01 01:55:21 +03:00
})
}
2019-06-14 18:49:42 +03:00
for (const packageName of pluginNames) {
if ((packageName.startsWith(PREFIX) || packageName.startsWith(LEGACY_PREFIX)) && !PLUGIN_BLACKLIST.includes(packageName)) {
candidateLocations.push({ pluginDir, packageName })
}
2017-07-01 01:55:21 +03:00
}
}
2017-04-30 20:21:53 +03:00
2019-06-14 18:49:42 +03:00
for (const { pluginDir, packageName } of candidateLocations) {
const pluginPath = path.join(pluginDir, packageName)
const infoPath = path.join(pluginPath, 'package.json')
2017-07-01 01:55:21 +03:00
if (!await fs.exists(infoPath)) {
continue
}
2021-06-30 00:57:04 +03:00
const name = packageName.startsWith(PREFIX) ? packageName.substring(PREFIX.length) : packageName.substring(LEGACY_PREFIX.length)
2017-11-27 00:14:46 +03:00
if (builtinModules.includes(packageName) && pluginDir !== builtinPluginsPath) {
continue
}
console.log(`Found ${name} in ${pluginDir}`)
2021-07-02 01:09:32 +03:00
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
}
2017-07-01 01:55:21 +03:00
}
2017-04-30 20:21:53 +03:00
2017-07-01 01:55:21 +03:00
try {
2019-06-14 18:49:42 +03:00
const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
2021-07-02 01:09:32 +03:00
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'))) {
2017-07-01 01:55:21 +03:00
continue
2017-04-24 22:26:59 +03:00
}
2017-07-05 16:31:23 +03:00
let author = info.author
author = author.name || author
2017-07-01 01:55:21 +03:00
foundPlugins.push({
2017-11-27 00:14:46 +03:00
name: name,
packageName: packageName,
2017-07-01 01:55:21 +03:00
isBuiltin: pluginDir === builtinPluginsPath,
2021-07-02 01:09:32 +03:00
isLegacy: info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'),
2017-07-01 01:55:21 +03:00
version: info.version,
description: info.description,
2017-07-05 16:31:23 +03:00
author,
2017-07-01 01:55:21 +03:00
path: pluginPath,
info,
})
} catch (error) {
2017-11-27 00:14:46 +03:00
console.error('Cannot load package info for', packageName)
2017-04-24 22:26:59 +03:00
}
2017-04-15 16:20:18 +03:00
}
2017-04-24 22:26:59 +03:00
2019-09-18 21:56:59 +03:00
foundPlugins.sort((a, b) => a.name > b.name ? 1 : -1)
foundPlugins.sort((a, b) => a.isBuiltin < b.isBuiltin ? 1 : -1)
2017-04-24 22:26:59 +03:00
return foundPlugins
}
2019-06-15 00:47:48 +03:00
export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
2019-06-14 18:49:42 +03:00
const plugins: any[] = []
2017-04-24 22:26:59 +03:00
progress(0, 1)
2017-04-28 23:40:58 +03:00
let index = 0
2019-06-14 18:49:42 +03:00
for (const foundPlugin of foundPlugins) {
2017-05-01 14:35:26 +03:00
console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
2017-04-24 01:34:07 +03:00
progress(index, foundPlugins.length)
try {
2019-06-14 18:49:42 +03:00
const packageModule = nodeRequire(foundPlugin.path)
if (foundPlugin.packageName.startsWith('tabby-')) {
cachedBuiltinModules[foundPlugin.packageName.replace('tabby-', 'terminus-')] = packageModule
}
2019-06-14 18:49:42 +03:00
const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
2020-12-27 23:03:12 +03:00
pluginModule.pluginName = foundPlugin.name
pluginModule.bootstrap = packageModule.bootstrap
2017-04-24 01:34:07 +03:00
plugins.push(pluginModule)
await new Promise(x => setTimeout(x, 50))
2017-04-24 01:34:07 +03:00
} catch (error) {
console.error(`Could not load ${foundPlugin.name}:`, error)
}
2017-04-28 23:40:58 +03:00
index++
}
2017-04-24 01:34:07 +03:00
progress(1, 1)
2017-04-15 16:20:18 +03:00
return plugins
}