-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
205 lines (199 loc) · 5.19 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import fs from 'fs-extra'
import path from 'path'
import program from 'commander'
import { getProgram } from './lib/commander'
import pkgUp from 'pkg-up'
import resolvePkg from 'resolve-pkg'
import { PkgConf, getConf } from './lib/config'
const debug = require('debug')('app:main')
import { Plugin, Package } from './lib/plugin'
const requireg = require('requireg')
const globalDirs = require('global-dirs')
export class App {
/**
* program
*/
protected readonly program: program.CommanderStatic
/**
* user configurations
*/
protected conf: PkgConf
/**
* plugin's name
*/
protected plugins: any[] = []
constructor() {
this.conf = getConf()
this.program = getProgram()
}
/**
* is plugin folder
*/
private isPluginFolder = (folder: string) => {
return new RegExp(this.conf.prefix).test(folder)
}
/**
* add plugin
* @param pluginName plugin name
* @param cwd plugin cwd
*/
private async addPlugin(pluginName: string, cwd: string, checkDeps = true) {
debug('add plugin: %o at %o', pluginName, cwd)
const pkgCwd = pkgUp.sync(cwd)
if (!pkgCwd) {
return
}
if (checkDeps) {
debug('checking dependencies...')
// add missing deps
await require('check-dependencies')({
install: true,
verbose: false,
packageDir: cwd,
})
}
if (!this.plugins.includes(pluginName)) {
const plugin = requireg(cwd)
const plug = plugin.default || plugin
if (typeof plug === 'function') {
try {
const Plug = new Plugin(this.program, this.conf, pkgCwd)
plug(Plug)
this.plugins.push(pluginName)
debug('loading plugin at path: %o succeed', cwd)
} catch (error) {
debug('loading plugin at path: %o failed with errors: %o', cwd, error)
}
} else {
debug(
'loading plugin at path: %o failed\n%s must be a function instead of %o',
cwd,
pluginName,
typeof plug
)
}
}
}
/**
* load local plugins defined by user
* @param dir dir for local plugins
*/
private async loadPluginsByDir(dir: string, checkDeps = true) {
if (!fs.pathExistsSync(dir)) {
debug('Not found local plugins at: %o', dir)
return
}
let folders = await fs.readdir(dir)
if (folders && folders.length) {
folders = folders.filter(f => this.isPluginFolder(f))
for (const f of folders) {
const cwd = path.join(dir, f)
await this.addPlugin(f, cwd, checkDeps)
}
}
}
/**
* load ./plugins
*/
private async loadUserLocalPlugins() {
const cwd = path.join(process.cwd(), this.conf.pluginDir)
await this.loadPluginsByDir(cwd)
}
/**
* loading plugins which install it as a dependence in package.json
*/
private async loadPlugins() {
const cwdPkg = path.join(process.cwd(), 'package.json')
if (!fs.existsSync(cwdPkg)) {
return
}
debug('finding npm plugins in %o ', cwdPkg)
const pkg: Package = await fs.readJSON(cwdPkg)
if (pkg.dependencies || pkg.devDependencies) {
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
const npmPlugins = Object.keys(deps).filter(f => this.isPluginFolder(f))
debug('npm plugins: %o', npmPlugins)
for (const dep of npmPlugins) {
const cwd = resolvePkg(dep)
if (cwd) {
await this.addPlugin(dep, cwd, false)
} else {
console.log(
'can not find %o, make sure you have installed it already',
dep
)
}
}
}
}
private async loadGlobalPlugins() {
const npmPackages = fs.pathExistsSync(globalDirs.npm.packages)
? globalDirs.npm.packages
: (globalDirs.npm.packages as string).includes('Cellar')
? '/usr/local/lib/node_modules'
: globalDirs.npm.packages
await this.loadPluginsByDir(npmPackages, false)
await this.loadPluginsByDir(globalDirs.yarn.packages, false)
}
/**
* install plugins the order is:
* - user local plugins
* - system local plugins
* - plugins
*/
public async install() {
await this.loadPluginsByDir(path.join(__dirname, 'plugins'))
await this.loadUserLocalPlugins()
await this.loadPlugins()
await this.loadGlobalPlugins()
}
/**
* run a plugin immediately
* @param plugin local plugin for debug
* - ./path/to/plugin-entry
* - /path/to/plugin-entry
* - merry-plugin-awesome-plugin
* @param options
*/
private async debug(plugin: string, options: any) {
debug('isAbsolute: %o', path.isAbsolute(plugin))
if (!path.isAbsolute(plugin) && this.isPluginFolder(plugin)) {
await this.addPlugin(plugin, process.cwd())
} else {
plugin = path.isAbsolute(plugin)
? plugin
: path.join(process.cwd(), plugin)
if (fs.existsSync(plugin)) {
try {
const p = require(plugin)
const plug = p.default || p
const pkgCwd = pkgUp.sync(plugin)
const Plug = new Plugin(this.program, this.conf, pkgCwd)
plug(Plug)
debug('loading plugin at path: %o succeed', pkgCwd)
} catch (error) {
debug('error: %o', error)
}
}
}
}
/**
* install plugins and run
*/
public async run() {
await this.install()
// only install debug plugin in DEBUG mode
if (!!process.env.DEBUG) {
this.program
.command('debug [plugin]')
.action((p: string, options: any) => {
this.debug(p, options)
})
}
this.program.parse(process.argv)
if (!process.argv.slice(2).length) {
this.program.outputHelp()
}
}
}
export default new App()