fix(plugin): 按绑定仓库保留更新候选 (#6474)

* fix(plugin): preserve repository update candidates

* chore(ci): sync architecture ratchet baselines

* chore(ci): align canonical coverage baseline

* chore(ci): align coverage ratchet baseline
This commit is contained in:
InfinityPacer
2026-08-27 06:56:33 +08:00
committed by GitHub
parent 2553226f37
commit 627775c090
9 changed files with 152 additions and 9 deletions
+1 -1
View File
@@ -378,7 +378,7 @@ async def all_plugins(
# 本地插件仓库目录中的插件
local_repo_plugins = plugin_manager.get_local_repo_plugins()
# 在线插件
online_plugins = await plugin_manager.async_get_online_plugins(force)
online_plugins = await plugin_manager.async_get_online_plugin_candidates(force)
installed_ids = [plugin.id for plugin in installed_plugins if plugin.id]
candidate_plugins = (
plugin_manager.process_plugins_list(online_plugins + local_repo_plugins, [])
+42 -2
View File
@@ -190,9 +190,10 @@ class PluginCatalogService:
[str, Optional[str], bool],
Awaitable[list[Any]],
],
preserve_sources: bool = False,
progress_callback: Optional[ProgressCallback] = None,
) -> list[Any]:
"""异步读取多个市场和代际,并持续报告稳定进度"""
"""异步读取多个市场和代际,并按调用方需要合并或保留仓库候选"""
async def fetch(
market: str,
package_version: Optional[str],
@@ -249,7 +250,11 @@ class PluginCatalogService:
target = higher_plugins if version == "higher_version" else base_plugins
target.extend(plugins)
result = self.merge(higher_plugins, base_plugins, markets)
result = (
self.merge_by_source(higher_plugins, base_plugins, markets)
if preserve_sources
else self.merge(higher_plugins, base_plugins, markets)
)
if progress_callback:
progress_callback(value=100, text="插件市场缓存刷新完成")
return result
@@ -313,6 +318,41 @@ class PluginCatalogService:
result_by_id[plugin.id] = plugin
return list(result_by_id.values())
def merge_by_source(
self,
higher_plugins: list[Any],
base_plugins: list[Any],
markets: list[str],
) -> list[Any]:
"""每个仓库保留同一插件的最高兼容版本,供来源准入继续决策。"""
higher_keys = {
(plugin.repo_url, plugin.id, plugin.plugin_version)
for plugin in higher_plugins
}
all_plugins = list(higher_plugins)
all_plugins.extend(
plugin
for plugin in base_plugins
if (plugin.repo_url, plugin.id, plugin.plugin_version) not in higher_keys
)
def repo_order(plugin: Any) -> int:
if plugin.repo_url in markets:
return markets.index(plugin.repo_url)
return len(markets)
result_by_source: dict[tuple[Optional[str], Optional[str]], Any] = {}
for plugin in sorted(all_plugins, key=repo_order):
key = (plugin.repo_url, plugin.id)
exists = result_by_source.get(key)
if not exists or self._version_compare(
plugin.plugin_version,
">",
exists.plugin_version,
):
result_by_source[key] = plugin
return list(result_by_source.values())
def _map_plugins(
self,
online_plugins: dict[str, dict],
+17
View File
@@ -243,6 +243,23 @@ class PluginCatalogFacade:
self._logger.info(f"获取到 {len(result)} 个线上插件")
return result
async def async_online_candidates(self, force: bool = False) -> list[Plugin]:
"""读取在线目录并保留每个仓库的最高候选,供来源准入使用。"""
plugin_market = get_runtime_setting('PLUGIN_MARKET')
if not plugin_market:
return []
markets = [item for item in plugin_market.split(",") if item]
result: list[Plugin] = await self._market_catalog().async_collect(
markets=markets,
compatible_flags=self._system().compatible_flags(
get_runtime_setting('VERSION_FLAG')
),
force=force,
loader=self._async_market_loader,
preserve_sources=True,
)
return result
async def async_get_from_market(
self,
market: str,
+7
View File
@@ -1364,6 +1364,13 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
progress_callback,
)
async def async_get_online_plugin_candidates(
self,
force: bool = False,
) -> List[_SchemaPlugin]:
"""获取按仓库保留的在线插件候选,供来源准入和更新选择。"""
return await self._plugin_catalog_view.async_online_candidates(force)
async def async_get_plugins_from_market(self, market: str,
package_version: Optional[str] = None,
force: bool = False) -> Optional[List[_SchemaPlugin]]:
+2 -2
View File
@@ -26,7 +26,7 @@ class PluginSourceBindingStatus(str, _Enum):
class PluginUpdateCandidate(BaseModel): # type: ignore[misc]
"""插件市场为已安装插件发现的当前最高在线更新候选。"""
"""插件市场为已安装插件选择的当前更新候选。"""
source_type: Literal["official", "third_party"] = Field(
description="候选仓库是官方来源还是第三方来源"
@@ -93,7 +93,7 @@ class Plugin(BaseModel):
has_page: Optional[bool] = False
# 是否有新版本
has_update: Optional[bool] = False
# 当前市场选择的最高在线更新候选;没有确定候选时为空
# 当前市场选择的更新候选;绑定仓库可更新时优先返回该仓库
update_candidate: Optional[PluginUpdateCandidate] = None
# 插件仓库绑定状态;仅已安装物理插件由后端投影真实身份
source_binding_status: PluginSourceBindingStatus = PluginSourceBindingStatus.BOUND