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'
|
2019-06-15 00:47:48 +03:00
|
|
|
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
2017-05-01 14:35:26 +03:00
|
|
|
const nodeRequire = (global as any).require
|
2017-04-17 15:57:22 +03:00
|
|
|
|
|
|
|
function normalizePath (path: string): string {
|
|
|
|
const cygwinPrefix = '/cygdrive/'
|
|
|
|
if (path.startsWith(cygwinPrefix)) {
|
|
|
|
path = path.substring(cygwinPrefix.length).replace('/', '\\')
|
|
|
|
path = path[0] + ':' + path.substring(1)
|
|
|
|
}
|
|
|
|
return path
|
2017-05-01 14:35:26 +03:00
|
|
|
}
|
2017-04-17 15:57:22 +03:00
|
|
|
|
2019-09-09 17:23:47 +03:00
|
|
|
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
2017-04-30 20:21:53 +03:00
|
|
|
|
2019-02-10 00:10:42 +03:00
|
|
|
if (process.env.TERMINUS_DEV) {
|
2017-04-30 20:21:53 +03:00
|
|
|
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
|
|
|
}
|
|
|
|
|
2019-02-10 00:10:42 +03:00
|
|
|
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
2017-06-01 23:23:36 +03:00
|
|
|
|
|
|
|
const userPluginsPath = path.join(
|
2017-04-30 20:21:53 +03:00
|
|
|
require('electron').remote.app.getPath('appData'),
|
|
|
|
'terminus',
|
|
|
|
'plugins',
|
2017-06-01 23:23:36 +03:00
|
|
|
)
|
|
|
|
|
2019-05-19 19:26:55 +03:00
|
|
|
if (!fs.existsSync(userPluginsPath)) {
|
|
|
|
fs.mkdir(userPluginsPath)
|
|
|
|
}
|
|
|
|
|
2017-06-01 23:23:36 +03:00
|
|
|
Object.assign(window, { builtinPluginsPath, userPluginsPath })
|
|
|
|
nodeModule.globalPaths.unshift(builtinPluginsPath)
|
2017-06-03 18:11:38 +03:00
|
|
|
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
|
2017-07-01 01:55:21 +03:00
|
|
|
// nodeModule.globalPaths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))
|
2017-04-15 16:20:18 +03:00
|
|
|
if (process.env.TERMINUS_PLUGINS) {
|
2017-07-01 01:55:21 +03:00
|
|
|
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
|
2017-04-15 16:20:18 +03:00
|
|
|
}
|
|
|
|
|
2019-06-15 00:47:48 +03:00
|
|
|
export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
|
2017-04-24 01:34:07 +03:00
|
|
|
|
2019-06-15 00:47:48 +03:00
|
|
|
export interface PluginInfo {
|
2017-04-24 01:34:07 +03:00
|
|
|
name: string
|
2017-06-01 23:23:36 +03:00
|
|
|
description: string
|
|
|
|
packageName: string
|
|
|
|
isBuiltin: boolean
|
|
|
|
version: string
|
2017-07-05 16:31:23 +03:00
|
|
|
author: string
|
2017-06-01 23:23:36 +03:00
|
|
|
homepage?: string
|
|
|
|
path?: string
|
|
|
|
info?: any
|
2017-04-24 01:34:07 +03:00
|
|
|
}
|
|
|
|
|
2017-07-01 01:55:21 +03:00
|
|
|
const builtinModules = [
|
|
|
|
'@angular/animations',
|
|
|
|
'@angular/common',
|
|
|
|
'@angular/compiler',
|
|
|
|
'@angular/core',
|
|
|
|
'@angular/forms',
|
|
|
|
'@angular/platform-browser',
|
|
|
|
'@angular/platform-browser-dynamic',
|
|
|
|
'@ng-bootstrap/ng-bootstrap',
|
2018-01-19 17:31:28 +03:00
|
|
|
'ngx-toastr',
|
2017-07-01 01:55:21 +03:00
|
|
|
'rxjs',
|
2018-07-02 20:53:48 +03:00
|
|
|
'rxjs/operators',
|
2019-05-31 19:17:15 +03:00
|
|
|
'rxjs-compat/Subject',
|
2017-07-01 01:55:21 +03:00
|
|
|
'terminus-core',
|
|
|
|
'terminus-settings',
|
|
|
|
'terminus-terminal',
|
|
|
|
'zone.js/dist/zone.js',
|
|
|
|
]
|
|
|
|
|
2017-07-01 02:22:01 +03:00
|
|
|
const cachedBuiltinModules = {}
|
|
|
|
builtinModules.forEach(m => {
|
2019-05-31 19:17:15 +03:00
|
|
|
const label = 'Caching ' + m
|
|
|
|
console.time(label)
|
2017-07-01 02:22:01 +03:00
|
|
|
cachedBuiltinModules[m] = nodeRequire(m)
|
2019-05-31 19:17:15 +03:00
|
|
|
console.timeEnd(label)
|
2017-07-01 02:22:01 +03:00
|
|
|
})
|
|
|
|
|
2019-05-31 19:17:15 +03:00
|
|
|
const originalRequire = (global as any).require
|
2019-06-14 18:49:42 +03:00
|
|
|
;(global as any).require = function (query: string) {
|
2017-07-01 02:22:01 +03:00
|
|
|
if (cachedBuiltinModules[query]) {
|
|
|
|
return cachedBuiltinModules[query]
|
|
|
|
}
|
|
|
|
return originalRequire.apply(this, arguments)
|
|
|
|
}
|
|
|
|
|
2019-07-01 00:08:30 +03:00
|
|
|
const originalModuleRequire = nodeModule.prototype.require
|
|
|
|
nodeModule.prototype.require = function (query: string) {
|
|
|
|
if (cachedBuiltinModules[query]) {
|
|
|
|
return cachedBuiltinModules[query]
|
|
|
|
}
|
|
|
|
return originalModuleRequire.call(this, query)
|
|
|
|
}
|
|
|
|
|
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 }[] = []
|
2019-05-31 14:41:56 +03:00
|
|
|
const PREFIX = 'terminus-'
|
2017-04-24 01:34:07 +03:00
|
|
|
|
2017-04-15 16:20:18 +03:00
|
|
|
for (let pluginDir of paths) {
|
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) {
|
2019-05-31 14:41:56 +03:00
|
|
|
if (packageName.startsWith(PREFIX)) {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-06-14 18:49:42 +03:00
|
|
|
const name = packageName.substring(PREFIX.length)
|
2017-11-27 00:14:46 +03:00
|
|
|
|
|
|
|
if (foundPlugins.some(x => x.name === name)) {
|
|
|
|
console.info(`Plugin ${packageName} already exists, overriding`)
|
|
|
|
foundPlugins = foundPlugins.filter(x => x.name !== name)
|
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' }))
|
2017-11-04 21:06:58 +03:00
|
|
|
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-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,
|
|
|
|
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
|
|
|
|
2017-06-01 23:23:36 +03:00
|
|
|
(window as any).installedPlugins = foundPlugins
|
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-05-31 19:17:15 +03:00
|
|
|
const label = 'Loading ' + foundPlugin.name
|
|
|
|
console.time(label)
|
2019-06-14 18:49:42 +03:00
|
|
|
const packageModule = nodeRequire(foundPlugin.path)
|
|
|
|
const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
|
2017-11-27 00:14:46 +03:00
|
|
|
pluginModule['pluginName'] = foundPlugin.name
|
|
|
|
pluginModule['bootstrap'] = packageModule.bootstrap
|
2017-04-24 01:34:07 +03:00
|
|
|
plugins.push(pluginModule)
|
2019-05-31 19:17:15 +03:00
|
|
|
console.timeEnd(label)
|
2019-07-24 12:24:57 +03:00
|
|
|
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
|
|
|
|
}
|