plugins.ts 6.2 KB
Newer Older
E
Eugene Pankov 已提交
1
import * as fs from 'mz/fs'
E
.  
Eugene Pankov 已提交
2
import * as path from 'path'
E
Eugene Pankov 已提交
3
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
E
lint  
Eugene Pankov 已提交
4
const nodeRequire = (global as any).require
E
.  
Eugene Pankov 已提交
5 6 7 8 9 10 11 12

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
E
lint  
Eugene Pankov 已提交
13
}
E
.  
Eugene Pankov 已提交
14

E
Eugene Pankov 已提交
15
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
E
.  
Eugene Pankov 已提交
16

E
Eugene Pankov 已提交
17
if (process.env.TERMINUS_DEV) {
E
.  
Eugene Pankov 已提交
18 19 20
    nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
}

E
Eugene Pankov 已提交
21
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
E
wip  
Eugene Pankov 已提交
22 23

const userPluginsPath = path.join(
E
.  
Eugene Pankov 已提交
24 25 26
    require('electron').remote.app.getPath('appData'),
    'terminus',
    'plugins',
E
wip  
Eugene Pankov 已提交
27 28
)

29 30 31 32
if (!fs.existsSync(userPluginsPath)) {
    fs.mkdir(userPluginsPath)
}

E
wip  
Eugene Pankov 已提交
33 34
Object.assign(window, { builtinPluginsPath, userPluginsPath })
nodeModule.globalPaths.unshift(builtinPluginsPath)
E
.  
Eugene Pankov 已提交
35
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
E
wip  
Eugene Pankov 已提交
36
// nodeModule.globalPaths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))
E
.  
Eugene Pankov 已提交
37
if (process.env.TERMINUS_PLUGINS) {
E
wip  
Eugene Pankov 已提交
38
    process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
E
.  
Eugene Pankov 已提交
39 40
}

E
Eugene Pankov 已提交
41
export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
E
.  
Eugene Pankov 已提交
42

E
Eugene Pankov 已提交
43
export interface PluginInfo {
E
.  
Eugene Pankov 已提交
44
    name: string
E
wip  
Eugene Pankov 已提交
45 46 47 48
    description: string
    packageName: string
    isBuiltin: boolean
    version: string
E
Eugene Pankov 已提交
49
    author: string
E
wip  
Eugene Pankov 已提交
50 51 52
    homepage?: string
    path?: string
    info?: any
E
.  
Eugene Pankov 已提交
53 54
}

E
wip  
Eugene Pankov 已提交
55 56 57 58 59 60 61 62 63
const builtinModules = [
    '@angular/animations',
    '@angular/common',
    '@angular/compiler',
    '@angular/core',
    '@angular/forms',
    '@angular/platform-browser',
    '@angular/platform-browser-dynamic',
    '@ng-bootstrap/ng-bootstrap',
E
Eugene Pankov 已提交
64
    'ngx-toastr',
E
wip  
Eugene Pankov 已提交
65
    'rxjs',
E
Eugene Pankov 已提交
66
    'rxjs/operators',
67
    'rxjs-compat/Subject',
E
wip  
Eugene Pankov 已提交
68 69 70 71 72 73
    'terminus-core',
    'terminus-settings',
    'terminus-terminal',
    'zone.js/dist/zone.js',
]

74 75
const cachedBuiltinModules = {}
builtinModules.forEach(m => {
76 77
    const label = 'Caching ' + m
    console.time(label)
78
    cachedBuiltinModules[m] = nodeRequire(m)
79
    console.timeEnd(label)
80 81
})

82
const originalRequire = (global as any).require
E
lint  
Eugene Pankov 已提交
83
;(global as any).require = function (query: string) {
84 85 86 87 88 89
    if (cachedBuiltinModules[query]) {
        return cachedBuiltinModules[query]
    }
    return originalRequire.apply(this, arguments)
}

90 91 92 93 94 95 96 97
const originalModuleRequire = nodeModule.prototype.require
nodeModule.prototype.require = function (query: string) {
    if (cachedBuiltinModules[query]) {
        return cachedBuiltinModules[query]
    }
    return originalModuleRequire.call(this, query)
}

