Skip to content

Commit

Permalink
feat(market): support experimental command usage for plugin manage
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 14, 2023
1 parent ebd1464 commit 12044ae
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 13 deletions.
2 changes: 1 addition & 1 deletion plugins/market/src/node/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Dependencies extends DataService<Dict<Dependency>> {
super(ctx, 'dependencies', { authority: 4 })

ctx.console.addListener('market/install', async (deps) => {
const code = await ctx.installer.installDep(deps)
const code = await ctx.installer.install(deps)
this.refresh()
this.ctx.console.packages?.refresh()
return code
Expand Down
83 changes: 82 additions & 1 deletion plugins/market/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Context, Schema } from 'koishi'
import { Context, Logger, pick, Schema } from 'koishi'
import { Registry } from '@koishijs/registry'
import { gt } from 'semver'
import { resolve } from 'path'
import Dependencies from './deps'
import Installer from './installer'
Expand All @@ -22,6 +24,8 @@ declare module '@koishijs/plugin-console' {
}
}

const logger = new Logger('market')

export const name = 'market'

export interface Config {
Expand All @@ -32,15 +36,92 @@ export interface Config {
export const Config: Schema<Config> = Schema.object({
registry: Installer.Config,
search: MarketProvider.Config,
}).i18n({
'zh-CN': require('./locales/schema.zh-CN'),
})

export function apply(ctx: Context, config: Config) {
if (!ctx.loader.writable) {
return ctx.logger('app').warn('@koishijs/plugin-market is only available for json/yaml config file')
}

ctx.i18n.define('zh', require('./locales/message.zh-CN'))

ctx.plugin(Installer, config.registry)

ctx.using(['installer'], (ctx) => {
ctx.command('plugin.install [name]', { authority: 4 })
.alias('.i')
.action(async ({ session }, name) => {
if (!name) return session.text('.expect-name')
const deps = await ctx.installer.get()
if (deps[name]) return session.text('.already-installed')
let registry: Registry
try {
registry = await ctx.installer.getRegistry(name)
} catch (error) {
logger.warn(error)
return session.text('.unknown-error')
}
ctx.envData.message = {
...pick(session, ['sid', 'channelId', 'guildId', 'subtype']),
content: session.text('.success'),
}
await ctx.installer.install({ [name]: registry.version })
ctx.envData.message = null
return session.text('.success')
})

ctx.command('plugin.uninstall <name>', { authority: 4 })
.alias('.r')
.action(async ({ session }, name) => {
if (!name) return session.text('.expect-name')
const deps = await ctx.installer.get()
if (!deps[name]) return session.text('.not-installed')
ctx.envData.message = {
...pick(session, ['sid', 'channelId', 'guildId', 'subtype']),
content: session.text('.success'),
}
await ctx.installer.install({ [name]: null })
ctx.envData.message = null
return session.text('.success')
})

ctx.command('plugin.upgrade', { authority: 4 })
.alias('.update', '.up')
.action(async ({ session }) => {
const deps = await ctx.installer.get()
const names = Object.keys(deps).filter((name) => {
const { latest, resolved, invalid } = deps[name]
try {
return !invalid && gt(latest, resolved)
} catch {}
})
if (!names.length) return session.text('.all-updated')
const output = names.map((name) => {
const { latest, resolved } = deps[name]
return `${name}: ${resolved} -> ${latest}`
})
output.unshift(session.text('.available'))
output.push(session.text('.prompt'))
await session.send(output.join('\n'))
const result = await session.prompt()
if (!['Y', 'y'].includes(result?.trim())) {
return session.text('.cancelled')
}
ctx.envData.message = {
...pick(session, ['sid', 'channelId', 'guildId', 'subtype']),
content: session.text('.success'),
}
await ctx.installer.install(names.reduce((result, name) => {
result[name] = deps[name].latest
return result
}, {}))
ctx.envData.message = null
return session.text('.success')
})
})

ctx.using(['console', 'installer'], (ctx) => {
ctx.plugin(Dependencies)
ctx.plugin(MarketProvider, config.search)
Expand Down
18 changes: 11 additions & 7 deletions plugins/market/src/node/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class Installer extends Service {
this.http = this.ctx.http.extend({ endpoint: this.registry, timeout })
}

getRegistry(name: string) {
return this.http.get<Registry>(`/${name}`)
}

private async _get() {
const result = valueMap(this.manifest.dependencies, (request) => {
return { request: request.replace(/^[~^]/, '') } as Dependency
Expand All @@ -67,7 +71,7 @@ class Installer extends Service {
}

try {
const registry = await this.http.get<Registry>(`/${name}`)
const registry = await this.getRegistry(name)
const entries = Object.values(registry.versions)
.map(item => [item.version, pick(item, Dependency.keys)] as const)
.sort(([a], [b]) => compare(b, a))
Expand Down Expand Up @@ -120,14 +124,14 @@ class Installer extends Service {
await fsp.writeFile(filename, JSON.stringify(this.manifest, null, 2))
}

install() {
private _install() {
const args: string[] = []
if (this.agent !== 'yarn') args.push('install')
args.push('--registry', this.registry)
return this.exec(this.agent, args)
}

installDep = async (deps: Dict<string>) => {
async install(deps: Dict<string>) {
const oldPayload = await this.get()
await this.override(deps)

Expand All @@ -140,7 +144,7 @@ class Installer extends Service {
}

if (shouldInstall) {
const code = await this.install()
const code = await this._install()
if (code) return code
}

Expand All @@ -164,9 +168,9 @@ namespace Installer {
}

export const Config: Schema<Config> = Schema.object({
endpoint: Schema.string().role('link').description('插件的下载源。默认跟随当前项目的 npm config。'),
timeout: Schema.number().role('time').default(Time.second * 5).description('获取插件数据的超时时间。'),
}).description('插件源设置')
endpoint: Schema.string().role('link'),
timeout: Schema.number().role('time').default(Time.second * 5),
})
}

export default Installer
28 changes: 28 additions & 0 deletions plugins/market/src/node/locales/message.zh-CN.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
commands.plugin:
description: 插件管理

commands.plugin.install:
description: 安装依赖
messages:
expect-name: 请输入依赖名。
already-installed: 该依赖已安装。
unknown-error: 遇到未知错误。
success: 安装成功!

commands.plugin.uninstall:
description: 卸载依赖
messages:
expect-name: 请输入依赖名。
not-installed: 该依赖未安装。
unknown-error: 遇到未知错误。
success: 卸载成功!

commands.plugin.upgrade:
description: 升级依赖
messages:
all-updated: 所有依赖已是最新版本。
available: 有可用的依赖更新:
prompt: 输入「Y」升级全部依赖,输入「N」取消操作。
cancelled: 已取消操作。
unknown-error: 遇到未知错误。
success: 升级成功!
10 changes: 10 additions & 0 deletions plugins/market/src/node/locales/schema.zh-CN.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
registry:
$description: 插件源设置
endpoint: 插件的下载源。默认跟随当前项目的 npm config。
timeout: 获取插件数据的超时时间。

search:
$description: 搜索设置
endpoint: 用于搜索插件市场的网址。默认跟随插件源设置。
timeout: 搜索插件市场的超时时间。
proxyAgent: 用于搜索插件市场的代理。
8 changes: 4 additions & 4 deletions plugins/market/src/node/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ namespace MarketProvider {
}

export const Config: Schema<Config> = Schema.object({
endpoint: Schema.string().role('link').description('用于搜索插件市场的网址。默认跟随 registry 设置。'),
timeout: Schema.number().role('time').default(Time.second * 30).description('搜索插件市场的超时时间。'),
proxyAgent: Schema.string().role('link').description('用于搜索插件市场的代理。'),
}).description('搜索设置')
endpoint: Schema.string().role('link'),
timeout: Schema.number().role('time').default(Time.second * 30),
proxyAgent: Schema.string().role('link'),
})
}

export default MarketProvider

0 comments on commit 12044ae

Please sign in to comment.