Skip to content

Commit f8ec464

Browse files
committed
✨ Feature: add local plugin install/uninstall/update support & imporve plugin name handler
1 parent 5a6d638 commit f8ec464

File tree

10 files changed

+326
-47
lines changed

10 files changed

+326
-47
lines changed

src/lib/Logger.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import {
1010
ILogArgvTypeWithError,
1111
IConfig,
1212
Undefinable,
13-
ILogColor
13+
ILogColor,
14+
ILogger
1415
} from '../types'
1516

16-
class Logger {
17+
class Logger implements ILogger {
1718
private readonly level = {
1819
[ILogType.success]: 'green',
1920
[ILogType.info]: 'blue',

src/lib/PluginHandler.ts

Lines changed: 124 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import PicGo from '../core/PicGo'
22
import spawn from 'cross-spawn'
3-
import { IResult, IProcessEnv, Undefinable } from '../types'
4-
import { IBuildInEvent } from 'src/utils/enum'
3+
import { IResult, IProcessEnv, Undefinable, IPluginProcessResult } from '../types'
4+
import { IBuildInEvent } from '../utils/enum'
5+
import { getProcessPluginName, getNormalPluginName } from '../utils/common'
56

67
class PluginHandler {
78
// Thanks to feflow -> https://github.com/feflow/feflow/blob/master/lib/internal/install/plugin.js
@@ -11,41 +12,89 @@ class PluginHandler {
1112
}
1213

1314
async install (plugins: string[], proxy: string = '', env?: IProcessEnv): Promise<void> {
14-
plugins = plugins.map((item: string) => 'picgo-plugin-' + item)
15-
const result = await this.execCommand('install', plugins, this.ctx.baseDir, proxy, env)
16-
if (!result.code) {
17-
plugins.forEach((plugin: string) => {
18-
this.ctx.pluginLoader.registerPlugin(plugin)
15+
const installedPlugins: string[] = []
16+
const processPlugins = plugins
17+
.map((item: string) => handlePluginNameProcess(this.ctx, item))
18+
.filter((item) => {
19+
// detect if has already installed
20+
// or will cause error
21+
if (this.ctx.pluginLoader.hasPlugin(item.pkgName)) {
22+
installedPlugins.push(item.pkgName)
23+
this.ctx.log.success(`PicGo has already installed ${item.pkgName}`)
24+
return false
25+
}
26+
// if something wrong, filter it out
27+
if (!item.success) {
28+
return false
29+
}
30+
return true
1931
})
20-
this.ctx.log.success('插件安装成功')
21-
this.ctx.emit('installSuccess', {
22-
title: '插件安装成功',
23-
body: plugins
24-
})
25-
} else {
26-
const err = `插件安装失败,失败码为${result.code},错误日志为${result.data}`
32+
const fullNameList = processPlugins.map(item => item.fullName)
33+
const pkgNameList = processPlugins.map(item => item.pkgName)
34+
if (fullNameList.length > 0) {
35+
// install plugins must use fullNameList:
36+
// 1. install remote pacage
37+
// 2. install local pacage
38+
const result = await this.execCommand('install', fullNameList, this.ctx.baseDir, proxy, env)
39+
if (!result.code) {
40+
pkgNameList.forEach((pluginName: string) => {
41+
this.ctx.pluginLoader.registerPlugin(pluginName)
42+
})
43+
this.ctx.log.success('插件安装成功')
44+
this.ctx.emit('installSuccess', {
45+
title: '插件安装成功',
46+
body: [...pkgNameList, ...installedPlugins]
47+
})
48+
} else {
49+
const err = `插件安装失败,失败码为${result.code},错误日志为${result.data}`
50+
this.ctx.log.error(err)
51+
this.ctx.emit('installFailed', {
52+
title: '插件安装失败',
53+
body: err
54+
})
55+
}
56+
} else if (installedPlugins.length === 0) {
57+
const err = '插件安装失败,请输入合法插件名或合法安装路径'
2758
this.ctx.log.error(err)
2859
this.ctx.emit('installFailed', {
2960
title: '插件安装失败',
3061
body: err
3162
})
63+
} else {
64+
this.ctx.log.success('插件安装成功')
65+
this.ctx.emit('installSuccess', {
66+
title: '插件安装成功',
67+
body: [...pkgNameList, ...installedPlugins]
68+
})
3269
}
3370
}
3471

3572
async uninstall (plugins: string[]): Promise<void> {
36-
plugins = plugins.map((item: string) => 'picgo-plugin-' + item)
37-
const result = await this.execCommand('uninstall', plugins, this.ctx.baseDir)
38-
if (!result.code) {
39-
plugins.forEach((plugin: string) => {
40-
this.ctx.pluginLoader.unregisterPlugin(plugin)
41-
})
42-
this.ctx.log.success('插件卸载成功')
43-
this.ctx.emit('uninstallSuccess', {
44-
title: '插件卸载成功',
45-
body: plugins
46-
})
73+
const processPlugins = plugins.map((item: string) => handlePluginNameProcess(this.ctx, item)).filter(item => item.success)
74+
const pkgNameList = processPlugins.map(item => item.pkgName)
75+
if (pkgNameList.length > 0) {
76+
// uninstall plugins must use pkgNameList:
77+
// npm uninstall will use the package.json's name
78+
const result = await this.execCommand('uninstall', pkgNameList, this.ctx.baseDir)
79+
if (!result.code) {
80+
pkgNameList.forEach((pluginName: string) => {
81+
this.ctx.pluginLoader.unregisterPlugin(pluginName)
82+
})
83+
this.ctx.log.success('插件卸载成功')
84+
this.ctx.emit('uninstallSuccess', {
85+
title: '插件卸载成功',
86+
body: pkgNameList
87+
})
88+
} else {
89+
const err = `插件卸载失败,失败码为${result.code},错误日志为${result.data}`
90+
this.ctx.log.error(err)
91+
this.ctx.emit('uninstallFailed', {
92+
title: '插件卸载失败',
93+
body: err
94+
})
95+
}
4796
} else {
48-
const err = `插件卸载失败,失败码为${result.code},错误日志为${result.data}`
97+
const err = '插件卸载失败,请输入合法插件名'
4998
this.ctx.log.error(err)
5099
this.ctx.emit('uninstallFailed', {
51100
title: '插件卸载失败',
@@ -55,16 +104,28 @@ class PluginHandler {
55104
}
56105

57106
async update (plugins: string[], proxy: string = '', env?: IProcessEnv): Promise<void> {
58-
plugins = plugins.map((item: string) => 'picgo-plugin-' + item)
59-
const result = await this.execCommand('update', plugins, this.ctx.baseDir, proxy, env)
60-
if (!result.code) {
61-
this.ctx.log.success('插件更新成功')
62-
this.ctx.emit('updateSuccess', {
63-
title: '插件更新成功',
64-
body: plugins
65-
})
107+
const processPlugins = plugins.map((item: string) => handlePluginNameProcess(this.ctx, item)).filter(item => item.success)
108+
const pkgNameList = processPlugins.map(item => item.pkgName)
109+
if (pkgNameList.length > 0) {
110+
// update plugins must use pkgNameList:
111+
// npm update will use the package.json's name
112+
const result = await this.execCommand('update', pkgNameList, this.ctx.baseDir, proxy, env)
113+
if (!result.code) {
114+
this.ctx.log.success('插件更新成功')
115+
this.ctx.emit('updateSuccess', {
116+
title: '插件更新成功',
117+
body: pkgNameList
118+
})
119+
} else {
120+
const err = `插件更新失败,失败码为${result.code},错误日志为 \n ${result.data}`
121+
this.ctx.log.error(err)
122+
this.ctx.emit('updateFailed', {
123+
title: '插件更新失败',
124+
body: err
125+
})
126+
}
66127
} else {
67-
const err = `插件更新失败,失败码为${result.code},错误日志为 \n ${result.data}`
128+
const err = '插件更新失败,请输入合法插件名'
68129
this.ctx.log.error(err)
69130
this.ctx.emit('updateFailed', {
70131
title: '插件更新失败',
@@ -116,4 +177,32 @@ class PluginHandler {
116177
}
117178
}
118179

180+
/**
181+
* transform the input plugin name or path string to valid result
182+
* @param ctx
183+
* @param nameOrPath
184+
*/
185+
const handlePluginNameProcess = (ctx: PicGo, nameOrPath: string): IPluginProcessResult => {
186+
const res = {
187+
success: false,
188+
fullName: '',
189+
pkgName: ''
190+
}
191+
const result = getProcessPluginName(nameOrPath, ctx.log)
192+
if (!result) {
193+
return res
194+
}
195+
// first get result then do this process
196+
// or some error will log twice
197+
const pkgName = getNormalPluginName(result, ctx.log)
198+
if (!pkgName) {
199+
return res
200+
}
201+
return {
202+
success: true,
203+
fullName: result,
204+
pkgName
205+
}
206+
}
207+
119208
export default PluginHandler

src/lib/PluginLoader.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class PluginLoader {
111111
return this.list
112112
}
113113

114+
hasPlugin (name: string): boolean {
115+
return this.fullList.has(name)
116+
}
117+
114118
/**
115119
* Get the full list of plugins, whether it is enabled or not
116120
*/

src/plugins/uploader/smms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import PicGo from '../../core/PicGo'
22
import { IPluginConfig, ISmmsConfig } from '../../types'
33
import { Options } from 'request-promise-native'
4-
import { IBuildInEvent } from 'src/utils/enum'
4+
import { IBuildInEvent } from '../../utils/enum'
55

66
const postOptions = (fileName: string, image: Buffer, apiToken: string): Options => {
77
return {

src/plugins/uploader/tcyun.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import crypto from 'crypto'
33
import mime from 'mime-types'
44
import { IPluginConfig, ITcyunConfig } from '../../types'
55
import { Options } from 'request-promise-native'
6-
import { IBuildInEvent } from 'src/utils/enum'
6+
import { IBuildInEvent } from '../../utils/enum'
77

88
// generate COS signature string
99

src/plugins/uploader/upyun.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { IPluginConfig, IUpyunConfig } from '../../types'
33
import crypto from 'crypto'
44
import MD5 from 'md5'
55
import { Options } from 'request-promise-native'
6-
import { IBuildInEvent } from 'src/utils/enum'
6+
import { IBuildInEvent } from '../../utils/enum'
77

88
// generate COS signature string
99
const generateSignature = (options: IUpyunConfig, fileName: string): string => {

src/types/index.d.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import PicGo from '../core/PicGo'
22
import LifecyclePlugins from '../lib/LifecyclePlugins'
3-
import Logger from '../lib/Logger'
43
import Commander from '../lib/Commander'
54
import PluginHandler from '../lib/PluginHandler'
65
import PluginLoader from '../lib/PluginLoader'
@@ -9,7 +8,7 @@ import Request from '../lib/Request'
98
interface IPicGo extends NodeJS.EventEmitter {
109
configPath: string
1110
baseDir: string
12-
log: Logger
11+
log: ILogger
1312
cmd: Commander
1413
output: IImgInfo[]
1514
input: any[]
@@ -198,6 +197,20 @@ interface IPlugin {
198197
[propName: string]: any
199198
}
200199

200+
type IPluginNameType = 'simple' | 'scope' | 'normal' | 'unknown'
201+
202+
interface IPluginProcessResult {
203+
success: boolean
204+
/**
205+
* the package.json's name filed
206+
*/
207+
pkgName: string
208+
/**
209+
* the plugin name or the fs absolute path
210+
*/
211+
fullName: string
212+
}
213+
201214
/**
202215
* for picgo npm plugins
203216
*/
@@ -282,3 +295,10 @@ type ILogArgvTypeWithError = ILogArgvType | Error
282295

283296
type Nullable<T> = T | null
284297
type Undefinable<T> = T | undefined
298+
299+
interface ILogger {
300+
success: (...msg: ILogArgvType[]) => void
301+
info: (...msg: ILogArgvType[]) => void
302+
error: (...msg: ILogArgvType[]) => void
303+
warn: (...msg: ILogArgvType[]) => void
304+
}

0 commit comments

Comments
 (0)