E
Eugene Pankov 已提交
98
export async function findPlugins (): Promise<PluginInfo[]> {
E
lint  
Eugene Pankov 已提交
99
    const paths = nodeModule.globalPaths
E
Eugene Pankov 已提交
100
    let foundPlugins: PluginInfo[] = []
E
lint  
Eugene Pankov 已提交
101
    const candidateLocations: { pluginDir: string, packageName: string }[] = []
102
    const PREFIX = 'terminus-'
E
.  
Eugene Pankov 已提交
103

E
.  
Eugene Pankov 已提交
104
    for (let pluginDir of paths) {
E
.  
Eugene Pankov 已提交
105
        pluginDir = normalizePath(pluginDir)
E
.  
Eugene Pankov 已提交
106 107 108
        if (!await fs.exists(pluginDir)) {
            continue
        }
E
lint  
Eugene Pankov 已提交
109
        const pluginNames = await fs.readdir(pluginDir)
E
wip  
Eugene Pankov 已提交
110 111 112
        if (await fs.exists(path.join(pluginDir, 'package.json'))) {
            candidateLocations.push({
                pluginDir: path.dirname(pluginDir),
E
Eugene Pankov 已提交
113
                packageName: path.basename(pluginDir),
E
wip  
Eugene Pankov 已提交
114 115
            })
        }
E
lint  
Eugene Pankov 已提交
116
        for (const packageName of pluginNames) {
117 118 119
            if (packageName.startsWith(PREFIX)) {
                candidateLocations.push({ pluginDir, packageName })
            }
E
wip  
Eugene Pankov 已提交
120 121
        }
    }
E
.  
Eugene Pankov 已提交
122

E
lint  
Eugene Pankov 已提交
123 124 125
    for (const { pluginDir, packageName } of candidateLocations) {
        const pluginPath = path.join(pluginDir, packageName)
        const infoPath = path.join(pluginPath, 'package.json')
E
wip  
Eugene Pankov 已提交
126 127 128 129
        if (!await fs.exists(infoPath)) {
            continue
        }

E
lint  
Eugene Pankov 已提交
130
        const name = packageName.substring(PREFIX.length)
E
Eugene Pankov 已提交
131 132 133 134

        if (foundPlugins.some(x => x.name === name)) {
            console.info(`Plugin ${packageName} already exists, overriding`)
            foundPlugins = foundPlugins.filter(x => x.name !== name)
E
wip  
Eugene Pankov 已提交
135
        }
E
.  
Eugene Pankov 已提交
136

E
wip  
Eugene Pankov 已提交
137
        try {
E
lint  
Eugene Pankov 已提交
138
            const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
E
Eugene Pankov 已提交
139
            if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
E
wip  
Eugene Pankov 已提交
140
                continue
E
.  
Eugene Pankov 已提交
141
            }
E
Eugene Pankov 已提交
142 143
            let author = info.author
            author = author.name || author
E
wip  
Eugene Pankov 已提交
144
            foundPlugins.push({
E
Eugene Pankov 已提交
145 146
                name: name,
                packageName: packageName,
E
wip  
Eugene Pankov 已提交
147 148 149
                isBuiltin: pluginDir === builtinPluginsPath,
                version: info.version,
                description: info.description,
E
Eugene Pankov 已提交
150
                author,
E
wip  
Eugene Pankov 已提交
151 152 153 154
                path: pluginPath,
                info,
            })
        } catch (error) {
E
Eugene Pankov 已提交
155
            console.error('Cannot load package info for', packageName)
E
.  
Eugene Pankov 已提交
156
        }
E
.  
Eugene Pankov 已提交
157
    }
E
.  
Eugene Pankov 已提交
158

E
wip  
Eugene Pankov 已提交
159
    (window as any).installedPlugins = foundPlugins
E
.  
Eugene Pankov 已提交
160 161 162
    return foundPlugins
}

E
Eugene Pankov 已提交
163
export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
E
lint  
Eugene Pankov 已提交
164
    const plugins: any[] = []
E
.  
Eugene Pankov 已提交
165
    progress(0, 1)
E
.  
Eugene Pankov 已提交
166
    let index = 0
E
lint  
Eugene Pankov 已提交
167
    for (const foundPlugin of foundPlugins) {
E
lint  
Eugene Pankov 已提交
168
        console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
E
.  
Eugene Pankov 已提交
169 170
        progress(index, foundPlugins.length)
        try {
171 172
            const label = 'Loading ' + foundPlugin.name
            console.time(label)
E
lint  
Eugene Pankov 已提交
173 174
            const packageModule = nodeRequire(foundPlugin.path)
            const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
E
Eugene Pankov 已提交
175 176
            pluginModule['pluginName'] = foundPlugin.name
            pluginModule['bootstrap'] = packageModule.bootstrap
E
.  
Eugene Pankov 已提交
177
            plugins.push(pluginModule)
178
            console.timeEnd(label)
179
            await new Promise(x => setTimeout(x, 50))
E
.  
Eugene Pankov 已提交
180 181 182
        } catch (error) {
            console.error(`Could not load ${foundPlugin.name}:`, error)
        }
E
.  
Eugene Pankov 已提交
183 184
        index++
    }
E
.  
Eugene Pankov 已提交
185
    progress(1, 1)
E
.  
Eugene Pankov 已提交
186 187
    return plugins
}