Skip to content

Commit

Permalink
feat(config): incremental sync packages
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 29, 2024
1 parent e50b923 commit 23ee289
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 153 deletions.
8 changes: 4 additions & 4 deletions packages/client/client/plugins/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export type LegacyMenuItem = Partial<ActionOptions> & Omit<MenuItem, 'id'>

export interface MenuItem {
id: string
label?: MaybeGetter<string>
type?: MaybeGetter<string>
icon?: MaybeGetter<string>
label?: MaybeGetter<string | undefined>
type?: MaybeGetter<string | undefined>
icon?: MaybeGetter<string | undefined>
order?: number
}

Expand Down Expand Up @@ -90,7 +90,7 @@ export default class ActionService extends Service {
for (const action of Object.values(ctx.internal.actions)) {
if (!action.shortcut) continue
const keys = action.shortcut.split('+').map(key => key.toLowerCase().trim())
let ctrlKey = false, shiftKey = false, metaKey = false, code: string
let ctrlKey = false, shiftKey = false, metaKey = false, code: string | undefined
for (const key of keys) {
switch (key) {
case 'shift': shiftKey = true; continue
Expand Down
24 changes: 19 additions & 5 deletions packages/client/client/plugins/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Context } from '../context'
import { Service } from '../utils'
import { receive, store } from '../data'
import { ForkScope } from 'cordis'
import { Dict } from 'cosmokit'
import { defineProperty, Dict } from 'cosmokit'

declare module '../context' {
interface Context {
Expand All @@ -26,7 +26,7 @@ export function unwrapExports(module: any) {
type LoaderFactory = (ctx: Context, url: string) => Promise<ForkScope>

function jsLoader(ctx: Context, exports: {}) {
return ctx.plugin(unwrapExports(exports), ctx.$entry.data)
return ctx.plugin(unwrapExports(exports), ctx.$entry!.data)
}

function cssLoader(ctx: Context, link: HTMLLinkElement) {
Expand All @@ -36,6 +36,9 @@ function cssLoader(ctx: Context, link: HTMLLinkElement) {
})
}

defineProperty(jsLoader, 'reusable', true)
defineProperty(cssLoader, 'reusable', true)

const loaders: Dict<LoaderFactory> = {
async [`.css`](ctx, url) {
const link = document.createElement('link')
Expand Down Expand Up @@ -68,12 +71,23 @@ export default class LoaderService extends Service {
constructor(ctx: Context) {
super(ctx, '$loader', true)

receive('entry-data', ({ id, data }) => {
receive('entry:data', ({ id, data }) => {
const entry = store.entry?.[id]
if (!entry) return
entry.data = data
this.extensions[id].data.value = data
})

receive('entry:patch', ({ id, data, key }) => {
const entry = store.entry?.[id]
if (!entry) return
let node = this.extensions[id].data.value
const parts: string[] = key ? key.split('.') : []
while (parts.length) {
const part = parts.shift()!
node = node[part]
}
Object.assign(node, data)
})
}

initTask = new Promise<void>((resolve) => {
Expand All @@ -99,7 +113,7 @@ export default class LoaderService extends Service {
const task = Promise.all(files.map(async (url) => {
for (const ext in loaders) {
if (!url.endsWith(ext)) continue
ctx.$entry.fork = await loaders[ext](ctx, url)
ctx.$entry!.fork = await loaders[ext](ctx, url)
}
}))
task.finally(() => this.extensions[key].done.value = true)
Expand Down
1 change: 1 addition & 0 deletions packages/registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@cordisjs/registry",
"description": "Scan Package Manager for Koishi Plugins",
"version": "7.0.3",
"type": "module",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/registry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default interface Scanner extends SearchResult {
}

export default class Scanner {
private cache: Dict<SearchObject>
private cache!: Dict<SearchObject>

constructor(public request: <T>(url: string, config?: RequestConfig) => Promise<T>) {
defineProperty(this, 'progress', 0)
Expand Down
101 changes: 65 additions & 36 deletions packages/registry/src/local.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,117 @@
/// <reference types="@types/node" />

import { defineProperty, Dict, pick } from 'cosmokit'
import { dirname } from 'path'
import { readdir, readFile } from 'fs/promises'
import { Awaitable, defineProperty, Dict, pick } from 'cosmokit'
import { dirname } from 'node:path'
import { createRequire } from 'node:module'
import { readdir, readFile } from 'node:fs/promises'
import { PackageJson, SearchObject, SearchResult } from './types'
import { conclude } from './utils'

export interface LocalScanner extends SearchResult {}
interface LocalObject extends Pick<SearchObject, 'shortname' | 'workspace' | 'manifest'> {
package: Pick<PackageJson, 'name' | 'version' | 'peerDependencies' | 'peerDependenciesMeta'>
}

export class LocalScanner {
private cache: Dict<Promise<SearchObject>>
private task: Promise<SearchObject[]>
export interface LocalScanner extends SearchResult<LocalObject>, LocalScanner.Options {}

constructor(public baseDir: string) {
defineProperty(this, 'cache', {})
export namespace LocalScanner {
export interface Options {
onFailure?(reason: any, name: string): void
onSuccess?(object: LocalObject): Awaitable<void>
}
}

onError(reason: any, name: string) {}
function clear(object: Dict) {
for (const key of Object.keys(object)) {
delete object[key]
}
}

export class LocalScanner {
public cache: Dict<LocalObject> = Object.create(null)

private subTasks!: Dict<Promise<LocalObject | undefined>>
private mainTask?: Promise<LocalObject[]>
private require!: NodeRequire

constructor(public baseDir: string, options: LocalScanner.Options = {}) {
defineProperty(this, 'require', createRequire(baseDir + '/package.json'))
Object.assign(this, options)
}

async _collect() {
this.cache = {}
clear(this.cache)
clear(this.subTasks)
let root = this.baseDir
const tasks: Promise<void>[] = []
const directoryTasks: Promise<void>[] = []
while (1) {
tasks.push(this.loadDirectory(root))
directoryTasks.push(this.loadDirectory(root))
const parent = dirname(root)
if (root === parent) break
root = parent
}
await Promise.all(tasks)
return Promise.all(Object.values(this.cache))
await Promise.all(directoryTasks)
await Promise.allSettled(Object.values(this.subTasks))
return Object.values(this.cache)
}

async collect(forced = false) {
if (forced) delete this.task
this.objects = await (this.task ||= this._collect())
if (forced) delete this.mainTask
this.objects = await (this.mainTask ||= this._collect())
}

private async loadDirectory(baseDir: string) {
const base = baseDir + '/node_modules'
const files = await readdir(base).catch(() => [])
for (const name of files) {
if (name.startsWith('cordis-plugin-')) {
this.cache[name] ||= this.loadPackage(name)
this.loadPackage(name)
} else if (name.startsWith('@')) {
const base2 = base + '/' + name
const files = await readdir(base2).catch(() => [])
for (const name2 of files) {
if (name === '@cordisjs' && name2.startsWith('plugin-') || name2.startsWith('cordis-plugin-')) {
this.cache[name + '/' + name2] ||= this.loadPackage(name + '/' + name2)
this.loadPackage(name + '/' + name2)
}
}
}
}
}

private async loadPackage(name: string) {
async loadPackage(name: string) {
return this.subTasks[name] ||= this._loadPackage(name)
}

private async _loadPackage(name: string) {
try {
return await this.parsePackage(name)
const [meta, workspace] = await this.loadManifest(name)
const object: LocalObject = {
workspace,
manifest: conclude(meta),
shortname: meta.name.replace(/(cordis-|^@cordisjs\/)plugin-/, ''),
package: pick(meta, [
'name',
'version',
'peerDependencies',
'peerDependenciesMeta',
]),
}
this.cache[name] = object
await this.onSuccess?.(object)
return object
} catch (error) {
this.onError(error, name)
this.onFailure?.(error, name)
}
}

private async loadManifest(name: string) {
const filename = require.resolve(name + '/package.json')
const filename = this.require.resolve(name + '/package.json')
const meta: PackageJson = JSON.parse(await readFile(filename, 'utf8'))
meta.peerDependencies ||= {}
meta.peerDependenciesMeta ||= {}
return [meta, !filename.includes('node_modules')] as const
}

protected async parsePackage(name: string) {
const [data, workspace] = await this.loadManifest(name)
return {
workspace,
manifest: conclude(data),
shortname: data.name.replace(/(cordis-|^@cordisjs\/)plugin-/, ''),
package: pick(data, [
'name',
'version',
'peerDependencies',
'peerDependenciesMeta',
]),
} as SearchObject
toJSON(): SearchResult<LocalObject> {
return pick(this, ['total', 'time', 'objects'])
}
}
4 changes: 2 additions & 2 deletions packages/registry/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ export namespace Score {
}
}

export interface SearchResult {
export interface SearchResult<T = SearchObject> {
total: number
time: string
objects: SearchObject[]
objects: T[]
version?: number
forceTime?: number
}
2 changes: 1 addition & 1 deletion packages/registry/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "../../tsconfig.base",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outFile": "lib/index.d.ts",
},
"include": [
"src",
Expand Down
2 changes: 1 addition & 1 deletion plugins/config/client/components/forks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const dialogFork = computed({
set: (value) => ctx.manager.dialogFork.value = value,
})
const local = computed(() => ctx.manager.data.value.packages?.[dialogFork.value])
const local = computed(() => ctx.manager.data.value.packages[dialogFork.value])
function getLabel(tree: Node) {
return `${tree.label ? `${tree.label} ` : ''}[${tree.path}]`
Expand Down
25 changes: 12 additions & 13 deletions plugins/config/client/components/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
v-model="showRemove"
title="确认移除"
destroy-on-close
@closed="remove = null"
@closed="remove = undefined"
>
<template v-if="remove">
确定要移除{{ remove.children ? `分组 ${remove.label || remove.path}` : `插件 ${remove.label || remove.name}` }} 吗?此操作不可撤销!
</template>
<template #footer>
<el-button @click="showRemove = false">取消</el-button>
<el-button type="danger" @click="(showRemove = false, ctx.manager.remove(remove), tree?.activate())">确定</el-button>
<el-button type="danger" @click="(showRemove = false, ctx.manager.remove(remove!), tree?.activate())">确定</el-button>
</template>
</el-dialog>

Expand All @@ -47,27 +47,27 @@
title="重命名"
destroy-on-close
@open="handleOpen"
@closed="rename = null"
@closed="rename = undefined"
>
<template v-if="rename">
<el-input ref="inputEl" v-model="input" @keydown.enter.stop.prevent="renameItem(rename, input)"/>
</template>
<template #footer>
<el-button @click="showRename = false">取消</el-button>
<el-button type="primary" @click="renameItem(rename, input)">确定</el-button>
<el-button type="primary" @click="renameItem(rename!, input)">确定</el-button>
</template>
</el-dialog>

<el-dialog
:model-value="groupCreate !== null"
@update:model-value="groupCreate = null"
:model-value="groupCreate !== undefined"
@update:model-value="groupCreate = undefined"
title="创建分组"
destroy-on-close
@open="handleOpen"
>
<el-input ref="inputEl" v-model="input" @keydown.enter.stop.prevent="createGroup(input)"/>
<template #footer>
<el-button @click="groupCreate = null">取消</el-button>
<el-button @click="groupCreate = undefined">取消</el-button>
<el-button type="primary" @click="createGroup(input)">确定</el-button>
</template>
</el-dialog>
Expand All @@ -86,6 +86,7 @@ import PluginSettings from './plugin.vue'
const route = useRoute()
const router = useRouter()
const ctx = useContext()
const current = computed(() => ctx.manager.current.value)
const plugins = computed(() => ctx.manager.plugins.value)
Expand Down Expand Up @@ -121,7 +122,7 @@ const remove = ref<Node>()
const showRemove = ref(false)
const rename = ref<Node>()
const showRename = ref(false)
const groupCreate = ref<string>(null)
const groupCreate = ref<string>()
watch(remove, (value) => {
if (value) showRemove.value = true
Expand All @@ -136,8 +137,6 @@ watch(() => plugins.value.paths[path.value], (value) => {
if (value) config.value = clone(value.config)
}, { immediate: true })
const ctx = useContext()
ctx.define('config.tree', ctx.manager.current)
ctx.action('config.tree.add-plugin', {
Expand All @@ -159,7 +158,7 @@ async function createGroup(label: string) {
label,
})
router.replace('/plugins/' + id)
groupCreate.value = null
groupCreate.value = undefined
}
ctx.action('config.tree.clone', {
Expand Down Expand Up @@ -201,7 +200,7 @@ ctx.action('config.tree.remove', {
})
function checkConfig(name: string) {
let schema = ctx.manager.data.value.packages[name]?.runtime.schema
let schema = ctx.manager.data.value.packages[name]?.runtime?.schema
if (!schema) return true
try {
(new Schema(schema))(config.value)
Expand All @@ -214,7 +213,7 @@ function checkConfig(name: string) {
ctx.action('config.tree.save', {
shortcut: 'ctrl+s',
disabled: (scope) => !scope?.config?.tree || !['config'].includes(router.currentRoute.value?.meta?.activity.id),
disabled: (scope) => !scope?.config?.tree || !['config'].includes(router.currentRoute.value?.meta?.activity?.id!),
action: async ({ config: { tree } }) => {
const { disabled } = tree
if (!disabled && !checkConfig(tree.name)) return
Expand Down
Loading

0 comments on commit 23ee289

Please sign in to comment.