@koishi-ce/plugin-config 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{Schema as e,ScopeStatus as t,Service as n,clone as r,icons as i,message as a,router as o,send as s,store as c,useContext as l,useI18nText as u,useMenu as d}from"@koishi-ce/client";import{Fragment as f,computed as p,createBlock as m,createCommentVNode as h,createElementBlock as g,createElementVNode as _,createTextVNode as v,createVNode as y,defineComponent as b,h as x,inject as S,nextTick as C,normalizeClass as w,onActivated as T,openBlock as E,provide as D,ref as O,renderList as k,renderSlot as A,resolveComponent as j,toDisplayString as M,unref as N,watch as P,withCtx as F,withKeys as I,withModifiers as L}from"vue";import{useRoute as R,useRouter as z}from"vue-router";var B=O(),V=O(),H=[`@koishi-ce/plugin-console`,`@koishi-ce/plugin-config`,`@koishi-ce/plugin-server`];function U(e){if(e.name&&(H.includes(`@koishi-ce/plugin-${e.name}`)||H.includes(`@koishijs/plugin-${e.name}`)))return!0;if(e.children)return e.children.some(U)}function W(e){function t(e,t){r.has(e)||e!==`console`&&(n.using[e]={required:t})}let n={impl:[],using:{},peer:{}},r=new Set,i=c.packages,a=i?.[e];if(!i||!a)return n;for(let e in a.package.peerDependencies??{}){if(!e.includes(`@koishi-ce/plugin-`)&&!e.includes(`@koishijs/plugin-`)&&!e.includes(`koishi-plugin-`)||H.includes(e))continue;let t=!a.package.peerDependenciesMeta?.[e]?.optional,o=!!i[e]?.runtime?.id;n.peer[e]={required:t,active:o};for(let t of i[e]?.manifest?.service.implements??[])r.add(t)}for(let e of a.manifest.service.implements)e!==`adapter`&&n.impl.push(e);for(let e of a.runtime?.required??[])t(e,!0);for(let e of a.runtime?.optional??[])t(e,!1);return a.runtime?.id&&!a.runtime?.forkable&&(n.warning=!0),a.runtime?.schema||(n.warning=!0),n}var G=p(()=>Object.fromEntries(Object.keys(c.packages??{}).filter(e=>e).map(e=>[e,W(e)]))),K=O();function q(e){if(!e)return e;if(e.includes(`/`)){let[t,n]=e.split(`/`);return[`${t}/koishi-plugin-${n}`].find(e=>e in(c.packages||{}))}return[`@koishijs/plugin-${e}`,`koishi-plugin-${e}`].find(e=>e in(c.packages||{}))}var J=p(()=>{if(K.value)return q(K.value.name)}),Y=p(()=>{let e=G.value[J.value??``];if(e){if(e.warning&&K.value?.disabled)return`warning`;for(let t in e.using)if(t in(c.services||{})){if(e.impl.includes(t))return`warning`}else if(e.using[t]?.required)return`warning`}});function ee(e,t){let n=[];if(!t)return n;for(let r in t){if(r.startsWith(`$`))continue;let i=t[r],a={config:i,parent:e};r.startsWith(`~`)&&(a.disabled=!0,r=r.slice(1)),a.name=r.split(`:`,1)[0]??``,a.id=r,a.path=r.slice(a.name.length+1);let o=i?.$label;o!==void 0&&(a.label=o),r.startsWith(`group:`)&&(a.children=ee(a,i)),n.push(a)}return n}var X=p(()=>{let e={name:``,id:``,path:``,label:`全局设置`,config:c.config,children:[]},t=[e],n=[],r={},i={"":e};for(let n of ee(e,c.config?.plugins))t.push(n),a(n);function a(e){!e.config?.$collapsed&&e.children&&n.push(e.path),(r[e.name]||=[]).push(e.path),i[e.path]=e,e.children?.forEach(a)}return{data:t,forks:r,paths:i,expanded:n}});function Z(e){switch(c.packages?.[q(e.name)??``]?.runtime?.forks?.[e.path]?.status){case t.PENDING:return`pending`;case t.LOADING:return`loading`;case t.ACTIVE:return`active`;case t.FAILED:return`failed`;case t.DISPOSED:return`disposed`;default:return`disabled`}}async function Q(e){let t=e.parent?.path??``;s(`manager/remove`,t,e.id),await o.replace(`/plugins/${t}`)}var te=[`id`],ne={class:`text-left`},re={class:`path`},ie={class:`text-right`},ae={class:`actions`},oe=[`onClick`],se=[`onClick`],ce={class:`left`},le={class:`right`},ue=b({__name:`forks`,setup(e){let t=p(()=>B.value?.replace(/(koishi-|^@koishijs\/)plugin-/,``)),n=p(()=>c.packages?.[B.value]);function r(e){return`${e.label?`${e.label} `:``}[${e.path}]`}function i(e){let t=[r(e)];for(;e.parent;)e=e.parent,t.unshift(r(e));return t.shift(),t.join(` > `)}async function a(e){let n=t.value;e||(e=Math.random().toString(36).slice(2,8),s(`manager/unload`,``,`${n}:${e}`,{})),await o.push(`/plugins/${e}`),B.value=null}return(e,r)=>{let o=j(`k-icon`),s=j(`el-button`),c=j(`el-dialog`);return E(),m(c,{"model-value":!!N(B),"onUpdate:modelValue":r[1]||=e=>B.value=null,class:`dialog-config-fork`,"destroy-on-close":``},{header:F(({titleId:e,titleClass:t})=>[_(`span`,{id:e,class:w(t)},M(N(B)+(n.value?.workspace?` (工作区)`:``)),11,te)]),footer:F(()=>[_(`div`,ce,[N(X).forks[t.value]?.length?(E(),g(f,{key:0},[v(` 此插件目前存在 `+M(N(X).forks[t.value]?.length)+` 份配置。 `,1)],64)):(E(),g(f,{key:1},[v(` 此插件尚未被配置。 `)],64))]),_(`div`,le,[y(s,{onClick:r[0]||=L(e=>a(),[`stop`])},{default:F(()=>[...r[2]||=[v(`添加新配置`,-1)]]),_:1})])]),default:F(()=>[_(`table`,null,[(E(!0),g(f,null,k(N(X).forks[t.value],e=>(E(),g(`tr`,{key:e},[_(`td`,ne,[_(`span`,{class:w([`status-light`,N(Z)(N(X).paths[e])])},null,2),_(`span`,re,M(i(N(X).paths[e])),1)]),_(`td`,ie,[_(`span`,ae,[_(`span`,{class:`action`,onClick:L(t=>a(e),[`stop`])},[y(o,{name:`arrow-right`})],8,oe),_(`span`,{class:`action`,onClick:L(t=>N(Q)(N(X).paths[e]),[`stop`])},[y(o,{name:`delete`})],8,se)])])]))),128))])]),_:1},8,[`model-value`])}}}),de=b({__name:`global`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)});return(t,n)=>{let r=j(`k-form`);return E(),m(r,{schema:N(c).packages[``].runtime.schema,initial:e.current.config,modelValue:i.value,"onUpdate:modelValue":n[0]||=e=>i.value=e},null,8,[`schema`,`initial`,`modelValue`])}}}),fe={class:`modifier`},pe=b({__name:`modifier`,props:{modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue?.$filter,set:e=>r(`update:modelValue`,{...n.modelValue,$filter:e})});return(e,t)=>{let n=j(`k-filter`);return E(),g(`div`,fe,[t[1]||=_(`h2`,{class:`k-schema-header`},`过滤器设置`,-1),y(n,{modelValue:i.value,"onUpdate:modelValue":t[0]||=e=>i.value=e},null,8,[`modelValue`])])}}}),me=b({__name:`group`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)});return(e,t)=>(E(),m(pe,{modelValue:i.value,"onUpdate:modelValue":t[0]||=e=>i.value=e},null,8,[`modelValue`]))}}),$=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},he=$(me,[[`__scopeId`,`data-v-826f5ec2`]]),ge=b({__name:`plugin`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=l(),a=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)}),o=p(()=>G.value[J.value]),u=p(()=>c.packages[J.value]),d=p(()=>u.value.workspace?`请检查插件源代码。`:`请联系插件作者并反馈此问题。`);return P(u,e=>{!e||e.runtime||s(`config/request-runtime`,e.name)},{immediate:!0}),D(`plugin:name`,J),D(`plugin:env`,o),D(`manager.settings.local`,u),D(`manager.settings.config`,a),D(`manager.settings.current`,p(()=>n.current)),(t,n)=>{let r=j(`k-comment`),s=j(`k-slot`),l=j(`k-slot-item`),p=j(`k-activity-link`),b=j(`k-markdown`),x=j(`k-form`);return N(J)?(E(),g(f,{key:0},[u.value.runtime?u.value.runtime.failed?(E(),m(r,{key:1,type:`danger`},{default:F(()=>[_(`p`,null,`插件加载失败,这可能是插件本身的问题所致。`+M(d.value),1)]),_:1})):(E(),m(s,{key:2,name:`plugin-details`},{default:F(()=>[h(` 依赖信息:peer 插件依赖与注入服务的加载状态提示 `),y(l,{order:800},{default:F(()=>[y(s,{name:`plugin-dependency`,single:``},{default:F(()=>[(E(!0),g(f,null,k(o.value.peer,({required:e,active:t},n)=>(E(),m(r,{key:n,type:t?`success`:e?`warning`:`primary`},{default:F(()=>[_(`p`,null,M(e?`必需`:`可选`)+`依赖:`+M(n)+` (`+M(t?`已加载`:`未加载`)+`) `,1)]),_:2},1032,[`type`]))),128)),(E(!0),g(f,null,k(o.value.using,({required:e},t)=>(E(),m(r,{key:t,type:t in N(c).services?`success`:e?`warning`:`primary`},{default:F(()=>[_(`p`,null,M(e?`必需`:`可选`)+`服务:`+M(t)+` (`+M(t in N(c).services?`已加载`:`未加载`)+`) `,1)]),_:2},1032,[`type`]))),128))]),_:1})]),_:1}),h(` 实现的服务:本插件将提供哪些服务 `),y(l,{order:600},{default:F(()=>[(E(!0),g(f,null,k(o.value.impl,t=>(E(),g(f,{key:t},[t in N(c).services&&e.current.disabled?(E(),m(r,{key:0,type:`warning`},{default:F(()=>[_(`p`,null,`此插件将会提供 `+M(t)+` 服务,但此服务已被其他插件实现。`,1)]),_:2},1024)):(E(),m(r,{key:1,type:e.current.disabled?`primary`:`success`},{default:F(()=>[_(`p`,null,`此插件`+M(e.current.disabled?`启用后将会提供`:`提供了`)+` `+M(t)+` 服务。`,1)]),_:2},1032,[`type`]))],64))),128))]),_:1}),h(` 可重用性:不可重用插件的重复启用警告与多份配置提示 `),y(l,{order:400},{default:F(()=>[u.value.runtime.id&&!u.value.runtime.forkable&&e.current.disabled?(E(),m(r,{key:0,type:`warning`},{default:F(()=>[...n[4]||=[_(`p`,null,`此插件已在运行且不可重用,启用可能会导致非预期的问题。`,-1)]]),_:1})):h(`v-if`,!0),N(X).forks[e.current.name]?.length>1?(E(),m(r,{key:1,type:`primary`},{default:F(()=>[_(`p`,null,[n[5]||=v(`此插件存在多份配置,`,-1),_(`span`,{class:`k-link`,onClick:n[0]||=L(e=>B.value=N(J),[`stop`])},`点击前往管理`),n[6]||=v(`。`,-1)])]),_:1})):h(`v-if`,!0)]),_:1}),h(` 提供的页面:本插件注册的控制台活动页 `),y(l,{order:300},{default:F(()=>[(E(!0),g(f,null,k(N(i).$router.pages,(t,i)=>(E(),g(f,{key:i},[t.ctx.extension?.paths.includes(e.current.path)&&!t.disabled()?(E(),m(r,{key:0,type:`success`},{default:F(()=>[_(`p`,null,[n[7]||=_(`span`,null,`此插件提供了页面:`,-1),y(p,{id:t.id},null,8,[`id`])])]),_:2},1024)):h(`v-if`,!0)],64))),128))]),_:1}),h(` 用法说明:插件自带的 Markdown 文档 `),u.value.runtime?.usage?(E(),m(l,{key:0,order:-200},{default:F(()=>[y(b,{unsafe:``,class:`usage`,source:u.value.runtime?.usage},null,8,[`source`])]),_:1})):h(`v-if`,!0),h(` 过滤器设置:编辑 $filter 上下文过滤条件 `),y(l,{order:-600},{default:F(()=>[u.value.runtime.filter===!1?h(`v-if`,!0):(E(),m(pe,{key:0,modelValue:a.value,"onUpdate:modelValue":n[1]||=e=>a.value=e},null,8,[`modelValue`]))]),_:1}),h(` 配置表单:按 schema 生成的设置项 `),y(l,{order:-1e3},{default:F(()=>[u.value.runtime.schema?(E(),m(x,{key:1,schema:u.value.runtime.schema,initial:e.current.config,modelValue:a.value,"onUpdate:modelValue":n[2]||=e=>a.value=e},{hint:F(()=>[v(M(d.value),1)]),_:1},8,[`schema`,`initial`,`modelValue`])):(E(),m(r,{key:0,type:`warning`},{default:F(()=>[_(`p`,null,`此插件未声明配置项,这可能并非预期行为。`+M(d.value),1)]),_:1}))]),_:1})]),_:1})):(E(),m(r,{key:0},{default:F(()=>[...n[3]||=[_(`p`,null,`正在加载插件配置……`,-1)]]),_:1}))],64)):(E(),m(s,{key:1,name:`plugin-missing`,single:``},{default:F(()=>[y(r,{type:`danger`},{default:F(()=>[...n[8]||=[_(`p`,null,`此插件尚未安装。`,-1)]]),_:1})]),_:1}))}}}),_e={class:`search`},ve=[`title`],ye={class:`right`},be=b({__name:`tree`,props:{modelValue:{}},emits:[`update:modelValue`],setup(e,{expose:t,emit:n}){let r=e,i=R(),a=d(`config.tree`),o=n,c=O(null),l=O(null),u=O(``);function f(e,t){return t.name.toLowerCase().includes(u.value.toLowerCase())}let p=O(!0);async function v(){await C();let e=c.value?.$el,t=e?.querySelector(`.el-tree-node.is-active`);!t||!t.offsetTop&&i.path.slice(9)||c.value.setScrollTop(t.offsetTop-(e.offsetHeight-t.offsetHeight)/2)}t({activate:v}),T(async()=>{v(),p.value=!1});function b(e){!e||p.value||v()}function x(e){return e.data.name===`group`?`分组:${e.label||e.data.path}`:e.label||e.data.name||`待添加`}function S(e){return e.data.path!==``}function D(e,t,n){return n===`inner`?t.data.id.startsWith(`group:`):t.data.path!==``||n===`next`}function k(e,t,n,r){o(`update:modelValue`,e.path),window.dispatchEvent(new MouseEvent(r.type,r))}function A(e,t,n){s(`manager/meta`,e.path,{$collapsed:null})}function I(e,t,n){s(`manager/meta`,e.path,{$collapsed:!0})}function L(e,t,n,r){let i=n===`inner`?t:t.parent,a=i.childNodes.findIndex(t=>t.data.path===e.data.path);i.data.path||--a,s(`manager/teleport`,e.data.parent?.path??``,e.data.id,i.data.path,a)}function z(e){let t=[];return e.children&&t.push(`is-group`),!e.children&&!q(e.name)&&t.push(`is-disabled`),e.path===r.modelValue&&t.push(`is-active`),t.join(` `)}return P(u,e=>{l.value.filter(e)}),(e,t)=>{let n=j(`k-icon`),r=j(`el-input`),i=j(`el-tree`),o=j(`el-scrollbar`);return E(),m(o,{class:`plugin-tree`,ref_key:`root`,ref:c},{default:F(()=>[_(`div`,_e,[y(r,{modelValue:u.value,"onUpdate:modelValue":t[0]||=e=>u.value=e},{suffix:F(()=>[y(n,{name:`search`})]),_:1},8,[`modelValue`])]),y(i,{ref_key:`tree`,ref:l,"node-key":`path`,data:N(X).data,draggable:!0,"auto-expand-parent":!1,"default-expanded-keys":N(X).expanded,"expand-on-click-node":!1,"filter-node-method":f,props:{class:z},"allow-drag":S,"allow-drop":D,onNodeClick:k,onNodeContextmenu:N(a),onNodeDrop:L,onNodeExpand:A,onNodeCollapse:I},{default:F(({node:e})=>[_(`div`,{class:`item`,ref:b},[_(`div`,{class:`label`,title:x(e)},M(x(e)),9,ve),_(`div`,ye,[N(q)(e.data.name)?(E(),g(`span`,{key:0,class:w([`status-light`,N(Z)(e.data)])},null,2)):h(`v-if`,!0)])],512)]),_:1},8,[`data`,`default-expanded-keys`,`props`,`onNodeContextmenu`])]),_:1},512)}}}),xe=b({__name:`index`,setup(t){let n=R(),i=z(),o=p({get(){let e=n.path.slice(9);return e in X.value.paths?e:``},set(e){e in X.value.paths||(e=``),i.replace(`/plugins/${e}`)}}),u=O(),d=O(``),_=O(),b=O();async function x(){await C(),_.value?.focus()}let S=O(),w=O(!1),T=O(),D=O(!1),k=O(null);P(S,e=>{e&&(w.value=!0)}),P(T,e=>{e&&(D.value=!0)}),P(()=>X.value.paths[o.value],e=>{K.value=e,u.value=r(e.config)},{immediate:!0});let A=l();A.define(`config.tree`,K),A.action(`config.tree.add-plugin`,{hidden:({config:e})=>e.tree.path&&!e.tree.children,action:({config:e})=>V.value=e.tree}),A.action(`config.tree.add-group`,{hidden:({config:e})=>e.tree.path&&!e.tree.children,action:({config:e})=>{k.value=e.tree.path}});function H(e){let t=Math.random().toString(36).slice(2,8);s(`manager/reload`,k.value,`group:${t}`,{$label:e}),i.replace(`/plugins/${t}`),k.value=null}A.action(`config.tree.clone`,{hidden:({config:e})=>!e.tree.path||!!e.tree.children,action:async({config:e})=>{let t=(e.tree.parent.path?e.tree.parent.children:X.value.data.slice(1)).findIndex(t=>t.path===e.tree.path),n=Math.random().toString(36).slice(2,8);s(`manager/unload`,e.tree.parent?.path??``,`${e.tree.name}:${n}`,e.tree.config,t+1),i.replace(`/plugins/${n}`)}}),A.action(`config.tree.manage`,{hidden:({config:e})=>!e.tree.path||!!e.tree.children,action:async({config:e})=>{B.value=e.tree.name}}),A.action(`config.tree.rename`,{disabled:({config:e})=>!e.tree.path,action:({config:e})=>{d.value=e.tree.label||(e.tree.name===`group`?e.tree.path:e.tree.name),T.value=e.tree}}),A.action(`config.tree.remove`,{disabled:({config:e})=>!e.tree.path||U(e.tree),action:({config:e})=>S.value=e.tree});function W(t){let n=c.packages[q(t)]?.runtime.schema;if(!n)return!0;try{return new e(n)(u.value),!0}catch{return a.error(`当前配置项不满足约束,请检查配置!`),!1}}A.action(`config.tree.save`,{shortcut:`ctrl+s`,disabled:e=>!e?.config?.tree||![`config`].includes(i.currentRoute.value?.meta?.activity.id),action:async({config:{tree:e}})=>{let{disabled:t,path:n}=e;if(!(!t&&!W(e.name))){if(!n)return s(`manager/app-reload`,u.value);try{await G(e,t?`unload`:`reload`),a.success(t?`配置已保存。`:`配置已重载。`)}catch{a.error(`操作失败,请检查日志!`)}}}}),A.action(`config.tree.toggle`,{disabled:({config:e})=>!e.tree.path||U(e.tree),action:async({config:{tree:e}})=>{let{disabled:t,name:n}=e;if(!(t&&!W(e.name)))try{await G(e,t?`reload`:`unload`),a.success((n===`group`?`分组`:`插件`)+(t?`已启用。`:`已停用。`))}catch{a.error(`操作失败,请检查日志!`)}}});async function G(e,t){return await s(`manager/${t}`,e.parent?.path??``,e.id,u.value)}function J(e,t){D.value=!1,e.label=t,s(`manager/meta`,e.path,{$label:t||null})}return(e,t)=>{let n=j(`k-content`),r=j(`el-button`),i=j(`el-dialog`),a=j(`el-input`),s=j(`k-layout`);return E(),m(s,{menu:`config.tree`,"menu-data":N(K)},{header:F(()=>[h(` 根节点:全局设置 `),N(K).path?N(K).children?(E(),g(f,{key:1},[h(` 分组节点 `),v(` 分组:`+M(N(K).label||N(K).id),1)],64)):(E(),g(f,{key:2},[h(` 普通插件节点 `),v(M(N(K).label||N(K).name)+` [`+M(N(K).path)+`] `,1)],64)):(E(),g(f,{key:0},[v(`全局设置`)],64))]),left:F(()=>[y(be,{ref_key:`tree`,ref:b,modelValue:o.value,"onUpdate:modelValue":t[0]||=e=>o.value=e},null,8,[`modelValue`])]),default:F(()=>[(E(),m(n,{class:`plugin-view`,key:o.value},{default:F(()=>[N(K).path?N(K).children?(E(),m(he,{key:1,modelValue:u.value,"onUpdate:modelValue":t[2]||=e=>u.value=e,current:N(K)},null,8,[`modelValue`,`current`])):(E(),m(ge,{key:2,current:N(K),modelValue:u.value,"onUpdate:modelValue":t[3]||=e=>u.value=e},null,8,[`current`,`modelValue`])):(E(),m(de,{key:0,current:N(K),modelValue:u.value,"onUpdate:modelValue":t[1]||=e=>u.value=e},null,8,[`current`,`modelValue`]))]),_:1})),y(i,{modelValue:w.value,"onUpdate:modelValue":t[6]||=e=>w.value=e,title:`确认移除`,"destroy-on-close":``,onClosed:t[7]||=e=>S.value=null},{footer:F(()=>[y(r,{onClick:t[4]||=e=>w.value=!1},{default:F(()=>[...t[19]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`danger`,onClick:t[5]||=e=>(w.value=!1,N(Q)(S.value),b.value?.activate())},{default:F(()=>[...t[20]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[S.value?(E(),g(f,{key:0},[v(` 确定要移除`+M(S.value.children?`分组 ${S.value.label||S.value.path}`:`插件 ${S.value.label||S.value.name}`)+` 吗?此操作不可撤销! `,1)],64)):h(`v-if`,!0)]),_:1},8,[`modelValue`]),y(i,{modelValue:D.value,"onUpdate:modelValue":t[12]||=e=>D.value=e,title:`重命名`,"destroy-on-close":``,onOpen:x,onClosed:t[13]||=e=>T.value=null},{footer:F(()=>[y(r,{onClick:t[10]||=e=>D.value=!1},{default:F(()=>[...t[21]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`primary`,onClick:t[11]||=e=>J(T.value,d.value)},{default:F(()=>[...t[22]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[T.value?(E(),m(a,{key:0,ref_key:`inputEl`,ref:_,modelValue:d.value,"onUpdate:modelValue":t[8]||=e=>d.value=e,onKeydown:t[9]||=I(L(e=>J(T.value,d.value),[`stop`,`prevent`]),[`enter`])},null,8,[`modelValue`])):h(`v-if`,!0)]),_:1},8,[`modelValue`]),y(i,{"model-value":k.value!==null,"onUpdate:modelValue":t[18]||=e=>k.value=null,title:`创建分组`,"destroy-on-close":``,onOpen:x},{footer:F(()=>[y(r,{onClick:t[16]||=e=>k.value=null},{default:F(()=>[...t[23]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`primary`,onClick:t[17]||=e=>H(d.value)},{default:F(()=>[...t[24]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[y(a,{ref_key:`inputEl`,ref:_,modelValue:d.value,"onUpdate:modelValue":t[14]||=e=>d.value=e,onKeydown:t[15]||=I(L(e=>H(d.value),[`stop`,`prevent`]),[`enter`])},null,8,[`modelValue`])]),_:1},8,[`model-value`])]),_:1},8,[`menu-data`])}}}),Se={class:`content`},Ce=[`onClick`],we=b({__name:`select`,setup(e){let t=u(),n=O(``),r=O(),i=S(`plugin-select-filter`,e=>!0),a=p(()=>Object.values(c.packages).filter(({name:e,shortname:t})=>e&&t.includes(n.value.toLowerCase())&&i(c.packages[e])));function l(e){let t=V.value.path,r=Math.random().toString(36).slice(2,8);V.value=null,n.value=``,s(`manager/unload`,t,`${e}:${r}`,{}),o.push(`/plugins/${r}`)}return P(V,async e=>{e&&(await C(),await r.value.focus())},{flush:`post`}),(e,i)=>{let o=j(`k-icon`),s=j(`el-input`),u=j(`k-markdown`),d=j(`el-scrollbar`),p=j(`el-dialog`);return N(c).packages?(E(),m(p,{key:0,modelValue:!!N(V),"onUpdate:modelValue":i[1]||=e=>V.value=null,class:`plugin-select`},{header:F(()=>[A(e.$slots,`title`,{packages:a.value},()=>[i[2]||=_(`span`,{class:`title`},`选择插件`,-1)]),y(s,{ref_key:`input`,ref:r,modelValue:n.value,"onUpdate:modelValue":i[0]||=e=>n.value=e},{suffix:F(()=>[y(o,{name:`search`})]),_:1},8,[`modelValue`])]),default:F(()=>[A(e.$slots,`tabs`,{packages:a.value}),_(`div`,Se,[y(d,null,{default:F(()=>[(E(!0),g(f,null,k(a.value,({name:e,shortname:n,manifest:r})=>(E(),g(`div`,{class:`package`,key:e,onClick:L(e=>l(n),[`stop`])},[_(`h3`,null,M(n),1),y(u,{inline:``,class:`desc`,source:N(t)(r.description)},null,8,[`source`])],8,Ce))),128))]),_:1})])]),_:3},8,[`modelValue`])):h(`v-if`,!0)}}}),Te={},Ee={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function De(e,t){return E(),g(`svg`,Ee,[...t[0]||=[_(`path`,{fill:`currentColor`,d:`M452 60H488C499 60 508 68.95 508 80C508 91.05 499 100 488 100H452V136C452 147 443 156 432 156C420.1 156 412 147 412 136V100H376C364.1 100 356 91.05 356 80C356 68.95 364.1 60 376 60H412V24C412 12.95 420.1 4 432 4C443 4 452 12.95 452 24V60zM280.8 133.1L490.1 222.8C503.4 228.5 512 241.5 512 256C512 270.5 503.4 283.5 490.1 289.2L280.8 378.9C272.1 382.3 264.5 384 256 384C247.5 384 239 382.3 231.2 378.9L21.9 289.2C8.614 283.5 0 270.5 0 256C0 241.5 8.614 228.5 21.9 222.8L231.2 133.1C239 129.7 247.5 128 256 128C264.5 128 272.1 129.7 280.8 133.1V133.1zM66.26 256L250.1 334.8C251.1 335.6 253.1 336 256 336C258 336 260 335.6 261.9 334.8L445.7 256L261.9 177.2C260 176.4 258 176 256 176C253.1 176 251.1 176.4 250.1 177.2L66.26 256zM59.53 350.6C66.15 359.9 65.24 372.3 57.1 380.5L250.1 462.8C251.1 463.6 253.1 464 256 464C258 464 260 463.6 261.9 462.8L452 381.3C447.4 374.4 446.5 365.2 450.5 357.3C456.5 345.4 470.9 340.6 482.7 346.5L491.8 351.1C504.2 357.3 512 369.9 512 383.7C512 398.4 503.3 411.6 489.9 417.3L280.8 506.9C272.1 510.3 264.5 512 256 512C247.5 512 239 510.3 231.2 506.9L22.81 417.6C8.971 411.7 .001 398.1 .001 383C.001 370.9 5.87 359.5 15.76 352.4L26.05 345C36.84 337.3 51.83 339.8 59.53 350.6L59.53 350.6z`},null,-1)]])}var Oe=$(Te,[[`render`,De]]),ke={},Ae={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function je(e,t){return E(),g(`svg`,Ae,[...t[0]||=[_(`path`,{fill:`currentColor`,d:`M144,0c13.3,0,24,10.8,24,24v88h-48V24C120,10.8,130.8,0,144,0z M336,0c13.3,0,24,10.8,24,24v88h-48V24
1
+ import{Schema as e,ScopeStatus as t,Service as n,clone as r,icons as i,message as a,router as o,send as s,store as c,useContext as l,useI18nText as u,useMenu as d}from"@koishi-ce/client";import{Fragment as f,computed as p,createBlock as m,createCommentVNode as h,createElementBlock as g,createElementVNode as _,createTextVNode as v,createVNode as y,defineComponent as b,h as x,inject as S,nextTick as C,normalizeClass as w,onActivated as T,openBlock as E,provide as D,ref as O,renderList as k,renderSlot as A,resolveComponent as j,toDisplayString as M,unref as N,watch as P,withCtx as F,withKeys as I,withModifiers as L}from"vue";import{useRoute as R,useRouter as z}from"vue-router";var B=O(),V=O(),H=[`@koishi-ce/plugin-console`,`@koishi-ce/plugin-config`,`@koishi-ce/plugin-server`];function U(e){if(e.name&&H.includes(q(e.name)??``))return!0;if(e.children)return e.children.some(U)}function W(e){function t(e,t){r.has(e)||e!==`console`&&(n.using[e]={required:t})}let n={impl:[],using:{},peer:{}},r=new Set,i=c.packages,a=i?.[e];if(!i||!a)return n;for(let e in a.package.peerDependencies??{}){if(!e.includes(`@koishi-ce/plugin-`)&&!e.includes(`@koishijs/plugin-`)&&!e.includes(`koishi-plugin-`)||H.includes(e))continue;let t=!a.package.peerDependenciesMeta?.[e]?.optional,o=!!i[e]?.runtime?.id;n.peer[e]={required:t,active:o};for(let t of i[e]?.manifest?.service.implements??[])r.add(t)}for(let e of a.manifest.service.implements)e!==`adapter`&&n.impl.push(e);for(let e of a.runtime?.required??[])t(e,!0);for(let e of a.runtime?.optional??[])t(e,!1);return a.runtime?.id&&!a.runtime?.forkable&&(n.warning=!0),a.runtime?.schema||(n.warning=!0),n}var G=p(()=>Object.fromEntries(Object.keys(c.packages??{}).filter(e=>e).map(e=>[e,W(e)]))),K=O();function q(e){if(!e)return e;if(e.startsWith(`./`))return Object.values(c.packages??{}).find(t=>t.paths?.includes(e))?.package?.name;if(e.includes(`/`)){let[t,n]=e.split(`/`);return[`${t}/koishi-plugin-${n}`].find(e=>e in(c.packages||{}))}return[`@koishi-ce/plugin-${e}`,`@koishijs/plugin-${e}`,`koishi-plugin-${e}`].find(e=>e in(c.packages||{}))}var J=p(()=>{if(K.value)return q(K.value.name)}),Y=p(()=>{let e=G.value[J.value??``];if(e){if(e.warning&&K.value?.disabled)return`warning`;for(let t in e.using)if(t in(c.services||{})){if(e.impl.includes(t))return`warning`}else if(e.using[t]?.required)return`warning`}});function ee(e,t){let n=[];if(!t)return n;for(let r in t){if(r.startsWith(`$`))continue;let i=t[r],a={config:i,parent:e};r.startsWith(`~`)&&(a.disabled=!0,r=r.slice(1)),a.name=r.split(`:`,1)[0]??``,a.id=r,a.path=r.slice(a.name.length+1);let o=i?.$label;o!==void 0&&(a.label=o),r.startsWith(`group:`)&&(a.children=ee(a,i)),n.push(a)}return n}var X=p(()=>{let e={name:``,id:``,path:``,label:`全局设置`,config:c.config,children:[]},t=[e],n=[],r={},i={"":e};for(let n of ee(e,c.config?.plugins))t.push(n),a(n);function a(e){!e.config?.$collapsed&&e.children&&n.push(e.path),(r[e.name]||=[]).push(e.path),i[e.path]=e,e.children?.forEach(a)}return{data:t,forks:r,paths:i,expanded:n}});function Z(e){switch(c.packages?.[q(e.name)??``]?.runtime?.forks?.[e.path]?.status){case t.PENDING:return`pending`;case t.LOADING:return`loading`;case t.ACTIVE:return`active`;case t.FAILED:return`failed`;case t.DISPOSED:return`disposed`;default:return`disabled`}}async function Q(e){let t=e.parent?.path??``;s(`manager/remove`,t,e.id),await o.replace(`/plugins/${t}`)}var te=[`id`],ne={class:`text-left`},re={class:`path`},ie={class:`text-right`},ae={class:`actions`},oe=[`onClick`],se=[`onClick`],ce={class:`left`},le={class:`right`},ue=b({__name:`forks`,setup(e){let t=p(()=>B.value?.replace(/(koishi-|^@koishijs\/)plugin-/,``)),n=p(()=>c.packages?.[B.value]);function r(e){return`${e.label?`${e.label} `:``}[${e.path}]`}function i(e){let t=[r(e)];for(;e.parent;)e=e.parent,t.unshift(r(e));return t.shift(),t.join(` > `)}async function a(e){let n=t.value;e||(e=Math.random().toString(36).slice(2,8),s(`manager/unload`,``,`${n}:${e}`,{})),await o.push(`/plugins/${e}`),B.value=null}return(e,r)=>{let o=j(`k-icon`),s=j(`el-button`),c=j(`el-dialog`);return E(),m(c,{"model-value":!!N(B),"onUpdate:modelValue":r[1]||=e=>B.value=null,class:`dialog-config-fork`,"destroy-on-close":``},{header:F(({titleId:e,titleClass:t})=>[_(`span`,{id:e,class:w(t)},M(N(B)+(n.value?.workspace?` (工作区)`:``)),11,te)]),footer:F(()=>[_(`div`,ce,[N(X).forks[t.value]?.length?(E(),g(f,{key:0},[v(` 此插件目前存在 `+M(N(X).forks[t.value]?.length)+` 份配置。 `,1)],64)):(E(),g(f,{key:1},[v(` 此插件尚未被配置。 `)],64))]),_(`div`,le,[y(s,{onClick:r[0]||=L(e=>a(),[`stop`])},{default:F(()=>[...r[2]||=[v(`添加新配置`,-1)]]),_:1})])]),default:F(()=>[_(`table`,null,[(E(!0),g(f,null,k(N(X).forks[t.value],e=>(E(),g(`tr`,{key:e},[_(`td`,ne,[_(`span`,{class:w([`status-light`,N(Z)(N(X).paths[e])])},null,2),_(`span`,re,M(i(N(X).paths[e])),1)]),_(`td`,ie,[_(`span`,ae,[_(`span`,{class:`action`,onClick:L(t=>a(e),[`stop`])},[y(o,{name:`arrow-right`})],8,oe),_(`span`,{class:`action`,onClick:L(t=>N(Q)(N(X).paths[e]),[`stop`])},[y(o,{name:`delete`})],8,se)])])]))),128))])]),_:1},8,[`model-value`])}}}),de=b({__name:`global`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)});return(t,n)=>{let r=j(`k-form`);return E(),m(r,{schema:N(c).packages[``].runtime.schema,initial:e.current.config,modelValue:i.value,"onUpdate:modelValue":n[0]||=e=>i.value=e},null,8,[`schema`,`initial`,`modelValue`])}}}),fe={class:`modifier`},pe=b({__name:`modifier`,props:{modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue?.$filter,set:e=>r(`update:modelValue`,{...n.modelValue,$filter:e})});return(e,t)=>{let n=j(`k-filter`);return E(),g(`div`,fe,[t[1]||=_(`h2`,{class:`k-schema-header`},`过滤器设置`,-1),y(n,{modelValue:i.value,"onUpdate:modelValue":t[0]||=e=>i.value=e},null,8,[`modelValue`])])}}}),me=b({__name:`group`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)});return(e,t)=>(E(),m(pe,{modelValue:i.value,"onUpdate:modelValue":t[0]||=e=>i.value=e},null,8,[`modelValue`]))}}),$=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},he=$(me,[[`__scopeId`,`data-v-826f5ec2`]]),ge=b({__name:`plugin`,props:{current:{},modelValue:{}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=l(),a=p({get:()=>n.modelValue,set:e=>r(`update:modelValue`,e)}),o=p(()=>G.value[J.value]),u=p(()=>c.packages[J.value]),d=p(()=>u.value.workspace?`请检查插件源代码。`:`请联系插件作者并反馈此问题。`);return P(u,e=>{!e||e.runtime||s(`config/request-runtime`,e.name)},{immediate:!0}),D(`plugin:name`,J),D(`plugin:env`,o),D(`manager.settings.local`,u),D(`manager.settings.config`,a),D(`manager.settings.current`,p(()=>n.current)),(t,n)=>{let r=j(`k-comment`),s=j(`k-slot`),l=j(`k-slot-item`),p=j(`k-activity-link`),b=j(`k-markdown`),x=j(`k-form`);return N(J)?(E(),g(f,{key:0},[u.value.runtime?u.value.runtime.failed?(E(),m(r,{key:1,type:`danger`},{default:F(()=>[_(`p`,null,`插件加载失败,这可能是插件本身的问题所致。`+M(d.value),1)]),_:1})):(E(),m(s,{key:2,name:`plugin-details`},{default:F(()=>[h(` 依赖信息:peer 插件依赖与注入服务的加载状态提示 `),y(l,{order:800},{default:F(()=>[y(s,{name:`plugin-dependency`,single:``},{default:F(()=>[(E(!0),g(f,null,k(o.value.peer,({required:e,active:t},n)=>(E(),m(r,{key:n,type:t?`success`:e?`warning`:`primary`},{default:F(()=>[_(`p`,null,M(e?`必需`:`可选`)+`依赖:`+M(n)+` (`+M(t?`已加载`:`未加载`)+`) `,1)]),_:2},1032,[`type`]))),128)),(E(!0),g(f,null,k(o.value.using,({required:e},t)=>(E(),m(r,{key:t,type:t in N(c).services?`success`:e?`warning`:`primary`},{default:F(()=>[_(`p`,null,M(e?`必需`:`可选`)+`服务:`+M(t)+` (`+M(t in N(c).services?`已加载`:`未加载`)+`) `,1)]),_:2},1032,[`type`]))),128))]),_:1})]),_:1}),h(` 实现的服务:本插件将提供哪些服务 `),y(l,{order:600},{default:F(()=>[(E(!0),g(f,null,k(o.value.impl,t=>(E(),g(f,{key:t},[t in N(c).services&&e.current.disabled?(E(),m(r,{key:0,type:`warning`},{default:F(()=>[_(`p`,null,`此插件将会提供 `+M(t)+` 服务,但此服务已被其他插件实现。`,1)]),_:2},1024)):(E(),m(r,{key:1,type:e.current.disabled?`primary`:`success`},{default:F(()=>[_(`p`,null,`此插件`+M(e.current.disabled?`启用后将会提供`:`提供了`)+` `+M(t)+` 服务。`,1)]),_:2},1032,[`type`]))],64))),128))]),_:1}),h(` 可重用性:不可重用插件的重复启用警告与多份配置提示 `),y(l,{order:400},{default:F(()=>[u.value.runtime.id&&!u.value.runtime.forkable&&e.current.disabled?(E(),m(r,{key:0,type:`warning`},{default:F(()=>[...n[4]||=[_(`p`,null,`此插件已在运行且不可重用,启用可能会导致非预期的问题。`,-1)]]),_:1})):h(`v-if`,!0),N(X).forks[e.current.name]?.length>1?(E(),m(r,{key:1,type:`primary`},{default:F(()=>[_(`p`,null,[n[5]||=v(`此插件存在多份配置,`,-1),_(`span`,{class:`k-link`,onClick:n[0]||=L(e=>B.value=N(J),[`stop`])},`点击前往管理`),n[6]||=v(`。`,-1)])]),_:1})):h(`v-if`,!0)]),_:1}),h(` 提供的页面:本插件注册的控制台活动页 `),y(l,{order:300},{default:F(()=>[(E(!0),g(f,null,k(N(i).$router.pages,(t,i)=>(E(),g(f,{key:i},[t.ctx.extension?.paths.includes(e.current.path)&&!t.disabled()?(E(),m(r,{key:0,type:`success`},{default:F(()=>[_(`p`,null,[n[7]||=_(`span`,null,`此插件提供了页面:`,-1),y(p,{id:t.id},null,8,[`id`])])]),_:2},1024)):h(`v-if`,!0)],64))),128))]),_:1}),h(` 用法说明:插件自带的 Markdown 文档 `),u.value.runtime?.usage?(E(),m(l,{key:0,order:-200},{default:F(()=>[y(b,{unsafe:``,class:`usage`,source:u.value.runtime?.usage},null,8,[`source`])]),_:1})):h(`v-if`,!0),h(` 过滤器设置:编辑 $filter 上下文过滤条件 `),y(l,{order:-600},{default:F(()=>[u.value.runtime.filter===!1?h(`v-if`,!0):(E(),m(pe,{key:0,modelValue:a.value,"onUpdate:modelValue":n[1]||=e=>a.value=e},null,8,[`modelValue`]))]),_:1}),h(` 配置表单:按 schema 生成的设置项 `),y(l,{order:-1e3},{default:F(()=>[u.value.runtime.schema?(E(),m(x,{key:1,schema:u.value.runtime.schema,initial:e.current.config,modelValue:a.value,"onUpdate:modelValue":n[2]||=e=>a.value=e},{hint:F(()=>[v(M(d.value),1)]),_:1},8,[`schema`,`initial`,`modelValue`])):(E(),m(r,{key:0,type:`warning`},{default:F(()=>[_(`p`,null,`此插件未声明配置项,这可能并非预期行为。`+M(d.value),1)]),_:1}))]),_:1})]),_:1})):(E(),m(r,{key:0},{default:F(()=>[...n[3]||=[_(`p`,null,`正在加载插件配置……`,-1)]]),_:1}))],64)):(E(),m(s,{key:1,name:`plugin-missing`,single:``},{default:F(()=>[y(r,{type:`danger`},{default:F(()=>[...n[8]||=[_(`p`,null,`此插件尚未安装。`,-1)]]),_:1})]),_:1}))}}}),_e={class:`search`},ve=[`title`],ye={class:`right`},be=b({__name:`tree`,props:{modelValue:{}},emits:[`update:modelValue`],setup(e,{expose:t,emit:n}){let r=e,i=R(),a=d(`config.tree`),o=n,c=O(null),l=O(null),u=O(``);function f(e,t){return t.name.toLowerCase().includes(u.value.toLowerCase())}let p=O(!0);async function v(){await C();let e=c.value?.$el,t=e?.querySelector(`.el-tree-node.is-active`);!t||!t.offsetTop&&i.path.slice(9)||c.value.setScrollTop(t.offsetTop-(e.offsetHeight-t.offsetHeight)/2)}t({activate:v}),T(async()=>{v(),p.value=!1});function b(e){!e||p.value||v()}function x(e){return e.data.name===`group`?`分组:${e.label||e.data.path}`:e.label||e.data.name||`待添加`}function S(e){return e.data.path!==``}function D(e,t,n){return n===`inner`?t.data.id.startsWith(`group:`):t.data.path!==``||n===`next`}function k(e,t,n,r){o(`update:modelValue`,e.path),window.dispatchEvent(new MouseEvent(r.type,r))}function A(e,t,n){s(`manager/meta`,e.path,{$collapsed:null})}function I(e,t,n){s(`manager/meta`,e.path,{$collapsed:!0})}function L(e,t,n,r){let i=n===`inner`?t:t.parent,a=i.childNodes.findIndex(t=>t.data.path===e.data.path);i.data.path||--a,s(`manager/teleport`,e.data.parent?.path??``,e.data.id,i.data.path,a)}function z(e){let t=[];return e.children&&t.push(`is-group`),!e.children&&!q(e.name)&&t.push(`is-disabled`),e.path===r.modelValue&&t.push(`is-active`),t.join(` `)}return P(u,e=>{l.value.filter(e)}),(e,t)=>{let n=j(`k-icon`),r=j(`el-input`),i=j(`el-tree`),o=j(`el-scrollbar`);return E(),m(o,{class:`plugin-tree`,ref_key:`root`,ref:c},{default:F(()=>[_(`div`,_e,[y(r,{modelValue:u.value,"onUpdate:modelValue":t[0]||=e=>u.value=e},{suffix:F(()=>[y(n,{name:`search`})]),_:1},8,[`modelValue`])]),y(i,{ref_key:`tree`,ref:l,"node-key":`path`,data:N(X).data,draggable:!0,"auto-expand-parent":!1,"default-expanded-keys":N(X).expanded,"expand-on-click-node":!1,"filter-node-method":f,props:{class:z},"allow-drag":S,"allow-drop":D,onNodeClick:k,onNodeContextmenu:N(a),onNodeDrop:L,onNodeExpand:A,onNodeCollapse:I},{default:F(({node:e})=>[_(`div`,{class:`item`,ref:b},[_(`div`,{class:`label`,title:x(e)},M(x(e)),9,ve),_(`div`,ye,[N(q)(e.data.name)?(E(),g(`span`,{key:0,class:w([`status-light`,N(Z)(e.data)])},null,2)):h(`v-if`,!0)])],512)]),_:1},8,[`data`,`default-expanded-keys`,`props`,`onNodeContextmenu`])]),_:1},512)}}}),xe=b({__name:`index`,setup(t){let n=R(),i=z(),o=p({get(){let e=n.path.slice(9);return e in X.value.paths?e:``},set(e){e in X.value.paths||(e=``),i.replace(`/plugins/${e}`)}}),u=O(),d=O(``),_=O(),b=O();async function x(){await C(),_.value?.focus()}let S=O(),w=O(!1),T=O(),D=O(!1),k=O(null);P(S,e=>{e&&(w.value=!0)}),P(T,e=>{e&&(D.value=!0)}),P(()=>X.value.paths[o.value],e=>{K.value=e,u.value=r(e.config)},{immediate:!0});let A=l();A.define(`config.tree`,K),A.action(`config.tree.add-plugin`,{hidden:({config:e})=>e.tree.path&&!e.tree.children,action:({config:e})=>V.value=e.tree}),A.action(`config.tree.add-group`,{hidden:({config:e})=>e.tree.path&&!e.tree.children,action:({config:e})=>{k.value=e.tree.path}});function H(e){let t=Math.random().toString(36).slice(2,8);s(`manager/reload`,k.value,`group:${t}`,{$label:e}),i.replace(`/plugins/${t}`),k.value=null}A.action(`config.tree.clone`,{hidden:({config:e})=>!e.tree.path||!!e.tree.children,action:async({config:e})=>{let t=(e.tree.parent.path?e.tree.parent.children:X.value.data.slice(1)).findIndex(t=>t.path===e.tree.path),n=Math.random().toString(36).slice(2,8);s(`manager/unload`,e.tree.parent?.path??``,`${e.tree.name}:${n}`,e.tree.config,t+1),i.replace(`/plugins/${n}`)}}),A.action(`config.tree.manage`,{hidden:({config:e})=>!e.tree.path||!!e.tree.children,action:async({config:e})=>{B.value=e.tree.name}}),A.action(`config.tree.rename`,{disabled:({config:e})=>!e.tree.path,action:({config:e})=>{d.value=e.tree.label||(e.tree.name===`group`?e.tree.path:e.tree.name),T.value=e.tree}}),A.action(`config.tree.remove`,{disabled:({config:e})=>!e.tree.path||U(e.tree),action:({config:e})=>S.value=e.tree});function W(t){let n=c.packages[q(t)]?.runtime.schema;if(!n)return!0;try{return new e(n)(u.value),!0}catch{return a.error(`当前配置项不满足约束,请检查配置!`),!1}}A.action(`config.tree.save`,{shortcut:`ctrl+s`,disabled:e=>!e?.config?.tree||![`config`].includes(i.currentRoute.value?.meta?.activity.id),action:async({config:{tree:e}})=>{let{disabled:t,path:n}=e;if(!(!t&&!W(e.name))){if(!n)return s(`manager/app-reload`,u.value);try{await G(e,t?`unload`:`reload`),a.success(t?`配置已保存。`:`配置已重载。`)}catch{a.error(`操作失败,请检查日志!`)}}}}),A.action(`config.tree.toggle`,{disabled:({config:e})=>!e.tree.path||U(e.tree),action:async({config:{tree:e}})=>{let{disabled:t,name:n}=e;if(!(t&&!W(e.name)))try{await G(e,t?`reload`:`unload`),a.success((n===`group`?`分组`:`插件`)+(t?`已启用。`:`已停用。`))}catch{a.error(`操作失败,请检查日志!`)}}});async function G(e,t){return await s(`manager/${t}`,e.parent?.path??``,e.id,u.value)}function J(e,t){D.value=!1,e.label=t,s(`manager/meta`,e.path,{$label:t||null})}return(e,t)=>{let n=j(`k-content`),r=j(`el-button`),i=j(`el-dialog`),a=j(`el-input`),s=j(`k-layout`);return E(),m(s,{menu:`config.tree`,"menu-data":N(K)},{header:F(()=>[h(` 根节点:全局设置 `),N(K).path?N(K).children?(E(),g(f,{key:1},[h(` 分组节点 `),v(` 分组:`+M(N(K).label||N(K).id),1)],64)):(E(),g(f,{key:2},[h(` 普通插件节点 `),v(M(N(K).label||N(K).name)+` [`+M(N(K).path)+`] `,1)],64)):(E(),g(f,{key:0},[v(`全局设置`)],64))]),left:F(()=>[y(be,{ref_key:`tree`,ref:b,modelValue:o.value,"onUpdate:modelValue":t[0]||=e=>o.value=e},null,8,[`modelValue`])]),default:F(()=>[(E(),m(n,{class:`plugin-view`,key:o.value},{default:F(()=>[N(K).path?N(K).children?(E(),m(he,{key:1,modelValue:u.value,"onUpdate:modelValue":t[2]||=e=>u.value=e,current:N(K)},null,8,[`modelValue`,`current`])):(E(),m(ge,{key:2,current:N(K),modelValue:u.value,"onUpdate:modelValue":t[3]||=e=>u.value=e},null,8,[`current`,`modelValue`])):(E(),m(de,{key:0,current:N(K),modelValue:u.value,"onUpdate:modelValue":t[1]||=e=>u.value=e},null,8,[`current`,`modelValue`]))]),_:1})),y(i,{modelValue:w.value,"onUpdate:modelValue":t[6]||=e=>w.value=e,title:`确认移除`,"destroy-on-close":``,onClosed:t[7]||=e=>S.value=null},{footer:F(()=>[y(r,{onClick:t[4]||=e=>w.value=!1},{default:F(()=>[...t[19]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`danger`,onClick:t[5]||=e=>(w.value=!1,N(Q)(S.value),b.value?.activate())},{default:F(()=>[...t[20]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[S.value?(E(),g(f,{key:0},[v(` 确定要移除`+M(S.value.children?`分组 ${S.value.label||S.value.path}`:`插件 ${S.value.label||S.value.name}`)+` 吗?此操作不可撤销! `,1)],64)):h(`v-if`,!0)]),_:1},8,[`modelValue`]),y(i,{modelValue:D.value,"onUpdate:modelValue":t[12]||=e=>D.value=e,title:`重命名`,"destroy-on-close":``,onOpen:x,onClosed:t[13]||=e=>T.value=null},{footer:F(()=>[y(r,{onClick:t[10]||=e=>D.value=!1},{default:F(()=>[...t[21]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`primary`,onClick:t[11]||=e=>J(T.value,d.value)},{default:F(()=>[...t[22]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[T.value?(E(),m(a,{key:0,ref_key:`inputEl`,ref:_,modelValue:d.value,"onUpdate:modelValue":t[8]||=e=>d.value=e,onKeydown:t[9]||=I(L(e=>J(T.value,d.value),[`stop`,`prevent`]),[`enter`])},null,8,[`modelValue`])):h(`v-if`,!0)]),_:1},8,[`modelValue`]),y(i,{"model-value":k.value!==null,"onUpdate:modelValue":t[18]||=e=>k.value=null,title:`创建分组`,"destroy-on-close":``,onOpen:x},{footer:F(()=>[y(r,{onClick:t[16]||=e=>k.value=null},{default:F(()=>[...t[23]||=[v(`取消`,-1)]]),_:1}),y(r,{type:`primary`,onClick:t[17]||=e=>H(d.value)},{default:F(()=>[...t[24]||=[v(`确定`,-1)]]),_:1})]),default:F(()=>[y(a,{ref_key:`inputEl`,ref:_,modelValue:d.value,"onUpdate:modelValue":t[14]||=e=>d.value=e,onKeydown:t[15]||=I(L(e=>H(d.value),[`stop`,`prevent`]),[`enter`])},null,8,[`modelValue`])]),_:1},8,[`model-value`])]),_:1},8,[`menu-data`])}}}),Se={class:`content`},Ce=[`onClick`],we=b({__name:`select`,setup(e){let t=u(),n=O(``),r=O(),i=S(`plugin-select-filter`,e=>!0),a=p(()=>Object.values(c.packages).filter(({name:e,shortname:t})=>e&&t.includes(n.value.toLowerCase())&&i(c.packages[e])));function l(e){let t=V.value.path,r=Math.random().toString(36).slice(2,8);V.value=null,n.value=``,s(`manager/unload`,t,`${e}:${r}`,{}),o.push(`/plugins/${r}`)}return P(V,async e=>{e&&(await C(),await r.value.focus())},{flush:`post`}),(e,i)=>{let o=j(`k-icon`),s=j(`el-input`),u=j(`k-markdown`),d=j(`el-scrollbar`),p=j(`el-dialog`);return N(c).packages?(E(),m(p,{key:0,modelValue:!!N(V),"onUpdate:modelValue":i[1]||=e=>V.value=null,class:`plugin-select`},{header:F(()=>[A(e.$slots,`title`,{packages:a.value},()=>[i[2]||=_(`span`,{class:`title`},`选择插件`,-1)]),y(s,{ref_key:`input`,ref:r,modelValue:n.value,"onUpdate:modelValue":i[0]||=e=>n.value=e},{suffix:F(()=>[y(o,{name:`search`})]),_:1},8,[`modelValue`])]),default:F(()=>[A(e.$slots,`tabs`,{packages:a.value}),_(`div`,Se,[y(d,null,{default:F(()=>[(E(!0),g(f,null,k(a.value,({name:e,shortname:n,manifest:r})=>(E(),g(`div`,{class:`package`,key:e,onClick:L(e=>l(n),[`stop`])},[_(`h3`,null,M(n),1),y(u,{inline:``,class:`desc`,source:N(t)(r.description)},null,8,[`source`])],8,Ce))),128))]),_:1})])]),_:3},8,[`modelValue`])):h(`v-if`,!0)}}}),Te={},Ee={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function De(e,t){return E(),g(`svg`,Ee,[...t[0]||=[_(`path`,{fill:`currentColor`,d:`M452 60H488C499 60 508 68.95 508 80C508 91.05 499 100 488 100H452V136C452 147 443 156 432 156C420.1 156 412 147 412 136V100H376C364.1 100 356 91.05 356 80C356 68.95 364.1 60 376 60H412V24C412 12.95 420.1 4 432 4C443 4 452 12.95 452 24V60zM280.8 133.1L490.1 222.8C503.4 228.5 512 241.5 512 256C512 270.5 503.4 283.5 490.1 289.2L280.8 378.9C272.1 382.3 264.5 384 256 384C247.5 384 239 382.3 231.2 378.9L21.9 289.2C8.614 283.5 0 270.5 0 256C0 241.5 8.614 228.5 21.9 222.8L231.2 133.1C239 129.7 247.5 128 256 128C264.5 128 272.1 129.7 280.8 133.1V133.1zM66.26 256L250.1 334.8C251.1 335.6 253.1 336 256 336C258 336 260 335.6 261.9 334.8L445.7 256L261.9 177.2C260 176.4 258 176 256 176C253.1 176 251.1 176.4 250.1 177.2L66.26 256zM59.53 350.6C66.15 359.9 65.24 372.3 57.1 380.5L250.1 462.8C251.1 463.6 253.1 464 256 464C258 464 260 463.6 261.9 462.8L452 381.3C447.4 374.4 446.5 365.2 450.5 357.3C456.5 345.4 470.9 340.6 482.7 346.5L491.8 351.1C504.2 357.3 512 369.9 512 383.7C512 398.4 503.3 411.6 489.9 417.3L280.8 506.9C272.1 510.3 264.5 512 256 512C247.5 512 239 510.3 231.2 506.9L22.81 417.6C8.971 411.7 .001 398.1 .001 383C.001 370.9 5.87 359.5 15.76 352.4L26.05 345C36.84 337.3 51.83 339.8 59.53 350.6L59.53 350.6z`},null,-1)]])}var Oe=$(Te,[[`render`,De]]),ke={},Ae={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function je(e,t){return E(),g(`svg`,Ae,[...t[0]||=[_(`path`,{fill:`currentColor`,d:`M144,0c13.3,0,24,10.8,24,24v88h-48V24C120,10.8,130.8,0,144,0z M336,0c13.3,0,24,10.8,24,24v88h-48V24
2
2
  C312,10.8,322.7,0,336,0z M408,144c13.3,0,24,10.7,24,24s-10.7,24-24,24h-8v64c0,80.2-59.9,146.6-136,158.2V512h-48v-97.8
3
3
  c-77-11.6-136-78-136-158.2v-64h-8c-13.2,0-24-10.7-24-24s10.8-24,24-24H408z M240,368c61.9,0,112-50.1,112-112v-64H128v64
4
4
  C128,317.9,178.1,368,240,368z`},null,-1),_(`path`,{fill:`currentColor`,d:`M444,416h36c11,0,20,9,20,20s-9,20-20,20h-36v36c0,11-9,20-20,20c-11.9,0-20-9-20-20v-36h-36c-11.9,0-20-9-20-20
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context, Dict, MainScope, Plugin, Schema, ScopeStatus } from "@koishi-ce/koishi";
2
2
  import { DataService } from "@koishi-ce/console";
3
- import { Loader } from "@koishi-ce/loader";
4
3
  import { PackageJson, SearchObject, SearchResult } from "@koishi-ce/registry";
4
+ import { Loader } from "@koishi-ce/loader";
5
5
  //#region src/shared/packages.d.ts
6
6
  declare module "@koishi-ce/loader" {
7
7
  interface Loader {
@@ -16,6 +16,8 @@ declare module "@koishi-ce/console" {
16
16
  declare abstract class PackageProvider extends DataService<Dict<PackageProvider.Data>> {
17
17
  cache: Dict<PackageProvider.RuntimeData>;
18
18
  debouncedRefresh: () => void;
19
+ /** 完整包名 → 引用它的首个配置键(workspace 相对路径),由 node 端 collect 填充 */
20
+ pathKeys: Dict<string>;
19
21
  ctx: Context;
20
22
  /**
21
23
  * @param ctx 应用上下文
@@ -65,6 +67,8 @@ declare namespace PackageProvider {
65
67
  interface Data extends Pick<SearchObject, "shortname" | "workspace" | "manifest" | "portable"> {
66
68
  /** 完整包名 */
67
69
  name?: string;
70
+ /** 引用本包的 koishi.yml 相对路径配置键(workspace 包专属,可多条) */
71
+ paths?: string[];
68
72
  /** 运行时信息(schema、依赖、fork 状态等) */
69
73
  runtime?: RuntimeData;
70
74
  package: Pick<PackageJson, "name" | "version" | "peerDependencies" | "peerDependenciesMeta">;
package/lib/index.mjs CHANGED
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  import { resolve } from "node:path";
3
3
  import { Context, Logger, Schema, remove } from "@koishi-ce/koishi";
4
4
  import { DataService } from "@koishi-ce/console";
5
+ import { LocalScanner, getPluginShortname } from "@koishi-ce/registry";
5
6
  import { Loader } from "@koishi-ce/loader";
6
- import { LocalScanner } from "@koishi-ce/registry";
7
7
  //#region \0rolldown/runtime.js
8
8
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
9
9
  //#endregion
@@ -23,6 +23,8 @@ const logger$2 = new Logger("config");
23
23
  var PackageProvider = class extends DataService {
24
24
  cache = {};
25
25
  debouncedRefresh;
26
+ /** 完整包名 → 引用它的首个配置键(workspace 相对路径),由 node 端 collect 填充 */
27
+ pathKeys = {};
26
28
  ctx;
27
29
  /**
28
30
  * @param ctx 应用上下文
@@ -38,8 +40,19 @@ var PackageProvider = class extends DataService {
38
40
  for (const [plugin] of reloads) this.update(plugin);
39
41
  });
40
42
  ctx.console.addListener("config/request-runtime", async (name) => {
41
- name = name.replace(/(koishi-|^@koishijs\/)plugin-/, "");
42
- this.cache[name] = await this.parseExports(name);
43
+ const candidates = [
44
+ this.pathKeys[name],
45
+ getPluginShortname(name),
46
+ name
47
+ ];
48
+ for (const key of candidates) {
49
+ if (!key) continue;
50
+ const result = await this.parseExports(key);
51
+ if (!result.failed) {
52
+ this.cache[key] = result;
53
+ break;
54
+ }
55
+ }
43
56
  this.refresh(false);
44
57
  }, { authority: 4 });
45
58
  }
@@ -79,7 +92,7 @@ var PackageProvider = class extends DataService {
79
92
  const objects = (await this.collect(forced)).slice();
80
93
  for (const object of objects) {
81
94
  object.name = object.package?.name || "";
82
- const cached = this.cache[object.shortname];
95
+ const cached = [...object.paths ?? [], object.shortname].map((key) => this.cache[key]).find(Boolean);
83
96
  if (!cached) continue;
84
97
  object.runtime = cached;
85
98
  }
@@ -444,6 +457,11 @@ var ConfigWriter = class extends DataService {
444
457
  * PackageProvider 的 Node 端实现:基于 LocalScanner 扫描本机
445
458
  * node_modules 中的插件包,并在扫描过程中顺带解析已加载插件的
446
459
  * 运行时信息写入共享 cache。
460
+ *
461
+ * 本仓库 koishi.yml 的插件键统一是相对路径(./plugins/...),而 Bun
462
+ * 不会把未被依赖的 workspace 包链入 node_modules,仅靠 LocalScanner
463
+ * 会漏掉大部分源码包——因此 collect 时额外按配置键逐个加载
464
+ * workspace 包,并写入 paths(配置键 → 包名映射,供前端解析插件名)。
447
465
  */
448
466
  const logger = new Logger("config");
449
467
  /**
@@ -470,8 +488,8 @@ var PackageScanner = class extends LocalScanner {
470
488
  try {
471
489
  const entry = __require.resolve(name);
472
490
  if (__require.cache[entry]) {
473
- name = name.replace(/(koishi-|^@koishijs\/)plugin-/, "");
474
- this.service.cache[name] = await this.service.parseExports(name);
491
+ const shortname = getPluginShortname(name);
492
+ this.service.cache[shortname] = await this.service.parseExports(shortname);
475
493
  }
476
494
  } catch (error) {
477
495
  this.onError(error, name);
@@ -479,7 +497,7 @@ var PackageScanner = class extends LocalScanner {
479
497
  return result;
480
498
  }
481
499
  };
482
- /** Node 端包提供器:collect 委托给本机扫描器。 */
500
+ /** Node 端包提供器:collect 委托给本机扫描器,并补齐 workspace 源码包。 */
483
501
  var PackageProvider$1 = class extends PackageProvider {
484
502
  scanner = new PackageScanner(this);
485
503
  /**
@@ -489,9 +507,56 @@ var PackageProvider$1 = class extends PackageProvider {
489
507
  */
490
508
  async collect(forced) {
491
509
  await this.scanner.collect(forced);
492
- return this.scanner.objects;
510
+ return this.collectWorkspacePackages();
511
+ }
512
+ /**
513
+ * 递归遍历 loader 配置的插件表(分组键的值内嵌套插件表),收集
514
+ * 其中以相对路径键引用的 workspace 源码包:按目录读取 package.json
515
+ * 生成数据条目,已存在于 node_modules 扫描结果中的包则就地合并
516
+ * paths 字段,避免同名条目重复。
517
+ */
518
+ async collectWorkspacePackages() {
519
+ const index = /* @__PURE__ */ new Map();
520
+ for (const object of this.scanner.objects) index.set(object.package.name, {
521
+ ...object,
522
+ paths: []
523
+ });
524
+ try {
525
+ await walkPlugins(this.ctx.loader?.config?.plugins, async (key) => {
526
+ const path = key.split(":", 1)[0];
527
+ if (!path?.startsWith("./")) return;
528
+ const object = await this.scanner.loadPath(resolve(this.scanner.baseDir, path));
529
+ if (!object) return;
530
+ const name = object.package.name;
531
+ const entry = index.get(name) ?? {
532
+ ...object,
533
+ paths: []
534
+ };
535
+ entry.paths ||= [];
536
+ if (!entry.paths.includes(path)) entry.paths.push(path);
537
+ index.set(name, entry);
538
+ this.pathKeys[name] ||= path;
539
+ this.cache[path] ||= await this.parseExports(path);
540
+ });
541
+ } catch (error) {
542
+ logger.warn(error);
543
+ }
544
+ return [...index.values()];
493
545
  }
494
546
  };
547
+ /**
548
+ * 递归遍历插件配置表并对每个插件键执行回调:`$` 开头的键是内部控制
549
+ * 字段,`group:` 分组键的值内嵌套下一层插件表,其余键原样传入回调。
550
+ */
551
+ async function walkPlugins(plugins, handler) {
552
+ for (const key of Object.keys(plugins ?? {})) {
553
+ if (key.startsWith("$")) continue;
554
+ if (key.split(":", 1)[0] === "group") {
555
+ const value = plugins?.[key];
556
+ await walkPlugins(value ?? {}, handler);
557
+ } else await handler(key);
558
+ }
559
+ }
495
560
  //#endregion
496
561
  //#region src/node/index.ts
497
562
  /**
@@ -510,8 +575,8 @@ function apply(ctx) {
510
575
  ctx.plugin(ServiceProvider);
511
576
  ctx.plugin(ConfigWriter);
512
577
  ctx.console.addEntry({
513
- dev: resolve(__dirname, "../../client/index.ts"),
514
- prod: resolve(__dirname, "../../dist")
578
+ dev: resolve(__dirname, "../client/index.ts"),
579
+ prod: resolve(__dirname, "../dist")
515
580
  });
516
581
  }
517
582
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@koishi-ce/plugin-config",
3
3
  "description": "Manage your bots and plugins with console",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
7
7
  "types": "lib/index.d.ts",
@@ -67,12 +67,12 @@
67
67
  "koishi": "^4.18.11"
68
68
  },
69
69
  "devDependencies": {
70
- "@koishi-ce/client": "^1.0.0",
71
- "@koishi-ce/plugin-hmr": "^1.0.0"
70
+ "@koishi-ce/client": "^1.0.1",
71
+ "@koishi-ce/plugin-hmr": "^1.0.1"
72
72
  },
73
73
  "dependencies": {
74
74
  "@koishi-ce/console": "^1.0.0",
75
- "@koishi-ce/loader": "^1.0.0",
76
- "@koishi-ce/registry": "^1.0.0"
75
+ "@koishi-ce/loader": "^1.0.1",
76
+ "@koishi-ce/registry": "^1.0.2"
77
77
  }
78
78
  }
package/src/node/index.ts CHANGED
@@ -35,8 +35,10 @@ export function apply(ctx: Context) {
35
35
  ctx.plugin(ConfigWriter);
36
36
 
37
37
  // 注册控制台前端资源:dev 模式直接指向 client 源码,prod 指向打包产物
38
+ // 路径相对 lib 产物(运行时执行形态)而非 src 源码:本文件在源码里位于
39
+ // src/node(两层),打包后位于 lib(一层),深度不一致,按 lib 计
38
40
  ctx.console.addEntry({
39
- dev: resolve(__dirname, "../../client/index.ts"),
40
- prod: resolve(__dirname, "../../dist"),
41
+ dev: resolve(__dirname, "../client/index.ts"),
42
+ prod: resolve(__dirname, "../dist"),
41
43
  });
42
44
  }
@@ -2,9 +2,15 @@
2
2
  * PackageProvider 的 Node 端实现:基于 LocalScanner 扫描本机
3
3
  * node_modules 中的插件包,并在扫描过程中顺带解析已加载插件的
4
4
  * 运行时信息写入共享 cache。
5
+ *
6
+ * 本仓库 koishi.yml 的插件键统一是相对路径(./plugins/...),而 Bun
7
+ * 不会把未被依赖的 workspace 包链入 node_modules,仅靠 LocalScanner
8
+ * 会漏掉大部分源码包——因此 collect 时额外按配置键逐个加载
9
+ * workspace 包,并写入 paths(配置键 → 包名映射,供前端解析插件名)。
5
10
  */
11
+ import { resolve } from "node:path";
6
12
  import { Logger } from "@koishi-ce/koishi";
7
- import { LocalScanner } from "@koishi-ce/registry";
13
+ import { getPluginShortname, LocalScanner } from "@koishi-ce/registry";
8
14
  import * as shared from "../shared/index.ts";
9
15
 
10
16
  const logger = new Logger("config");
@@ -14,9 +20,9 @@ const logger = new Logger("config");
14
20
  * 让"扫描到某个包"与"解析该插件的运行时信息"在同一次遍历中完成。
15
21
  */
16
22
  class PackageScanner extends LocalScanner {
17
- private service: PackageProvider;
23
+ private service: shared.PackageProvider;
18
24
 
19
- constructor(service: PackageProvider) {
25
+ constructor(service: shared.PackageProvider) {
20
26
  super(service.ctx.baseDir);
21
27
  this.service = service;
22
28
  }
@@ -38,8 +44,9 @@ class PackageScanner extends LocalScanner {
38
44
  // 因为 tsconfig-paths 可能以不同方式解析路径
39
45
  const entry = require.resolve(name);
40
46
  if (require.cache[entry]) {
41
- name = name.replace(/(koishi-|^@koishijs\/)plugin-/, "");
42
- this.service.cache[name] = await this.service.parseExports(name);
47
+ const shortname = getPluginShortname(name);
48
+ this.service.cache[shortname] =
49
+ await this.service.parseExports(shortname);
43
50
  }
44
51
  } catch (error) {
45
52
  void this.onError(error, name);
@@ -48,7 +55,7 @@ class PackageScanner extends LocalScanner {
48
55
  }
49
56
  }
50
57
 
51
- /** Node 端包提供器:collect 委托给本机扫描器。 */
58
+ /** Node 端包提供器:collect 委托给本机扫描器,并补齐 workspace 源码包。 */
52
59
  export class PackageProvider extends shared.PackageProvider {
53
60
  scanner = new PackageScanner(this);
54
61
 
@@ -59,6 +66,70 @@ export class PackageProvider extends shared.PackageProvider {
59
66
  */
60
67
  async collect(forced: boolean) {
61
68
  await this.scanner.collect(forced);
62
- return this.scanner.objects;
69
+ return this.collectWorkspacePackages();
70
+ }
71
+
72
+ /**
73
+ * 递归遍历 loader 配置的插件表(分组键的值内嵌套插件表),收集
74
+ * 其中以相对路径键引用的 workspace 源码包:按目录读取 package.json
75
+ * 生成数据条目,已存在于 node_modules 扫描结果中的包则就地合并
76
+ * paths 字段,避免同名条目重复。
77
+ */
78
+ private async collectWorkspacePackages(): Promise<
79
+ shared.PackageProvider.Data[]
80
+ > {
81
+ // 以完整包名为键索引全部条目(浅拷贝,避免跨 forced 扫描累积 paths)
82
+ const index = new Map<string, shared.PackageProvider.Data>();
83
+ for (const object of this.scanner.objects) {
84
+ index.set(object.package.name, { ...object, paths: [] });
85
+ }
86
+ try {
87
+ await walkPlugins(
88
+ this.ctx.loader?.config?.plugins as Record<string, unknown> | undefined,
89
+ async (key) => {
90
+ // 配置键形如 ./plugins/webui/config:uid,取 : 前的路径部分
91
+ const path = key.split(":", 1)[0];
92
+ if (!path?.startsWith("./")) return;
93
+ const object = await this.scanner.loadPath(
94
+ resolve(this.scanner.baseDir, path),
95
+ );
96
+ if (!object) return;
97
+ const name = object.package.name;
98
+ const entry = index.get(name) ?? { ...object, paths: [] };
99
+ entry.paths ||= [];
100
+ if (!entry.paths.includes(path)) entry.paths.push(path);
101
+ index.set(name, entry);
102
+ // 登记「包名 → 配置键」反查,供 request-runtime 按路径解析
103
+ this.pathKeys[name] ||= path;
104
+ // 顺带解析运行时信息(loader.resolve 对路径键原生支持)
105
+ this.cache[path] ||= await this.parseExports(path);
106
+ },
107
+ );
108
+ } catch (error) {
109
+ logger.warn(error);
110
+ }
111
+ return [...index.values()];
112
+ }
113
+ }
114
+
115
+ /**
116
+ * 递归遍历插件配置表并对每个插件键执行回调:`$` 开头的键是内部控制
117
+ * 字段,`group:` 分组键的值内嵌套下一层插件表,其余键原样传入回调。
118
+ */
119
+ async function walkPlugins(
120
+ plugins: Record<string, unknown> | undefined,
121
+ handler: (key: string) => Promise<void>,
122
+ ) {
123
+ for (const key of Object.keys(plugins ?? {})) {
124
+ if (key.startsWith("$")) continue;
125
+ if (key.split(":", 1)[0] === "group") {
126
+ const value = plugins?.[key];
127
+ await walkPlugins(
128
+ (value ?? {}) as Record<string, unknown> | undefined,
129
+ handler,
130
+ );
131
+ } else {
132
+ await handler(key);
133
+ }
63
134
  }
64
135
  }
@@ -26,6 +26,7 @@ import type {
26
26
  SearchObject,
27
27
  SearchResult,
28
28
  } from "@koishi-ce/registry";
29
+ import { getPluginShortname } from "@koishi-ce/registry";
29
30
 
30
31
  declare module "@koishi-ce/loader" {
31
32
  interface Loader {
@@ -47,6 +48,9 @@ export abstract class PackageProvider extends DataService<
47
48
  cache: Dict<PackageProvider.RuntimeData> = {};
48
49
  debouncedRefresh: () => void;
49
50
 
51
+ /** 完整包名 → 引用它的首个配置键(workspace 相对路径),由 node 端 collect 填充 */
52
+ pathKeys: Dict<string> = {};
53
+
50
54
  override ctx: Context;
51
55
 
52
56
  /**
@@ -68,12 +72,24 @@ export abstract class PackageProvider extends DataService<
68
72
  });
69
73
 
70
74
  // 前端按需请求:某插件缺少运行时信息时(如刚安装尚未解析),
71
- // 按短名解析其导出并立即推送
75
+ // 按其配置键(workspace 相对路径)或短名解析其导出并立即推送
72
76
  ctx.console.addListener(
73
77
  "config/request-runtime",
74
78
  async (name) => {
75
- name = name.replace(/(koishi-|^@koishijs\/)plugin-/, "");
76
- this.cache[name] = await this.parseExports(name);
79
+ // 依次尝试:路径配置键 → 短名 → 原名,首个解析成功的为准
80
+ const candidates = [
81
+ this.pathKeys[name],
82
+ getPluginShortname(name),
83
+ name,
84
+ ];
85
+ for (const key of candidates) {
86
+ if (!key) continue;
87
+ const result = await this.parseExports(key);
88
+ if (!result.failed) {
89
+ this.cache[key] = result;
90
+ break;
91
+ }
92
+ }
77
93
  this.refresh(false);
78
94
  },
79
95
  { authority: 4 },
@@ -130,7 +146,11 @@ export abstract class PackageProvider extends DataService<
130
146
  const objects = (await this.collect(forced)).slice();
131
147
  for (const object of objects) {
132
148
  object.name = object.package?.name || "";
133
- const cached = this.cache[object.shortname];
149
+ // 运行时缓存的键与写入方一致:workspace 包用配置键(./...),
150
+ // npm 包用短名;paths 为空时退回短名查找
151
+ const cached = [...(object.paths ?? []), object.shortname]
152
+ .map((key) => this.cache[key])
153
+ .find(Boolean);
134
154
  if (!cached) continue;
135
155
  object.runtime = cached;
136
156
  }
@@ -211,6 +231,8 @@ export namespace PackageProvider {
211
231
  > {
212
232
  /** 完整包名 */
213
233
  name?: string;
234
+ /** 引用本包的 koishi.yml 相对路径配置键(workspace 包专属,可多条) */
235
+ paths?: string[];
214
236
  /** 运行时信息(schema、依赖、fork 状态等) */
215
237
  runtime?: RuntimeData;
216
238
  package: Pick<
File without changes