fix: 提升打包版桌面端启动稳定性并优化插件依赖处理 (#5031)
* fix(desktop): rotate electron and backend logs * refactor(desktop): centralize log rotation defaults and debug fs errors * fix(desktop): harden rotation fs ops and buffer backend log writes * refactor(desktop): extract buffered logger and reduce sync stat calls * refactor(desktop): simplify rotation flow and harden logger config * fix(desktop): make app logging async and flush-safe * fix: harden app log path switching and debug-gated rotation errors * fix: cap buffered log chunk size during path switch * fix: avoid redundant plugin reinstall and upgrade electron * fix: stop webchat tasks cleanly and bind packaged backend to localhost * fix: unify platform shutdown and await webchat listener cleanup * fix: improve startup logs for dashboard and onebot listeners * fix: revert extra startup service logs * fix: harden plugin import recovery and webchat listener cleanup * fix: pin dashboard ci node version to 24.13.0 * fix: avoid duplicate webchat listener cleanup on terminate * refactor: clarify platform task lifecycle management * fix: continue platform shutdown when terminate fails
This commit is contained in:
13 files changed
+227
-70
No files matched your search
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'latest'
|
||||
node-version: '24.13.0'
|
||||
|
||||
- name: npm install, build
|
||||
run: |
|
||||
@@ -52,4 +52,4 @@ jobs:
|
||||
repo: astrbot-release-harbour
|
||||
body: "Automated release from commit ${{ github.sha }}"
|
||||
token: ${{ secrets.ASTRBOT_HARBOUR_TOKEN }}
|
||||
artifacts: "dashboard/dist.zip"
|
||||
artifacts: "dashboard/dist.zip"
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: '24.13.0'
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: dashboard/pnpm-lock.yaml
|
||||
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: '24.13.0'
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: |
|
||||
dashboard/pnpm-lock.yaml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import traceback
|
||||
from asyncio import Queue
|
||||
from dataclasses import dataclass
|
||||
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
@@ -12,12 +13,19 @@ from .register import platform_cls_map
|
||||
from .sources.webchat.webchat_adapter import WebChatAdapter
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlatformTasks:
|
||||
run: asyncio.Task
|
||||
wrapper: asyncio.Task
|
||||
|
||||
|
||||
class PlatformManager:
|
||||
def __init__(self, config: AstrBotConfig, event_queue: Queue) -> None:
|
||||
self.platform_insts: list[Platform] = []
|
||||
"""加载的 Platform 的实例"""
|
||||
|
||||
self._inst_map: dict[str, dict] = {}
|
||||
self._platform_tasks: dict[str, PlatformTasks] = {}
|
||||
|
||||
self.astrbot_config = config
|
||||
self.platforms_config = config["platform"]
|
||||
@@ -38,6 +46,44 @@ class PlatformManager:
|
||||
sanitized = platform_id.replace(":", "_").replace("!", "_")
|
||||
return sanitized, sanitized != platform_id
|
||||
|
||||
def _start_platform_task(self, task_name: str, inst: Platform) -> None:
|
||||
run_task = asyncio.create_task(inst.run(), name=task_name)
|
||||
wrapper_task = asyncio.create_task(
|
||||
self._task_wrapper(run_task, platform=inst),
|
||||
name=f"{task_name}_wrapper",
|
||||
)
|
||||
self._platform_tasks[inst.client_self_id] = PlatformTasks(
|
||||
run=run_task,
|
||||
wrapper=wrapper_task,
|
||||
)
|
||||
|
||||
async def _stop_platform_task(self, client_id: str) -> None:
|
||||
tasks = self._platform_tasks.pop(client_id, None)
|
||||
if not tasks:
|
||||
return
|
||||
for task in (tasks.run, tasks.wrapper):
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
await asyncio.gather(tasks.run, tasks.wrapper, return_exceptions=True)
|
||||
|
||||
async def _terminate_inst_and_tasks(self, inst: Platform) -> None:
|
||||
client_id = inst.client_self_id
|
||||
try:
|
||||
if getattr(inst, "terminate", None):
|
||||
try:
|
||||
await inst.terminate()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"终止平台适配器失败: client_id=%s, error=%s",
|
||||
client_id,
|
||||
e,
|
||||
)
|
||||
logger.error(traceback.format_exc())
|
||||
finally:
|
||||
await self._stop_platform_task(client_id)
|
||||
|
||||
async def initialize(self) -> None:
|
||||
"""初始化所有平台适配器"""
|
||||
for platform in self.platforms_config:
|
||||
@@ -51,12 +97,7 @@ class PlatformManager:
|
||||
# 网页聊天
|
||||
webchat_inst = WebChatAdapter({}, self.settings, self.event_queue)
|
||||
self.platform_insts.append(webchat_inst)
|
||||
asyncio.create_task(
|
||||
self._task_wrapper(
|
||||
asyncio.create_task(webchat_inst.run(), name="webchat"),
|
||||
platform=webchat_inst,
|
||||
),
|
||||
)
|
||||
self._start_platform_task("webchat", webchat_inst)
|
||||
|
||||
async def load_platform(self, platform_config: dict) -> None:
|
||||
"""实例化一个平台"""
|
||||
@@ -154,15 +195,9 @@ class PlatformManager:
|
||||
"client_id": inst.client_self_id,
|
||||
}
|
||||
self.platform_insts.append(inst)
|
||||
|
||||
asyncio.create_task(
|
||||
self._task_wrapper(
|
||||
asyncio.create_task(
|
||||
inst.run(),
|
||||
name=f"platform_{platform_config['type']}_{platform_config['id']}",
|
||||
),
|
||||
platform=inst,
|
||||
),
|
||||
self._start_platform_task(
|
||||
f"platform_{platform_config['type']}_{platform_config['id']}",
|
||||
inst,
|
||||
)
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnPlatformLoadedEvent,
|
||||
@@ -230,13 +265,25 @@ class PlatformManager:
|
||||
except Exception:
|
||||
logger.warning(f"可能未完全移除 {platform_id} 平台适配器")
|
||||
|
||||
if getattr(inst, "terminate", None):
|
||||
await inst.terminate()
|
||||
await self._terminate_inst_and_tasks(inst)
|
||||
|
||||
async def terminate(self) -> None:
|
||||
for inst in self.platform_insts:
|
||||
if getattr(inst, "terminate", None):
|
||||
await inst.terminate()
|
||||
terminated_client_ids: set[str] = set()
|
||||
for platform_id in list(self._inst_map.keys()):
|
||||
info = self._inst_map.get(platform_id)
|
||||
if info:
|
||||
terminated_client_ids.add(info["client_id"])
|
||||
await self.terminate_platform(platform_id)
|
||||
|
||||
for inst in list(self.platform_insts):
|
||||
client_id = inst.client_self_id
|
||||
if client_id in terminated_client_ids:
|
||||
continue
|
||||
await self._terminate_inst_and_tasks(inst)
|
||||
|
||||
self.platform_insts.clear()
|
||||
self._inst_map.clear()
|
||||
self._platform_tasks.clear()
|
||||
|
||||
def get_insts(self):
|
||||
return self.platform_insts
|
||||
|
||||
@@ -26,14 +26,23 @@ from .webchat_queue_mgr import WebChatQueueMgr, webchat_queue_mgr
|
||||
|
||||
|
||||
class QueueListener:
|
||||
def __init__(self, webchat_queue_mgr: WebChatQueueMgr, callback: Callable) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
webchat_queue_mgr: WebChatQueueMgr,
|
||||
callback: Callable,
|
||||
stop_event: asyncio.Event,
|
||||
) -> None:
|
||||
self.webchat_queue_mgr = webchat_queue_mgr
|
||||
self.callback = callback
|
||||
self.stop_event = stop_event
|
||||
|
||||
async def run(self) -> None:
|
||||
"""Register callback and keep adapter task alive."""
|
||||
self.webchat_queue_mgr.set_listener(self.callback)
|
||||
await asyncio.Event().wait()
|
||||
try:
|
||||
await self.stop_event.wait()
|
||||
finally:
|
||||
await self.webchat_queue_mgr.clear_listener()
|
||||
|
||||
|
||||
@register_platform_adapter("webchat", "webchat")
|
||||
@@ -56,6 +65,8 @@ class WebChatAdapter(Platform):
|
||||
id="webchat",
|
||||
support_proactive_message=False,
|
||||
)
|
||||
self._shutdown_event = asyncio.Event()
|
||||
self._webchat_queue_mgr = webchat_queue_mgr
|
||||
|
||||
async def send_by_session(
|
||||
self,
|
||||
@@ -184,7 +195,7 @@ class WebChatAdapter(Platform):
|
||||
abm = await self.convert_message(data)
|
||||
await self.handle_msg(abm)
|
||||
|
||||
bot = QueueListener(webchat_queue_mgr, callback)
|
||||
bot = QueueListener(self._webchat_queue_mgr, callback, self._shutdown_event)
|
||||
return bot.run()
|
||||
|
||||
def meta(self) -> PlatformMetadata:
|
||||
@@ -209,5 +220,4 @@ class WebChatAdapter(Platform):
|
||||
self.commit_event(message_event)
|
||||
|
||||
async def terminate(self) -> None:
|
||||
# Do nothing
|
||||
pass
|
||||
self._shutdown_event.set()
|
||||
@@ -87,6 +87,19 @@ class WebChatQueueMgr:
|
||||
for conversation_id in list(self.queues.keys()):
|
||||
self._start_listener_if_needed(conversation_id)
|
||||
|
||||
async def clear_listener(self) -> None:
|
||||
self._listener_callback = None
|
||||
for close_event in list(self._queue_close_events.values()):
|
||||
close_event.set()
|
||||
self._queue_close_events.clear()
|
||||
|
||||
listener_tasks = list(self._listener_tasks.values())
|
||||
for task in listener_tasks:
|
||||
task.cancel()
|
||||
if listener_tasks:
|
||||
await asyncio.gather(*listener_tasks, return_exceptions=True)
|
||||
self._listener_tasks.clear()
|
||||
|
||||
def _start_listener_if_needed(self, conversation_id: str):
|
||||
if self._listener_callback is None:
|
||||
return
|
||||
|
||||
@@ -193,6 +193,37 @@ class PluginManager:
|
||||
logger.error(f"更新插件 {p} 的依赖失败。Code: {e!s}")
|
||||
return True
|
||||
|
||||
async def _import_plugin_with_dependency_recovery(
|
||||
self,
|
||||
path: str,
|
||||
module_str: str,
|
||||
root_dir_name: str,
|
||||
requirements_path: str,
|
||||
) -> ModuleType:
|
||||
try:
|
||||
return __import__(path, fromlist=[module_str])
|
||||
except (ModuleNotFoundError, ImportError) as import_exc:
|
||||
if os.path.exists(requirements_path):
|
||||
try:
|
||||
logger.info(
|
||||
f"插件 {root_dir_name} 导入失败,尝试从已安装依赖恢复: {import_exc!s}"
|
||||
)
|
||||
pip_installer.prefer_installed_dependencies(
|
||||
requirements_path=requirements_path
|
||||
)
|
||||
module = __import__(path, fromlist=[module_str])
|
||||
logger.info(
|
||||
f"插件 {root_dir_name} 已从 site-packages 恢复依赖,跳过重新安装。"
|
||||
)
|
||||
return module
|
||||
except Exception as recover_exc:
|
||||
logger.info(
|
||||
f"插件 {root_dir_name} 已安装依赖恢复失败,将重新安装依赖: {recover_exc!s}"
|
||||
)
|
||||
|
||||
await self._check_plugin_dept_update(target_plugin=root_dir_name)
|
||||
return __import__(path, fromlist=[module_str])
|
||||
|
||||
@staticmethod
|
||||
def _load_plugin_metadata(plugin_path: str, plugin_obj=None) -> StarMetadata | None:
|
||||
"""先寻找 metadata.yaml 文件,如果不存在,则使用插件对象的 info() 函数获取元数据。
|
||||
@@ -386,6 +417,12 @@ class PluginManager:
|
||||
"reserved",
|
||||
False,
|
||||
) # 是否是保留插件。目前在 astrbot/builtin_stars 目录下的都是保留插件。保留插件不可以卸载。
|
||||
plugin_dir_path = (
|
||||
os.path.join(self.plugin_store_path, root_dir_name)
|
||||
if not reserved
|
||||
else os.path.join(self.reserved_plugin_path, root_dir_name)
|
||||
)
|
||||
requirements_path = os.path.join(plugin_dir_path, "requirements.txt")
|
||||
|
||||
path = "data.plugins." if not reserved else "astrbot.builtin_stars."
|
||||
path += root_dir_name + "." + module_str
|
||||
@@ -400,11 +437,12 @@ class PluginManager:
|
||||
|
||||
# 尝试导入模块
|
||||
try:
|
||||
module = __import__(path, fromlist=[module_str])
|
||||
except (ModuleNotFoundError, ImportError):
|
||||
# 尝试安装依赖
|
||||
await self._check_plugin_dept_update(target_plugin=root_dir_name)
|
||||
module = __import__(path, fromlist=[module_str])
|
||||
module = await self._import_plugin_with_dependency_recovery(
|
||||
path=path,
|
||||
module_str=module_str,
|
||||
root_dir_name=root_dir_name,
|
||||
requirements_path=requirements_path,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error(f"插件 {root_dir_name} 导入失败。原因:{e!s}")
|
||||
@@ -412,11 +450,6 @@ class PluginManager:
|
||||
|
||||
# 检查 _conf_schema.json
|
||||
plugin_config = None
|
||||
plugin_dir_path = (
|
||||
os.path.join(self.plugin_store_path, root_dir_name)
|
||||
if not reserved
|
||||
else os.path.join(self.reserved_plugin_path, root_dir_name)
|
||||
)
|
||||
plugin_schema_path = os.path.join(
|
||||
plugin_dir_path,
|
||||
self.conf_schema_fname,
|
||||
|
||||
@@ -580,6 +580,26 @@ class PipInstaller:
|
||||
)
|
||||
importlib.invalidate_caches()
|
||||
|
||||
def prefer_installed_dependencies(self, requirements_path: str) -> None:
|
||||
"""优先使用已安装在插件 site-packages 中的依赖,不执行安装。"""
|
||||
if not is_packaged_electron_runtime():
|
||||
return
|
||||
|
||||
target_site_packages = get_astrbot_site_packages_path()
|
||||
if not os.path.isdir(target_site_packages):
|
||||
return
|
||||
|
||||
requested_requirements = _extract_requirement_names(requirements_path)
|
||||
if not requested_requirements:
|
||||
return
|
||||
|
||||
_prepend_sys_path(target_site_packages)
|
||||
_ensure_plugin_dependencies_preferred(
|
||||
target_site_packages,
|
||||
requested_requirements,
|
||||
)
|
||||
importlib.invalidate_caches()
|
||||
|
||||
async def _run_pip_in_process(self, args: list[str]) -> int:
|
||||
pip_main = _get_pip_main()
|
||||
_patch_distlib_finder_for_frozen_runtime()
|
||||
|
||||
@@ -201,11 +201,16 @@ class AstrBotDashboard:
|
||||
|
||||
def run(self):
|
||||
ip_addr = []
|
||||
if p := os.environ.get("DASHBOARD_PORT"):
|
||||
port = p
|
||||
else:
|
||||
port = self.core_lifecycle.astrbot_config["dashboard"].get("port", 6185)
|
||||
host = self.core_lifecycle.astrbot_config["dashboard"].get("host", "0.0.0.0")
|
||||
port = (
|
||||
os.environ.get("DASHBOARD_PORT")
|
||||
or os.environ.get("ASTRBOT_DASHBOARD_PORT")
|
||||
or self.core_lifecycle.astrbot_config["dashboard"].get("port", 6185)
|
||||
)
|
||||
host = (
|
||||
os.environ.get("DASHBOARD_HOST")
|
||||
or os.environ.get("ASTRBOT_DASHBOARD_HOST")
|
||||
or self.core_lifecycle.astrbot_config["dashboard"].get("host", "0.0.0.0")
|
||||
)
|
||||
enable = self.core_lifecycle.astrbot_config["dashboard"].get("enable", True)
|
||||
|
||||
if not enable:
|
||||
@@ -213,7 +218,6 @@ class AstrBotDashboard:
|
||||
return None
|
||||
|
||||
logger.info(f"正在启动 WebUI, 监听地址: http://{host}:{port}")
|
||||
|
||||
if host == "0.0.0.0":
|
||||
logger.info(
|
||||
"提示: WebUI 将监听所有网络接口,请注意安全。(可在 data/cmd_config.json 中配置 dashboard.host 以修改 host)",
|
||||
|
||||
@@ -8,6 +8,7 @@ const { BufferedRotatingLogger } = require('./buffered-rotating-logger');
|
||||
const {
|
||||
delay,
|
||||
ensureDir,
|
||||
formatLogTimestamp,
|
||||
normalizeUrl,
|
||||
parseLogBackupCount,
|
||||
parseLogMaxBytes,
|
||||
@@ -391,6 +392,18 @@ class BackendManager {
|
||||
};
|
||||
if (this.app.isPackaged) {
|
||||
env.ASTRBOT_ELECTRON_CLIENT = '1';
|
||||
const hasExplicitDashboardHost = Boolean(
|
||||
process.env.DASHBOARD_HOST || process.env.ASTRBOT_DASHBOARD_HOST,
|
||||
);
|
||||
const hasExplicitDashboardPort = Boolean(
|
||||
process.env.DASHBOARD_PORT || process.env.ASTRBOT_DASHBOARD_PORT,
|
||||
);
|
||||
if (!hasExplicitDashboardHost) {
|
||||
env.DASHBOARD_HOST = '127.0.0.1';
|
||||
}
|
||||
if (!hasExplicitDashboardPort) {
|
||||
env.DASHBOARD_PORT = '6185';
|
||||
}
|
||||
}
|
||||
if (backendConfig.webuiDir) {
|
||||
env.ASTRBOT_WEBUI_DIR = backendConfig.webuiDir;
|
||||
@@ -431,7 +444,7 @@ class BackendManager {
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(' ');
|
||||
this.backendLogger.log(
|
||||
`[${new Date().toISOString()}] [Electron] Start backend ${launchLine}\n`,
|
||||
`[${formatLogTimestamp()}] [Electron] Start backend ${launchLine}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -439,7 +452,7 @@ class BackendManager {
|
||||
this.backendLastExitReason =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
this.backendLogger.log(
|
||||
`[${new Date().toISOString()}] [Electron] Backend spawn error: ${
|
||||
`[${formatLogTimestamp()}] [Electron] Backend spawn error: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}\n`,
|
||||
);
|
||||
|
||||
@@ -81,11 +81,30 @@ function waitForProcessExit(child, timeoutMs = 5000) {
|
||||
});
|
||||
}
|
||||
|
||||
function formatLogTimestamp(date = new Date()) {
|
||||
const year = date.getFullYear();
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0');
|
||||
const day = `${date.getDate()}`.padStart(2, '0');
|
||||
const hour = `${date.getHours()}`.padStart(2, '0');
|
||||
const minute = `${date.getMinutes()}`.padStart(2, '0');
|
||||
const second = `${date.getSeconds()}`.padStart(2, '0');
|
||||
const millisecond = `${date.getMilliseconds()}`.padStart(3, '0');
|
||||
|
||||
const offsetMinutes = -date.getTimezoneOffset();
|
||||
const offsetSign = offsetMinutes >= 0 ? '+' : '-';
|
||||
const absOffsetMinutes = Math.abs(offsetMinutes);
|
||||
const offsetHour = `${Math.floor(absOffsetMinutes / 60)}`.padStart(2, '0');
|
||||
const offsetMinute = `${absOffsetMinutes % 60}`.padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}.${millisecond} ${offsetSign}${offsetHour}${offsetMinute}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
LOG_ROTATION_DEFAULT_BACKUP_COUNT,
|
||||
LOG_ROTATION_DEFAULT_MAX_MB,
|
||||
delay,
|
||||
ensureDir,
|
||||
formatLogTimestamp,
|
||||
isIgnorableFsError,
|
||||
isLogRotationDebugEnabled,
|
||||
normalizeUrl,
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
const path = require('path');
|
||||
const { RotatingLogWriter } = require('./rotating-log-writer');
|
||||
const { parseLogBackupCount, parseLogMaxBytes } = require('./common');
|
||||
const {
|
||||
formatLogTimestamp,
|
||||
parseLogBackupCount,
|
||||
parseLogMaxBytes,
|
||||
} = require('./common');
|
||||
|
||||
function createElectronLogger({ app, getRootDir }) {
|
||||
const electronLogMaxBytes = parseLogMaxBytes(
|
||||
@@ -28,7 +32,7 @@ function createElectronLogger({ app, getRootDir }) {
|
||||
|
||||
function logElectron(message) {
|
||||
const logPath = getElectronLogPath();
|
||||
const line = `[${new Date().toISOString()}] ${message}\n`;
|
||||
const line = `[${formatLogTimestamp()}] ${message}\n`;
|
||||
void writer.setLogPath(logPath);
|
||||
void writer.append(line);
|
||||
}
|
||||
@@ -47,4 +51,3 @@ function createElectronLogger({ app, getRootDir }) {
|
||||
module.exports = {
|
||||
createElectronLogger,
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"dist": "pnpm run sync:version && electron-builder"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^30.0.0",
|
||||
"electron": "^40.3.0",
|
||||
"electron-builder": "^24.13.0"
|
||||
},
|
||||
"build": {
|
||||
|
||||
Generated
+14
-19
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
electron:
|
||||
specifier: ^30.0.0
|
||||
version: 30.5.1
|
||||
specifier: ^40.3.0
|
||||
version: 40.3.0
|
||||
electron-builder:
|
||||
specifier: ^24.13.0
|
||||
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||
@@ -92,8 +92,8 @@ packages:
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
'@types/node@20.19.33':
|
||||
resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==}
|
||||
'@types/node@24.10.13':
|
||||
resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==}
|
||||
|
||||
'@types/node@25.2.2':
|
||||
resolution: {integrity: sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==}
|
||||
@@ -397,8 +397,8 @@ packages:
|
||||
electron-publish@24.13.1:
|
||||
resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==}
|
||||
|
||||
electron@30.5.1:
|
||||
resolution: {integrity: sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==}
|
||||
electron@40.3.0:
|
||||
resolution: {integrity: sha512-ZaDkTZpNHr863tyZHieoqbaiLI0e3RVCXoEC5y1Ld70/Q5H1mPV9d5TK0h1dWtaSFVOW0w8iDvtdLwAXtasXpg==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
hasBin: true
|
||||
|
||||
@@ -992,9 +992,6 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
@@ -1158,7 +1155,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/http-cache-semantics': 4.2.0
|
||||
'@types/keyv': 3.1.4
|
||||
'@types/node': 20.19.33
|
||||
'@types/node': 25.2.2
|
||||
'@types/responselike': 1.0.3
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
@@ -1173,13 +1170,13 @@ snapshots:
|
||||
|
||||
'@types/keyv@3.1.4':
|
||||
dependencies:
|
||||
'@types/node': 20.19.33
|
||||
'@types/node': 25.2.2
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/node@20.19.33':
|
||||
'@types/node@24.10.13':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/node@25.2.2':
|
||||
dependencies:
|
||||
@@ -1193,14 +1190,14 @@ snapshots:
|
||||
|
||||
'@types/responselike@1.0.3':
|
||||
dependencies:
|
||||
'@types/node': 20.19.33
|
||||
'@types/node': 25.2.2
|
||||
|
||||
'@types/verror@1.10.11':
|
||||
optional: true
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 20.19.33
|
||||
'@types/node': 25.2.2
|
||||
optional: true
|
||||
|
||||
'@xmldom/xmldom@0.8.11': {}
|
||||
@@ -1597,10 +1594,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron@30.5.1:
|
||||
electron@40.3.0:
|
||||
dependencies:
|
||||
'@electron/get': 2.0.3
|
||||
'@types/node': 20.19.33
|
||||
'@types/node': 24.10.13
|
||||
extract-zip: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -2211,8 +2208,6 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
universalify@0.1.2: {}
|
||||
|
||||
Reference in new issue
Block a user