ErisPulse 2.1.14.dev1__py3-none-any.whl → 2.1.15__py3-none-any.whl
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.
- ErisPulse/Core/__init__.py +4 -2
- ErisPulse/Core/adapter.py +16 -13
- ErisPulse/Core/config.py +3 -97
- ErisPulse/Core/env.py +8 -535
- ErisPulse/Core/erispulse_config.py +105 -0
- ErisPulse/Core/exceptions.py +50 -78
- ErisPulse/Core/logger.py +79 -2
- ErisPulse/Core/mods.py +22 -18
- ErisPulse/Core/router.py +6 -1
- ErisPulse/Core/storage.py +547 -0
- ErisPulse/__init__.py +40 -22
- ErisPulse/__main__.py +991 -71
- {erispulse-2.1.14.dev1.dist-info → erispulse-2.1.15.dist-info}/METADATA +40 -19
- erispulse-2.1.15.dist-info/RECORD +17 -0
- {erispulse-2.1.14.dev1.dist-info → erispulse-2.1.15.dist-info}/licenses/LICENSE +7 -1
- erispulse-2.1.14.dev1.dist-info/RECORD +0 -15
- {erispulse-2.1.14.dev1.dist-info → erispulse-2.1.15.dist-info}/WHEEL +0 -0
- {erispulse-2.1.14.dev1.dist-info → erispulse-2.1.15.dist-info}/entry_points.txt +0 -0
ErisPulse/__main__.py
CHANGED
|
@@ -95,7 +95,7 @@ class PackageManager:
|
|
|
95
95
|
"""
|
|
96
96
|
REMOTE_SOURCES = [
|
|
97
97
|
"https://erisdev.com/packages.json",
|
|
98
|
-
"https://raw.githubusercontent.com/ErisPulse/ErisPulse
|
|
98
|
+
"https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/packages.json"
|
|
99
99
|
]
|
|
100
100
|
|
|
101
101
|
CACHE_EXPIRY = 3600 # 1小时缓存
|
|
@@ -256,13 +256,76 @@ class PackageManager:
|
|
|
256
256
|
except Exception:
|
|
257
257
|
return False
|
|
258
258
|
|
|
259
|
-
def
|
|
259
|
+
def _normalize_name(self, name: str) -> str:
|
|
260
260
|
"""
|
|
261
|
-
|
|
261
|
+
标准化包名,统一转为小写以实现大小写不敏感比较
|
|
262
|
+
|
|
263
|
+
:param name: 原始名称
|
|
264
|
+
:return: 标准化后的名称
|
|
265
|
+
"""
|
|
266
|
+
return name.lower().strip()
|
|
267
|
+
|
|
268
|
+
async def _find_package_by_alias(self, alias: str) -> Optional[str]:
|
|
269
|
+
"""
|
|
270
|
+
通过别名查找实际包名(大小写不敏感)
|
|
271
|
+
|
|
272
|
+
:param alias: 包别名
|
|
273
|
+
:return: 实际包名,未找到返回None
|
|
274
|
+
"""
|
|
275
|
+
normalized_alias = self._normalize_name(alias)
|
|
276
|
+
remote_packages = await self.get_remote_packages()
|
|
277
|
+
|
|
278
|
+
# 检查模块
|
|
279
|
+
for name, info in remote_packages["modules"].items():
|
|
280
|
+
if self._normalize_name(name) == normalized_alias:
|
|
281
|
+
return info["package"]
|
|
282
|
+
|
|
283
|
+
# 检查适配器
|
|
284
|
+
for name, info in remote_packages["adapters"].items():
|
|
285
|
+
if self._normalize_name(name) == normalized_alias:
|
|
286
|
+
return info["package"]
|
|
287
|
+
|
|
288
|
+
# 检查CLI扩展
|
|
289
|
+
for name, info in remote_packages.get("cli_extensions", {}).items():
|
|
290
|
+
if self._normalize_name(name) == normalized_alias:
|
|
291
|
+
return info["package"]
|
|
292
|
+
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
def _find_installed_package_by_name(self, name: str) -> Optional[str]:
|
|
296
|
+
"""
|
|
297
|
+
在已安装包中查找实际包名(大小写不敏感)
|
|
298
|
+
|
|
299
|
+
:param name: 包名或别名
|
|
300
|
+
:return: 实际包名,未找到返回None
|
|
301
|
+
"""
|
|
302
|
+
normalized_name = self._normalize_name(name)
|
|
303
|
+
installed = self.get_installed_packages()
|
|
304
|
+
|
|
305
|
+
# 在已安装的模块中查找
|
|
306
|
+
for module_info in installed["modules"].values():
|
|
307
|
+
if self._normalize_name(module_info["package"]) == normalized_name:
|
|
308
|
+
return module_info["package"]
|
|
309
|
+
|
|
310
|
+
# 在已安装的适配器中查找
|
|
311
|
+
for adapter_info in installed["adapters"].values():
|
|
312
|
+
if self._normalize_name(adapter_info["package"]) == normalized_name:
|
|
313
|
+
return adapter_info["package"]
|
|
314
|
+
|
|
315
|
+
# 在已安装的CLI扩展中查找
|
|
316
|
+
for cli_info in installed["cli_extensions"].values():
|
|
317
|
+
if self._normalize_name(cli_info["package"]) == normalized_name:
|
|
318
|
+
return cli_info["package"]
|
|
319
|
+
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
def _run_pip_command_with_output(self, args: List[str], description: str) -> Tuple[bool, str, str]:
|
|
323
|
+
"""
|
|
324
|
+
执行pip命令并捕获输出
|
|
262
325
|
|
|
263
326
|
:param args: pip命令参数列表
|
|
264
327
|
:param description: 进度条描述
|
|
265
|
-
:return:
|
|
328
|
+
:return: (是否成功, 标准输出, 标准错误)
|
|
266
329
|
"""
|
|
267
330
|
with Progress(
|
|
268
331
|
TextColumn(f"[progress.description]{description}"),
|
|
@@ -276,61 +339,266 @@ class PackageManager:
|
|
|
276
339
|
[sys.executable, "-m", "pip"] + args,
|
|
277
340
|
stdout=subprocess.PIPE,
|
|
278
341
|
stderr=subprocess.PIPE,
|
|
279
|
-
universal_newlines=True
|
|
342
|
+
universal_newlines=True,
|
|
343
|
+
bufsize=1 # 行缓冲
|
|
280
344
|
)
|
|
281
345
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
346
|
+
stdout_lines = []
|
|
347
|
+
stderr_lines = []
|
|
348
|
+
|
|
349
|
+
# 使用超时机制避免永久阻塞
|
|
350
|
+
import threading
|
|
351
|
+
import queue
|
|
352
|
+
|
|
353
|
+
def read_output(pipe, lines_list):
|
|
354
|
+
try:
|
|
355
|
+
for line in iter(pipe.readline, ''):
|
|
356
|
+
lines_list.append(line)
|
|
357
|
+
progress.update(task, advance=5) # 每行增加进度
|
|
358
|
+
pipe.close()
|
|
359
|
+
except Exception:
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
stdout_thread = threading.Thread(target=read_output, args=(process.stdout, stdout_lines))
|
|
363
|
+
stderr_thread = threading.Thread(target=read_output, args=(process.stderr, stderr_lines))
|
|
364
|
+
|
|
365
|
+
stdout_thread.start()
|
|
366
|
+
stderr_thread.start()
|
|
367
|
+
|
|
368
|
+
# 等待进程结束,最多等待5分钟
|
|
369
|
+
try:
|
|
370
|
+
process.wait(timeout=300)
|
|
371
|
+
except subprocess.TimeoutExpired:
|
|
372
|
+
process.kill()
|
|
373
|
+
process.wait()
|
|
374
|
+
console.print("[warning]命令执行超时,已强制终止[/]")
|
|
375
|
+
return False, "", "命令执行超时"
|
|
376
|
+
|
|
377
|
+
stdout_thread.join(timeout=10)
|
|
378
|
+
stderr_thread.join(timeout=10)
|
|
379
|
+
|
|
380
|
+
stdout = ''.join(stdout_lines)
|
|
381
|
+
stderr = ''.join(stderr_lines)
|
|
382
|
+
|
|
383
|
+
return process.returncode == 0, stdout, stderr
|
|
290
384
|
except subprocess.CalledProcessError as e:
|
|
291
385
|
console.print(f"[error]命令执行失败: {e}[/]")
|
|
292
|
-
return False
|
|
293
|
-
|
|
294
|
-
|
|
386
|
+
return False, "", str(e)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
console.print(f"[error]执行过程中发生异常: {e}[/]")
|
|
389
|
+
return False, "", str(e)
|
|
390
|
+
|
|
391
|
+
def _compare_versions(self, version1: str, version2: str) -> int:
|
|
392
|
+
"""
|
|
393
|
+
比较两个版本号
|
|
394
|
+
|
|
395
|
+
:param version1: 版本号1
|
|
396
|
+
:param version2: 版本号2
|
|
397
|
+
:return: 1 if version1 > version2, -1 if version1 < version2, 0 if equal
|
|
398
|
+
"""
|
|
399
|
+
from packaging import version as comparison
|
|
400
|
+
try:
|
|
401
|
+
v1 = comparison.parse(version1)
|
|
402
|
+
v2 = comparison.parse(version2)
|
|
403
|
+
if v1 > v2:
|
|
404
|
+
return 1
|
|
405
|
+
elif v1 < v2:
|
|
406
|
+
return -1
|
|
407
|
+
else:
|
|
408
|
+
return 0
|
|
409
|
+
except comparison.InvalidVersion:
|
|
410
|
+
# 如果无法解析,使用字符串比较作为后备
|
|
411
|
+
if version1 > version2:
|
|
412
|
+
return 1
|
|
413
|
+
elif version1 < version2:
|
|
414
|
+
return -1
|
|
415
|
+
else:
|
|
416
|
+
return 0
|
|
417
|
+
|
|
418
|
+
def _check_sdk_compatibility(self, min_sdk_version: str) -> Tuple[bool, str]:
|
|
419
|
+
"""
|
|
420
|
+
检查SDK版本兼容性
|
|
421
|
+
|
|
422
|
+
:param min_sdk_version: 所需的最小SDK版本
|
|
423
|
+
:return: (是否兼容, 当前版本信息)
|
|
424
|
+
"""
|
|
425
|
+
try:
|
|
426
|
+
from ErisPulse import __version__
|
|
427
|
+
current_version = __version__
|
|
428
|
+
except ImportError:
|
|
429
|
+
current_version = "unknown"
|
|
430
|
+
|
|
431
|
+
if current_version == "unknown":
|
|
432
|
+
return True, "无法确定当前SDK版本"
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
compatibility = self._compare_versions(current_version, min_sdk_version)
|
|
436
|
+
if compatibility >= 0:
|
|
437
|
+
return True, f"当前SDK版本 {current_version} 满足最低要求 {min_sdk_version}"
|
|
438
|
+
else:
|
|
439
|
+
return False, f"当前SDK版本 {current_version} 低于最低要求 {min_sdk_version}"
|
|
440
|
+
except Exception:
|
|
441
|
+
return True, "无法验证SDK版本兼容性"
|
|
442
|
+
|
|
443
|
+
async def _get_package_info(self, package_name: str) -> Optional[Dict[str, Any]]:
|
|
444
|
+
"""
|
|
445
|
+
获取包的详细信息(包括min_sdk_version等)
|
|
446
|
+
|
|
447
|
+
:param package_name: 包名或别名
|
|
448
|
+
:return: 包信息字典
|
|
449
|
+
"""
|
|
450
|
+
# 首先尝试通过别名查找
|
|
451
|
+
normalized_name = self._normalize_name(package_name)
|
|
452
|
+
remote_packages = await self.get_remote_packages()
|
|
453
|
+
|
|
454
|
+
# 检查模块
|
|
455
|
+
for name, info in remote_packages["modules"].items():
|
|
456
|
+
if self._normalize_name(name) == normalized_name:
|
|
457
|
+
return info
|
|
458
|
+
|
|
459
|
+
# 检查适配器
|
|
460
|
+
for name, info in remote_packages["adapters"].items():
|
|
461
|
+
if self._normalize_name(name) == normalized_name:
|
|
462
|
+
return info
|
|
463
|
+
|
|
464
|
+
# 检查CLI扩展
|
|
465
|
+
for name, info in remote_packages.get("cli_extensions", {}).items():
|
|
466
|
+
if self._normalize_name(name) == normalized_name:
|
|
467
|
+
return info
|
|
468
|
+
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
def install_package(self, package_names: List[str], upgrade: bool = False, pre: bool = False) -> bool:
|
|
295
472
|
"""
|
|
296
|
-
|
|
473
|
+
安装指定包(支持多个包)
|
|
297
474
|
|
|
298
|
-
:param
|
|
475
|
+
:param package_names: 要安装的包名或别名列表
|
|
299
476
|
:param upgrade: 是否升级已安装的包
|
|
477
|
+
:param pre: 是否包含预发布版本
|
|
300
478
|
:return: 安装是否成功
|
|
301
479
|
"""
|
|
302
|
-
|
|
303
|
-
if upgrade:
|
|
304
|
-
cmd.append("--upgrade")
|
|
305
|
-
cmd.append(package_name)
|
|
480
|
+
all_success = True
|
|
306
481
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
console.print(f"[success]包 {package_name} 安装成功[/]")
|
|
311
|
-
else:
|
|
312
|
-
console.print(f"[error]包 {package_name} 安装失败[/]")
|
|
482
|
+
for package_name in package_names:
|
|
483
|
+
# 首先尝试通过别名查找实际包名
|
|
484
|
+
actual_package = asyncio.run(self._find_package_by_alias(package_name))
|
|
313
485
|
|
|
314
|
-
|
|
486
|
+
if actual_package:
|
|
487
|
+
console.print(f"[info]找到别名映射: [bold]{package_name}[/] → [package]{actual_package}[/][/]")
|
|
488
|
+
current_package_name = actual_package
|
|
489
|
+
else:
|
|
490
|
+
console.print(f"[info]未找到别名,将直接安装: [package]{package_name}[/][/]")
|
|
491
|
+
current_package_name = package_name
|
|
492
|
+
|
|
493
|
+
# 检查SDK版本兼容性
|
|
494
|
+
package_info = asyncio.run(self._get_package_info(package_name))
|
|
495
|
+
if package_info and "min_sdk_version" in package_info:
|
|
496
|
+
is_compatible, message = self._check_sdk_compatibility(package_info["min_sdk_version"])
|
|
497
|
+
if not is_compatible:
|
|
498
|
+
console.print(Panel(
|
|
499
|
+
f"[warning]SDK版本兼容性警告[/]\n"
|
|
500
|
+
f"包 [package]{current_package_name}[/] 需要最低SDK版本 {package_info['min_sdk_version']}\n"
|
|
501
|
+
f"{message}\n\n"
|
|
502
|
+
f"继续安装可能会导致问题。",
|
|
503
|
+
title="兼容性警告",
|
|
504
|
+
border_style="warning"
|
|
505
|
+
))
|
|
506
|
+
if not Confirm.ask("是否继续安装?", default=False):
|
|
507
|
+
console.print("[info]已取消安装[/]")
|
|
508
|
+
all_success = False
|
|
509
|
+
continue
|
|
510
|
+
else:
|
|
511
|
+
console.print(f"[success]{message}[/]")
|
|
512
|
+
|
|
513
|
+
# 构建pip命令
|
|
514
|
+
cmd = ["install"]
|
|
515
|
+
if upgrade:
|
|
516
|
+
cmd.append("--upgrade")
|
|
517
|
+
if pre:
|
|
518
|
+
cmd.append("--pre")
|
|
519
|
+
cmd.append(current_package_name)
|
|
520
|
+
|
|
521
|
+
# 执行安装命令
|
|
522
|
+
success, stdout, stderr = self._run_pip_command_with_output(cmd, f"安装 {current_package_name}")
|
|
523
|
+
|
|
524
|
+
if success:
|
|
525
|
+
console.print(Panel(
|
|
526
|
+
f"[success]包 {current_package_name} 安装成功[/]\n\n"
|
|
527
|
+
f"[dim]{stdout}[/]",
|
|
528
|
+
title="安装完成",
|
|
529
|
+
border_style="success"
|
|
530
|
+
))
|
|
531
|
+
else:
|
|
532
|
+
console.print(Panel(
|
|
533
|
+
f"[error]包 {current_package_name} 安装失败[/]\n\n"
|
|
534
|
+
f"[dim]{stderr}[/]",
|
|
535
|
+
title="安装失败",
|
|
536
|
+
border_style="error"
|
|
537
|
+
))
|
|
538
|
+
all_success = False
|
|
539
|
+
|
|
540
|
+
return all_success
|
|
315
541
|
|
|
316
|
-
def uninstall_package(self,
|
|
542
|
+
def uninstall_package(self, package_names: List[str]) -> bool:
|
|
317
543
|
"""
|
|
318
|
-
|
|
544
|
+
卸载指定包(支持多个包,支持别名)
|
|
319
545
|
|
|
320
|
-
:param
|
|
546
|
+
:param package_names: 要卸载的包名或别名列表
|
|
321
547
|
:return: 卸载是否成功
|
|
322
548
|
"""
|
|
323
|
-
|
|
324
|
-
["uninstall", "-y", package_name],
|
|
325
|
-
f"卸载 {package_name}"
|
|
326
|
-
)
|
|
549
|
+
all_success = True
|
|
327
550
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
551
|
+
packages_to_uninstall = []
|
|
552
|
+
|
|
553
|
+
# 首先处理所有包名,查找实际包名
|
|
554
|
+
for package_name in package_names:
|
|
555
|
+
# 首先尝试通过别名查找实际包名
|
|
556
|
+
actual_package = asyncio.run(self._find_package_by_alias(package_name))
|
|
332
557
|
|
|
333
|
-
|
|
558
|
+
if actual_package:
|
|
559
|
+
console.print(f"[info]找到别名映射: [bold]{package_name}[/] → [package]{actual_package}[/][/]")
|
|
560
|
+
packages_to_uninstall.append(actual_package)
|
|
561
|
+
else:
|
|
562
|
+
# 如果找不到别名映射,检查是否是已安装的包
|
|
563
|
+
installed_package = self._find_installed_package_by_name(package_name)
|
|
564
|
+
if installed_package:
|
|
565
|
+
package_name = installed_package
|
|
566
|
+
console.print(f"[info]找到已安装包: [bold]{package_name}[/][/]")
|
|
567
|
+
packages_to_uninstall.append(package_name)
|
|
568
|
+
else:
|
|
569
|
+
console.print(f"[warning]未找到别名映射,将尝试直接卸载: [package]{package_name}[/][/]")
|
|
570
|
+
packages_to_uninstall.append(package_name)
|
|
571
|
+
|
|
572
|
+
# 确认卸载操作
|
|
573
|
+
package_list = "\n".join([f" - [package]{pkg}[/]" for pkg in packages_to_uninstall])
|
|
574
|
+
if not Confirm.ask(f"确认卸载以下包吗?\n{package_list}", default=False):
|
|
575
|
+
console.print("[info]操作已取消[/]")
|
|
576
|
+
return False
|
|
577
|
+
|
|
578
|
+
# 执行卸载命令
|
|
579
|
+
for package_name in packages_to_uninstall:
|
|
580
|
+
success, stdout, stderr = self._run_pip_command_with_output(
|
|
581
|
+
["uninstall", "-y", package_name],
|
|
582
|
+
f"卸载 {package_name}"
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
if success:
|
|
586
|
+
console.print(Panel(
|
|
587
|
+
f"[success]包 {package_name} 卸载成功[/]\n\n"
|
|
588
|
+
f"[dim]{stdout}[/]",
|
|
589
|
+
title="卸载完成",
|
|
590
|
+
border_style="success"
|
|
591
|
+
))
|
|
592
|
+
else:
|
|
593
|
+
console.print(Panel(
|
|
594
|
+
f"[error]包 {package_name} 卸载失败[/]\n\n"
|
|
595
|
+
f"[dim]{stderr}[/]",
|
|
596
|
+
title="卸载失败",
|
|
597
|
+
border_style="error"
|
|
598
|
+
))
|
|
599
|
+
all_success = False
|
|
600
|
+
|
|
601
|
+
return all_success
|
|
334
602
|
|
|
335
603
|
def upgrade_all(self) -> bool:
|
|
336
604
|
"""
|
|
@@ -362,7 +630,7 @@ class PackageManager:
|
|
|
362
630
|
|
|
363
631
|
results = {}
|
|
364
632
|
for pkg in sorted(all_packages):
|
|
365
|
-
results[pkg] = self.install_package(pkg, upgrade=True)
|
|
633
|
+
results[pkg] = self.install_package([pkg], upgrade=True)
|
|
366
634
|
|
|
367
635
|
failed = [pkg for pkg, success in results.items() if not success]
|
|
368
636
|
if failed:
|
|
@@ -375,6 +643,272 @@ class PackageManager:
|
|
|
375
643
|
|
|
376
644
|
return True
|
|
377
645
|
|
|
646
|
+
def upgrade_package(self, package_names: List[str], pre: bool = False) -> bool:
|
|
647
|
+
"""
|
|
648
|
+
升级指定包(支持多个包)
|
|
649
|
+
|
|
650
|
+
:param package_names: 要升级的包名或别名列表
|
|
651
|
+
:param pre: 是否包含预发布版本
|
|
652
|
+
:return: 升级是否成功
|
|
653
|
+
"""
|
|
654
|
+
all_success = True
|
|
655
|
+
|
|
656
|
+
for package_name in package_names:
|
|
657
|
+
# 首先尝试通过别名查找实际包名
|
|
658
|
+
actual_package = asyncio.run(self._find_package_by_alias(package_name))
|
|
659
|
+
|
|
660
|
+
if actual_package:
|
|
661
|
+
console.print(f"[info]找到别名映射: [bold]{package_name}[/] → [package]{actual_package}[/][/]")
|
|
662
|
+
current_package_name = actual_package
|
|
663
|
+
else:
|
|
664
|
+
current_package_name = package_name
|
|
665
|
+
|
|
666
|
+
# 检查SDK版本兼容性
|
|
667
|
+
package_info = asyncio.run(self._get_package_info(package_name))
|
|
668
|
+
if package_info and "min_sdk_version" in package_info:
|
|
669
|
+
is_compatible, message = self._check_sdk_compatibility(package_info["min_sdk_version"])
|
|
670
|
+
if not is_compatible:
|
|
671
|
+
console.print(Panel(
|
|
672
|
+
f"[warning]SDK版本兼容性警告[/]\n"
|
|
673
|
+
f"包 [package]{current_package_name}[/] 需要最低SDK版本 {package_info['min_sdk_version']}\n"
|
|
674
|
+
f"{message}\n\n"
|
|
675
|
+
f"继续升级可能会导致问题。",
|
|
676
|
+
title="兼容性警告",
|
|
677
|
+
border_style="warning"
|
|
678
|
+
))
|
|
679
|
+
if not Confirm.ask("是否继续升级?", default=False):
|
|
680
|
+
console.print("[info]已取消升级[/]")
|
|
681
|
+
all_success = False
|
|
682
|
+
continue
|
|
683
|
+
else:
|
|
684
|
+
console.print(f"[success]{message}[/]")
|
|
685
|
+
|
|
686
|
+
# 构建pip命令
|
|
687
|
+
cmd = ["install", "--upgrade"]
|
|
688
|
+
if pre:
|
|
689
|
+
cmd.append("--pre")
|
|
690
|
+
cmd.append(current_package_name)
|
|
691
|
+
|
|
692
|
+
# 执行升级命令
|
|
693
|
+
success, stdout, stderr = self._run_pip_command_with_output(cmd, f"升级 {current_package_name}")
|
|
694
|
+
|
|
695
|
+
if success:
|
|
696
|
+
console.print(Panel(
|
|
697
|
+
f"[success]包 {current_package_name} 升级成功[/]\n\n"
|
|
698
|
+
f"[dim]{stdout}[/]",
|
|
699
|
+
title="升级完成",
|
|
700
|
+
border_style="success"
|
|
701
|
+
))
|
|
702
|
+
else:
|
|
703
|
+
console.print(Panel(
|
|
704
|
+
f"[error]包 {current_package_name} 升级失败[/]\n\n"
|
|
705
|
+
f"[dim]{stderr}[/]",
|
|
706
|
+
title="升级失败",
|
|
707
|
+
border_style="error"
|
|
708
|
+
))
|
|
709
|
+
all_success = False
|
|
710
|
+
|
|
711
|
+
return all_success
|
|
712
|
+
|
|
713
|
+
def search_package(self, query: str) -> Dict[str, List[Dict[str, str]]]:
|
|
714
|
+
"""
|
|
715
|
+
搜索包(本地和远程)
|
|
716
|
+
|
|
717
|
+
:param query: 搜索关键词
|
|
718
|
+
:return: 匹配的包信息
|
|
719
|
+
"""
|
|
720
|
+
normalized_query = self._normalize_name(query)
|
|
721
|
+
results = {"installed": [], "remote": []}
|
|
722
|
+
|
|
723
|
+
# 搜索已安装的包
|
|
724
|
+
installed = self.get_installed_packages()
|
|
725
|
+
for pkg_type in ["modules", "adapters", "cli_extensions"]:
|
|
726
|
+
for name, info in installed[pkg_type].items():
|
|
727
|
+
if (normalized_query in self._normalize_name(name) or
|
|
728
|
+
normalized_query in self._normalize_name(info["package"]) or
|
|
729
|
+
normalized_query in self._normalize_name(info["summary"])):
|
|
730
|
+
results["installed"].append({
|
|
731
|
+
"type": pkg_type[:-1] if pkg_type.endswith("s") else pkg_type, # 移除复数s
|
|
732
|
+
"name": name,
|
|
733
|
+
"package": info["package"],
|
|
734
|
+
"version": info["version"],
|
|
735
|
+
"summary": info["summary"]
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
# 搜索远程包
|
|
739
|
+
remote = asyncio.run(self.get_remote_packages())
|
|
740
|
+
for pkg_type in ["modules", "adapters", "cli_extensions"]:
|
|
741
|
+
for name, info in remote[pkg_type].items():
|
|
742
|
+
if (normalized_query in self._normalize_name(name) or
|
|
743
|
+
normalized_query in self._normalize_name(info["package"]) or
|
|
744
|
+
normalized_query in self._normalize_name(info.get("description", "")) or
|
|
745
|
+
normalized_query in self._normalize_name(info.get("summary", ""))):
|
|
746
|
+
results["remote"].append({
|
|
747
|
+
"type": pkg_type[:-1] if pkg_type.endswith("s") else pkg_type, # 移除复数s
|
|
748
|
+
"name": name,
|
|
749
|
+
"package": info["package"],
|
|
750
|
+
"version": info["version"],
|
|
751
|
+
"summary": info.get("description", info.get("summary", ""))
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
return results
|
|
755
|
+
|
|
756
|
+
def get_installed_version(self) -> str:
|
|
757
|
+
"""
|
|
758
|
+
获取当前安装的ErisPulse版本
|
|
759
|
+
|
|
760
|
+
:return: 当前版本号
|
|
761
|
+
"""
|
|
762
|
+
try:
|
|
763
|
+
from ErisPulse import __version__
|
|
764
|
+
return __version__
|
|
765
|
+
except ImportError:
|
|
766
|
+
return "unknown"
|
|
767
|
+
|
|
768
|
+
async def get_pypi_versions(self) -> List[Dict[str, Any]]:
|
|
769
|
+
"""
|
|
770
|
+
从PyPI获取ErisPulse的所有可用版本
|
|
771
|
+
|
|
772
|
+
:return: 版本信息列表
|
|
773
|
+
"""
|
|
774
|
+
import aiohttp
|
|
775
|
+
from aiohttp import ClientError, ClientTimeout
|
|
776
|
+
from packaging import version as comparison
|
|
777
|
+
|
|
778
|
+
timeout = ClientTimeout(total=10)
|
|
779
|
+
url = "https://pypi.org/pypi/ErisPulse/json"
|
|
780
|
+
|
|
781
|
+
try:
|
|
782
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
783
|
+
async with session.get(url) as response:
|
|
784
|
+
if response.status == 200:
|
|
785
|
+
data = await response.json()
|
|
786
|
+
versions = []
|
|
787
|
+
for version_str, releases in data["releases"].items():
|
|
788
|
+
if releases: # 只包含有文件的版本
|
|
789
|
+
release_info = {
|
|
790
|
+
"version": version_str,
|
|
791
|
+
"uploaded": releases[0].get("upload_time_iso_8601", ""),
|
|
792
|
+
"pre_release": self._is_pre_release(version_str)
|
|
793
|
+
}
|
|
794
|
+
versions.append(release_info)
|
|
795
|
+
|
|
796
|
+
# 使用版本比较函数正确排序版本
|
|
797
|
+
versions.sort(key=lambda x: comparison.parse(x["version"]), reverse=True)
|
|
798
|
+
return versions
|
|
799
|
+
except (ClientError, asyncio.TimeoutError, json.JSONDecodeError, KeyError, Exception) as e:
|
|
800
|
+
console.print(f"[error]获取PyPI版本信息失败: {e}[/]")
|
|
801
|
+
return []
|
|
802
|
+
|
|
803
|
+
def _is_pre_release(self, version: str) -> bool:
|
|
804
|
+
"""
|
|
805
|
+
判断版本是否为预发布版本
|
|
806
|
+
|
|
807
|
+
:param version: 版本号
|
|
808
|
+
:return: 是否为预发布版本
|
|
809
|
+
"""
|
|
810
|
+
import re
|
|
811
|
+
# 检查是否包含预发布标识符 (alpha, beta, rc, dev等)
|
|
812
|
+
pre_release_pattern = re.compile(r'(a|b|rc|dev|alpha|beta)\d*', re.IGNORECASE)
|
|
813
|
+
return bool(pre_release_pattern.search(version))
|
|
814
|
+
|
|
815
|
+
def update_self(self, target_version: str = None, force: bool = False) -> bool:
|
|
816
|
+
"""
|
|
817
|
+
更新ErisPulse SDK本身
|
|
818
|
+
|
|
819
|
+
:param target_version: 目标版本号,None表示更新到最新版本
|
|
820
|
+
:param force: 是否强制更新
|
|
821
|
+
:return: 更新是否成功
|
|
822
|
+
"""
|
|
823
|
+
current_version = self.get_installed_version()
|
|
824
|
+
|
|
825
|
+
if target_version and target_version == current_version and not force:
|
|
826
|
+
console.print(f"[info]当前已是目标版本 [bold]{current_version}[/][/]")
|
|
827
|
+
return True
|
|
828
|
+
|
|
829
|
+
# 确定要安装的版本
|
|
830
|
+
package_spec = "ErisPulse"
|
|
831
|
+
if target_version:
|
|
832
|
+
package_spec += f"=={target_version}"
|
|
833
|
+
|
|
834
|
+
# 检查是否在Windows上且尝试更新自身
|
|
835
|
+
if sys.platform == "win32":
|
|
836
|
+
# 构建更新脚本
|
|
837
|
+
update_script = f"""
|
|
838
|
+
import time
|
|
839
|
+
import subprocess
|
|
840
|
+
import sys
|
|
841
|
+
import os
|
|
842
|
+
|
|
843
|
+
# 等待原进程结束
|
|
844
|
+
time.sleep(2)
|
|
845
|
+
|
|
846
|
+
# 执行更新命令
|
|
847
|
+
try:
|
|
848
|
+
result = subprocess.run([
|
|
849
|
+
sys.executable, "-m", "pip", "install", "--upgrade", "{package_spec}"
|
|
850
|
+
], capture_output=True, text=True, timeout=300)
|
|
851
|
+
|
|
852
|
+
if result.returncode == 0:
|
|
853
|
+
print("更新成功!")
|
|
854
|
+
print(result.stdout)
|
|
855
|
+
else:
|
|
856
|
+
print("更新失败:")
|
|
857
|
+
print(result.stderr)
|
|
858
|
+
except Exception as e:
|
|
859
|
+
print(f"更新过程中出错: {{e}}")
|
|
860
|
+
|
|
861
|
+
# 清理临时脚本
|
|
862
|
+
try:
|
|
863
|
+
os.remove(__file__)
|
|
864
|
+
except:
|
|
865
|
+
pass
|
|
866
|
+
"""
|
|
867
|
+
# 创建临时更新脚本
|
|
868
|
+
import tempfile
|
|
869
|
+
script_path = os.path.join(tempfile.gettempdir(), "epsdk_update.py")
|
|
870
|
+
with open(script_path, "w", encoding="utf-8") as f:
|
|
871
|
+
f.write(update_script)
|
|
872
|
+
|
|
873
|
+
# 启动更新进程并退出当前进程
|
|
874
|
+
console.print("[info]正在启动更新进程...[/]")
|
|
875
|
+
console.print("[info]请稍后重新运行CLI以使用新版本[/]")
|
|
876
|
+
|
|
877
|
+
subprocess.Popen([
|
|
878
|
+
sys.executable, script_path
|
|
879
|
+
], creationflags=subprocess.CREATE_NEW_CONSOLE)
|
|
880
|
+
|
|
881
|
+
return True
|
|
882
|
+
else:
|
|
883
|
+
# 非Windows平台
|
|
884
|
+
success, stdout, stderr = self._run_pip_command_with_output(
|
|
885
|
+
["install", "--upgrade", package_spec],
|
|
886
|
+
f"更新 ErisPulse SDK {f'到 {target_version}' if target_version else '到最新版本'}"
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
if success:
|
|
890
|
+
new_version = target_version or "最新版本"
|
|
891
|
+
console.print(Panel(
|
|
892
|
+
f"[success]ErisPulse SDK 更新成功[/]\n"
|
|
893
|
+
f" 当前版本: [bold]{current_version}[/]\n"
|
|
894
|
+
f" 更新版本: [bold]{new_version}[/]\n\n"
|
|
895
|
+
f"[dim]{stdout}[/]",
|
|
896
|
+
title="更新完成",
|
|
897
|
+
border_style="success"
|
|
898
|
+
))
|
|
899
|
+
|
|
900
|
+
if not target_version:
|
|
901
|
+
console.print("[info]请重新启动CLI以使用新版本[/]")
|
|
902
|
+
else:
|
|
903
|
+
console.print(Panel(
|
|
904
|
+
f"[error]ErisPulse SDK 更新失败[/]\n\n"
|
|
905
|
+
f"[dim]{stderr}[/]",
|
|
906
|
+
title="更新失败",
|
|
907
|
+
border_style="error"
|
|
908
|
+
))
|
|
909
|
+
|
|
910
|
+
return success
|
|
911
|
+
|
|
378
912
|
class ReloadHandler(FileSystemEventHandler):
|
|
379
913
|
"""
|
|
380
914
|
文件系统事件处理器
|
|
@@ -454,12 +988,44 @@ class ReloadHandler(FileSystemEventHandler):
|
|
|
454
988
|
|
|
455
989
|
def _handle_reload(self, event, reason: str):
|
|
456
990
|
"""
|
|
457
|
-
|
|
458
|
-
|
|
991
|
+
处理热重载逻辑
|
|
459
992
|
:param event: 文件系统事件
|
|
460
|
-
:param reason:
|
|
993
|
+
:param reason: 重载原因
|
|
461
994
|
"""
|
|
462
|
-
|
|
995
|
+
from ErisPulse.Core import adapter, logger
|
|
996
|
+
# 在重载前确保所有适配器正确停止
|
|
997
|
+
try:
|
|
998
|
+
# 检查适配器是否正在运行
|
|
999
|
+
if hasattr(adapter, '_started_instances') and adapter._started_instances:
|
|
1000
|
+
logger.info("正在停止适配器...")
|
|
1001
|
+
# 创建新的事件循环来运行异步停止操作
|
|
1002
|
+
import asyncio
|
|
1003
|
+
import threading
|
|
1004
|
+
|
|
1005
|
+
# 如果在主线程中
|
|
1006
|
+
if threading.current_thread() is threading.main_thread():
|
|
1007
|
+
try:
|
|
1008
|
+
# 尝试获取当前事件循环
|
|
1009
|
+
loop = asyncio.get_running_loop()
|
|
1010
|
+
# 在新线程中运行适配器停止
|
|
1011
|
+
stop_thread = threading.Thread(target=lambda: asyncio.run(adapter.shutdown()))
|
|
1012
|
+
stop_thread.start()
|
|
1013
|
+
stop_thread.join(timeout=10) # 最多等待10秒
|
|
1014
|
+
except RuntimeError:
|
|
1015
|
+
# 没有运行中的事件循环
|
|
1016
|
+
asyncio.run(adapter.shutdown())
|
|
1017
|
+
else:
|
|
1018
|
+
# 在非主线程中,创建新的事件循环
|
|
1019
|
+
new_loop = asyncio.new_event_loop()
|
|
1020
|
+
asyncio.set_event_loop(new_loop)
|
|
1021
|
+
new_loop.run_until_complete(adapter.shutdown())
|
|
1022
|
+
|
|
1023
|
+
logger.info("适配器已停止")
|
|
1024
|
+
except Exception as e:
|
|
1025
|
+
logger.warning(f"停止适配器时出错: {e}")
|
|
1026
|
+
|
|
1027
|
+
# 原有的重载逻辑
|
|
1028
|
+
logger.info(f"检测到文件变更 ({reason}),正在重启...")
|
|
463
1029
|
self._terminate_process()
|
|
464
1030
|
self.start_process()
|
|
465
1031
|
|
|
@@ -519,11 +1085,12 @@ class CLI:
|
|
|
519
1085
|
# 安装命令
|
|
520
1086
|
install_parser = subparsers.add_parser(
|
|
521
1087
|
'install',
|
|
522
|
-
help='
|
|
1088
|
+
help='安装模块/适配器包(支持多个,用空格分隔)'
|
|
523
1089
|
)
|
|
524
1090
|
install_parser.add_argument(
|
|
525
1091
|
'package',
|
|
526
|
-
|
|
1092
|
+
nargs='+', # 改为接受多个参数
|
|
1093
|
+
help='要安装的包名或模块/适配器简称(可指定多个)'
|
|
527
1094
|
)
|
|
528
1095
|
install_parser.add_argument(
|
|
529
1096
|
'--upgrade', '-U',
|
|
@@ -539,11 +1106,12 @@ class CLI:
|
|
|
539
1106
|
# 卸载命令
|
|
540
1107
|
uninstall_parser = subparsers.add_parser(
|
|
541
1108
|
'uninstall',
|
|
542
|
-
help='
|
|
1109
|
+
help='卸载模块/适配器包(支持多个,用空格分隔)'
|
|
543
1110
|
)
|
|
544
1111
|
uninstall_parser.add_argument(
|
|
545
1112
|
'package',
|
|
546
|
-
|
|
1113
|
+
nargs='+', # 改为接受多个参数
|
|
1114
|
+
help='要卸载的包名(可指定多个)'
|
|
547
1115
|
)
|
|
548
1116
|
|
|
549
1117
|
# 模块管理命令
|
|
@@ -613,11 +1181,11 @@ class CLI:
|
|
|
613
1181
|
# 升级命令
|
|
614
1182
|
upgrade_parser = subparsers.add_parser(
|
|
615
1183
|
'upgrade',
|
|
616
|
-
help='
|
|
1184
|
+
help='升级组件(支持多个,用空格分隔)'
|
|
617
1185
|
)
|
|
618
1186
|
upgrade_parser.add_argument(
|
|
619
1187
|
'package',
|
|
620
|
-
nargs='
|
|
1188
|
+
nargs='*', # 改为接受可选的多个参数
|
|
621
1189
|
help='要升级的包名 (可选,不指定则升级所有)'
|
|
622
1190
|
)
|
|
623
1191
|
upgrade_parser.add_argument(
|
|
@@ -625,6 +1193,52 @@ class CLI:
|
|
|
625
1193
|
action='store_true',
|
|
626
1194
|
help='跳过确认直接升级'
|
|
627
1195
|
)
|
|
1196
|
+
upgrade_parser.add_argument(
|
|
1197
|
+
'--pre',
|
|
1198
|
+
action='store_true',
|
|
1199
|
+
help='包含预发布版本'
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
# 搜索命令
|
|
1203
|
+
search_parser = subparsers.add_parser(
|
|
1204
|
+
'search',
|
|
1205
|
+
help='搜索模块/适配器包'
|
|
1206
|
+
)
|
|
1207
|
+
search_parser.add_argument(
|
|
1208
|
+
'query',
|
|
1209
|
+
help='搜索关键词'
|
|
1210
|
+
)
|
|
1211
|
+
search_parser.add_argument(
|
|
1212
|
+
'--installed', '-i',
|
|
1213
|
+
action='store_true',
|
|
1214
|
+
help='仅搜索已安装的包'
|
|
1215
|
+
)
|
|
1216
|
+
search_parser.add_argument(
|
|
1217
|
+
'--remote', '-r',
|
|
1218
|
+
action='store_true',
|
|
1219
|
+
help='仅搜索远程包'
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
# 自更新命令
|
|
1223
|
+
self_update_parser = subparsers.add_parser(
|
|
1224
|
+
'self-update',
|
|
1225
|
+
help='更新ErisPulse SDK本身'
|
|
1226
|
+
)
|
|
1227
|
+
self_update_parser.add_argument(
|
|
1228
|
+
'version',
|
|
1229
|
+
nargs='?',
|
|
1230
|
+
help='要更新到的版本号 (可选,默认为最新版本)'
|
|
1231
|
+
)
|
|
1232
|
+
self_update_parser.add_argument(
|
|
1233
|
+
'--pre',
|
|
1234
|
+
action='store_true',
|
|
1235
|
+
help='包含预发布版本'
|
|
1236
|
+
)
|
|
1237
|
+
self_update_parser.add_argument(
|
|
1238
|
+
'--force', '-f',
|
|
1239
|
+
action='store_true',
|
|
1240
|
+
help='强制更新,即使版本相同'
|
|
1241
|
+
)
|
|
628
1242
|
|
|
629
1243
|
# 运行命令
|
|
630
1244
|
run_parser = subparsers.add_parser(
|
|
@@ -899,23 +1513,133 @@ class CLI:
|
|
|
899
1513
|
|
|
900
1514
|
def _resolve_package_name(self, short_name: str) -> Optional[str]:
|
|
901
1515
|
"""
|
|
902
|
-
|
|
1516
|
+
解析简称到完整包名(大小写不敏感)
|
|
903
1517
|
|
|
904
1518
|
:param short_name: 模块/适配器简称
|
|
905
1519
|
:return: 完整包名,未找到返回None
|
|
906
1520
|
"""
|
|
1521
|
+
normalized_name = self.package_manager._normalize_name(short_name)
|
|
907
1522
|
remote_packages = asyncio.run(self.package_manager.get_remote_packages())
|
|
908
1523
|
|
|
909
1524
|
# 检查模块
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1525
|
+
for name, info in remote_packages["modules"].items():
|
|
1526
|
+
if self.package_manager._normalize_name(name) == normalized_name:
|
|
1527
|
+
return info["package"]
|
|
1528
|
+
|
|
913
1529
|
# 检查适配器
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1530
|
+
for name, info in remote_packages["adapters"].items():
|
|
1531
|
+
if self.package_manager._normalize_name(name) == normalized_name:
|
|
1532
|
+
return info["package"]
|
|
1533
|
+
|
|
917
1534
|
return None
|
|
918
1535
|
|
|
1536
|
+
def _print_search_results(self, query: str, results: Dict[str, List[Dict[str, str]]]):
|
|
1537
|
+
"""
|
|
1538
|
+
打印搜索结果
|
|
1539
|
+
|
|
1540
|
+
:param query: 搜索关键词
|
|
1541
|
+
:param results: 搜索结果
|
|
1542
|
+
"""
|
|
1543
|
+
if not results["installed"] and not results["remote"]:
|
|
1544
|
+
console.print(f"[info]未找到与 '[bold]{query}[/]' 匹配的包[/]")
|
|
1545
|
+
return
|
|
1546
|
+
|
|
1547
|
+
# 打印已安装的包
|
|
1548
|
+
if results["installed"]:
|
|
1549
|
+
table = Table(
|
|
1550
|
+
title="已安装的包",
|
|
1551
|
+
box=SIMPLE,
|
|
1552
|
+
header_style="info"
|
|
1553
|
+
)
|
|
1554
|
+
table.add_column("类型")
|
|
1555
|
+
table.add_column("名称")
|
|
1556
|
+
table.add_column("包名")
|
|
1557
|
+
table.add_column("版本")
|
|
1558
|
+
table.add_column("描述")
|
|
1559
|
+
|
|
1560
|
+
for item in results["installed"]:
|
|
1561
|
+
table.add_row(
|
|
1562
|
+
item["type"],
|
|
1563
|
+
item["name"],
|
|
1564
|
+
item["package"],
|
|
1565
|
+
item["version"],
|
|
1566
|
+
item["summary"]
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
console.print(table)
|
|
1570
|
+
|
|
1571
|
+
# 打印远程包
|
|
1572
|
+
if results["remote"]:
|
|
1573
|
+
table = Table(
|
|
1574
|
+
title="远程包",
|
|
1575
|
+
box=SIMPLE,
|
|
1576
|
+
header_style="info"
|
|
1577
|
+
)
|
|
1578
|
+
table.add_column("类型")
|
|
1579
|
+
table.add_column("名称")
|
|
1580
|
+
table.add_column("包名")
|
|
1581
|
+
table.add_column("版本")
|
|
1582
|
+
table.add_column("描述")
|
|
1583
|
+
|
|
1584
|
+
for item in results["remote"]:
|
|
1585
|
+
table.add_row(
|
|
1586
|
+
item["type"],
|
|
1587
|
+
item["name"],
|
|
1588
|
+
item["package"],
|
|
1589
|
+
item["version"],
|
|
1590
|
+
item["summary"]
|
|
1591
|
+
)
|
|
1592
|
+
|
|
1593
|
+
console.print(table)
|
|
1594
|
+
|
|
1595
|
+
def _print_version_list(self, versions: List[Dict[str, Any]], include_pre: bool = False):
|
|
1596
|
+
"""
|
|
1597
|
+
打印版本列表
|
|
1598
|
+
|
|
1599
|
+
:param versions: 版本信息列表
|
|
1600
|
+
:param include_pre: 是否包含预发布版本
|
|
1601
|
+
"""
|
|
1602
|
+
if not versions:
|
|
1603
|
+
console.print("[info]未找到可用版本[/]")
|
|
1604
|
+
return
|
|
1605
|
+
|
|
1606
|
+
table = Table(
|
|
1607
|
+
title="可用版本",
|
|
1608
|
+
box=SIMPLE,
|
|
1609
|
+
header_style="info"
|
|
1610
|
+
)
|
|
1611
|
+
table.add_column("序号")
|
|
1612
|
+
table.add_column("版本")
|
|
1613
|
+
table.add_column("类型")
|
|
1614
|
+
table.add_column("上传时间")
|
|
1615
|
+
|
|
1616
|
+
displayed = 0
|
|
1617
|
+
version_list = []
|
|
1618
|
+
for version_info in versions:
|
|
1619
|
+
# 如果不包含预发布版本,则跳过预发布版本
|
|
1620
|
+
if not include_pre and version_info["pre_release"]:
|
|
1621
|
+
continue
|
|
1622
|
+
|
|
1623
|
+
version_list.append(version_info)
|
|
1624
|
+
version_type = "[yellow]预发布[/]" if version_info["pre_release"] else "[green]稳定版[/]"
|
|
1625
|
+
table.add_row(
|
|
1626
|
+
str(displayed + 1),
|
|
1627
|
+
version_info["version"],
|
|
1628
|
+
version_type,
|
|
1629
|
+
version_info["uploaded"][:10] if version_info["uploaded"] else "未知"
|
|
1630
|
+
)
|
|
1631
|
+
displayed += 1
|
|
1632
|
+
|
|
1633
|
+
# 只显示前10个版本
|
|
1634
|
+
if displayed >= 10:
|
|
1635
|
+
break
|
|
1636
|
+
|
|
1637
|
+
if displayed == 0:
|
|
1638
|
+
console.print("[info]没有找到符合条件的版本[/]")
|
|
1639
|
+
else:
|
|
1640
|
+
console.print(table)
|
|
1641
|
+
return version_list
|
|
1642
|
+
|
|
919
1643
|
def _setup_watchdog(self, script_path: str, reload_mode: bool):
|
|
920
1644
|
"""
|
|
921
1645
|
设置文件监控
|
|
@@ -980,16 +1704,19 @@ class CLI:
|
|
|
980
1704
|
|
|
981
1705
|
try:
|
|
982
1706
|
if args.command == "install":
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1707
|
+
success = self.package_manager.install_package(
|
|
1708
|
+
args.package, # 现在是列表
|
|
1709
|
+
upgrade=args.upgrade,
|
|
1710
|
+
pre=args.pre
|
|
1711
|
+
)
|
|
1712
|
+
if not success:
|
|
1713
|
+
sys.exit(1)
|
|
989
1714
|
|
|
990
1715
|
elif args.command == "uninstall":
|
|
991
|
-
self.package_manager.uninstall_package(args.package)
|
|
992
|
-
|
|
1716
|
+
success = self.package_manager.uninstall_package(args.package) # 现在是列表
|
|
1717
|
+
if not success:
|
|
1718
|
+
sys.exit(1)
|
|
1719
|
+
|
|
993
1720
|
elif args.command == "module":
|
|
994
1721
|
from ErisPulse.Core import mods
|
|
995
1722
|
installed = self.package_manager.get_installed_packages()
|
|
@@ -1030,11 +1757,165 @@ class CLI:
|
|
|
1030
1757
|
|
|
1031
1758
|
elif args.command == "upgrade":
|
|
1032
1759
|
if args.package:
|
|
1033
|
-
self.package_manager.
|
|
1760
|
+
success = self.package_manager.upgrade_package(
|
|
1761
|
+
args.package, # 现在是列表
|
|
1762
|
+
pre=args.pre
|
|
1763
|
+
)
|
|
1764
|
+
if not success:
|
|
1765
|
+
sys.exit(1)
|
|
1034
1766
|
else:
|
|
1035
|
-
if args.force or Confirm.ask("确定要升级所有ErisPulse组件吗?"):
|
|
1036
|
-
self.package_manager.upgrade_all()
|
|
1767
|
+
if args.force or Confirm.ask("确定要升级所有ErisPulse组件吗?", default=False):
|
|
1768
|
+
success = self.package_manager.upgrade_all()
|
|
1769
|
+
if not success:
|
|
1770
|
+
sys.exit(1)
|
|
1771
|
+
|
|
1772
|
+
elif args.command == "search":
|
|
1773
|
+
results = self.package_manager.search_package(args.query)
|
|
1774
|
+
|
|
1775
|
+
# 根据选项过滤结果
|
|
1776
|
+
if args.installed:
|
|
1777
|
+
results["remote"] = []
|
|
1778
|
+
elif args.remote:
|
|
1779
|
+
results["installed"] = []
|
|
1780
|
+
|
|
1781
|
+
self._print_search_results(args.query, results)
|
|
1782
|
+
|
|
1783
|
+
elif args.command == "self-update":
|
|
1784
|
+
current_version = self.package_manager.get_installed_version()
|
|
1785
|
+
console.print(Panel(
|
|
1786
|
+
f"[title]ErisPulse SDK 自更新[/]\n"
|
|
1787
|
+
f"当前版本: [bold]{current_version}[/]",
|
|
1788
|
+
title_align="left"
|
|
1789
|
+
))
|
|
1790
|
+
|
|
1791
|
+
# 获取可用版本
|
|
1792
|
+
with console.status("[bold green]正在获取版本信息...", spinner="dots"):
|
|
1793
|
+
versions = asyncio.run(self.package_manager.get_pypi_versions())
|
|
1794
|
+
|
|
1795
|
+
if not versions:
|
|
1796
|
+
console.print("[error]无法获取版本信息[/]")
|
|
1797
|
+
sys.exit(1)
|
|
1798
|
+
|
|
1799
|
+
# 交互式选择更新选项
|
|
1800
|
+
if not args.version:
|
|
1801
|
+
# 显示最新版本
|
|
1802
|
+
stable_versions = [v for v in versions if not v["pre_release"]]
|
|
1803
|
+
pre_versions = [v for v in versions if v["pre_release"]]
|
|
1804
|
+
|
|
1805
|
+
latest_stable = stable_versions[0] if stable_versions else None
|
|
1806
|
+
latest_pre = pre_versions[0] if pre_versions and args.pre else None
|
|
1807
|
+
|
|
1808
|
+
choices = []
|
|
1809
|
+
choice_versions = {}
|
|
1810
|
+
choice_index = {}
|
|
1811
|
+
|
|
1812
|
+
if latest_stable:
|
|
1813
|
+
choice = f"最新稳定版 ({latest_stable['version']})"
|
|
1814
|
+
choices.append(choice)
|
|
1815
|
+
choice_versions[choice] = latest_stable['version']
|
|
1816
|
+
choice_index[len(choices)] = choice
|
|
1037
1817
|
|
|
1818
|
+
if args.pre and latest_pre:
|
|
1819
|
+
choice = f"最新预发布版 ({latest_pre['version']})"
|
|
1820
|
+
choices.append(choice)
|
|
1821
|
+
choice_versions[choice] = latest_pre['version']
|
|
1822
|
+
choice_index[len(choices)] = choice
|
|
1823
|
+
|
|
1824
|
+
# 添加其他选项
|
|
1825
|
+
choices.append("查看所有版本")
|
|
1826
|
+
choices.append("手动指定版本")
|
|
1827
|
+
choices.append("取消")
|
|
1828
|
+
|
|
1829
|
+
# 创建数字索引映射
|
|
1830
|
+
for i, choice in enumerate(choices, 1):
|
|
1831
|
+
choice_index[i] = choice
|
|
1832
|
+
|
|
1833
|
+
# 显示选项
|
|
1834
|
+
console.print("\n[info]请选择更新选项:[/]")
|
|
1835
|
+
for i, choice in enumerate(choices, 1):
|
|
1836
|
+
console.print(f" {i}. {choice}")
|
|
1837
|
+
|
|
1838
|
+
while True:
|
|
1839
|
+
try:
|
|
1840
|
+
selected_input = Prompt.ask(
|
|
1841
|
+
"请输入选项编号",
|
|
1842
|
+
default="1"
|
|
1843
|
+
)
|
|
1844
|
+
|
|
1845
|
+
if selected_input.isdigit():
|
|
1846
|
+
selected_index = int(selected_input)
|
|
1847
|
+
if selected_index in choice_index:
|
|
1848
|
+
selected = choice_index[selected_index]
|
|
1849
|
+
break
|
|
1850
|
+
else:
|
|
1851
|
+
console.print("[warning]请输入有效的选项编号[/]")
|
|
1852
|
+
else:
|
|
1853
|
+
# 检查是否是选项文本
|
|
1854
|
+
if selected_input in choices:
|
|
1855
|
+
selected = selected_input
|
|
1856
|
+
break
|
|
1857
|
+
else:
|
|
1858
|
+
console.print("[warning]请输入有效的选项编号或选项名称[/]")
|
|
1859
|
+
except KeyboardInterrupt:
|
|
1860
|
+
console.print("\n[info]操作已取消[/]")
|
|
1861
|
+
sys.exit(0)
|
|
1862
|
+
|
|
1863
|
+
if selected == "取消":
|
|
1864
|
+
console.print("[info]操作已取消[/]")
|
|
1865
|
+
sys.exit(0)
|
|
1866
|
+
elif selected == "手动指定版本":
|
|
1867
|
+
target_version = Prompt.ask("请输入要更新到的版本号")
|
|
1868
|
+
if not any(v['version'] == target_version for v in versions):
|
|
1869
|
+
console.print(f"[warning]版本 {target_version} 可能不存在[/]")
|
|
1870
|
+
if not Confirm.ask("是否继续?", default=False):
|
|
1871
|
+
sys.exit(0)
|
|
1872
|
+
elif selected == "查看所有版本":
|
|
1873
|
+
version_list = self._print_version_list(versions, include_pre=args.pre)
|
|
1874
|
+
if not version_list:
|
|
1875
|
+
console.print("[info]没有可用版本[/]")
|
|
1876
|
+
sys.exit(0)
|
|
1877
|
+
|
|
1878
|
+
# 显示版本选择
|
|
1879
|
+
console.print("\n[info]请选择要更新到的版本:[/]")
|
|
1880
|
+
while True:
|
|
1881
|
+
try:
|
|
1882
|
+
version_input = Prompt.ask("请输入版本序号或版本号")
|
|
1883
|
+
if version_input.isdigit():
|
|
1884
|
+
version_index = int(version_input)
|
|
1885
|
+
if 1 <= version_index <= len(version_list):
|
|
1886
|
+
target_version = version_list[version_index - 1]['version']
|
|
1887
|
+
break
|
|
1888
|
+
else:
|
|
1889
|
+
console.print("[warning]请输入有效的版本序号[/]")
|
|
1890
|
+
else:
|
|
1891
|
+
# 检查是否是有效的版本号
|
|
1892
|
+
if any(v['version'] == version_input for v in version_list):
|
|
1893
|
+
target_version = version_input
|
|
1894
|
+
break
|
|
1895
|
+
else:
|
|
1896
|
+
console.print("[warning]请输入有效的版本序号或版本号[/]")
|
|
1897
|
+
except KeyboardInterrupt:
|
|
1898
|
+
console.print("\n[info]操作已取消[/]")
|
|
1899
|
+
sys.exit(0)
|
|
1900
|
+
else:
|
|
1901
|
+
target_version = choice_versions[selected]
|
|
1902
|
+
else:
|
|
1903
|
+
target_version = args.version
|
|
1904
|
+
|
|
1905
|
+
# 确认更新
|
|
1906
|
+
if target_version == current_version and not args.force:
|
|
1907
|
+
console.print(f"[info]当前已是目标版本 [bold]{current_version}[/][/]")
|
|
1908
|
+
sys.exit(0)
|
|
1909
|
+
elif not args.force:
|
|
1910
|
+
if not Confirm.ask(f"确认将ErisPulse SDK从 [bold]{current_version}[/] 更新到 [bold]{target_version}[/] 吗?", default=False):
|
|
1911
|
+
console.print("[info]操作已取消[/]")
|
|
1912
|
+
sys.exit(0)
|
|
1913
|
+
|
|
1914
|
+
# 执行更新
|
|
1915
|
+
success = self.package_manager.update_self(target_version, args.force)
|
|
1916
|
+
if not success:
|
|
1917
|
+
sys.exit(1)
|
|
1918
|
+
|
|
1038
1919
|
elif args.command == "run":
|
|
1039
1920
|
script = args.script or "main.py"
|
|
1040
1921
|
if not os.path.exists(script):
|
|
@@ -1046,9 +1927,10 @@ class CLI:
|
|
|
1046
1927
|
|
|
1047
1928
|
try:
|
|
1048
1929
|
while True:
|
|
1049
|
-
time.sleep(
|
|
1930
|
+
time.sleep(0.5)
|
|
1050
1931
|
except KeyboardInterrupt:
|
|
1051
1932
|
console.print("\n[info]正在安全关闭...[/]")
|
|
1933
|
+
self._cleanup_adapters()
|
|
1052
1934
|
self._cleanup()
|
|
1053
1935
|
console.print("[success]已安全退出[/]")
|
|
1054
1936
|
|
|
@@ -1087,6 +1969,44 @@ class CLI:
|
|
|
1087
1969
|
console.print(traceback.format_exc())
|
|
1088
1970
|
self._cleanup()
|
|
1089
1971
|
sys.exit(1)
|
|
1972
|
+
|
|
1973
|
+
def _cleanup_adapters(self):
|
|
1974
|
+
"""
|
|
1975
|
+
清理适配器资源
|
|
1976
|
+
"""
|
|
1977
|
+
from ErisPulse import adapter, logger
|
|
1978
|
+
try:
|
|
1979
|
+
import asyncio
|
|
1980
|
+
import threading
|
|
1981
|
+
|
|
1982
|
+
# 检查是否有正在运行的适配器
|
|
1983
|
+
if (hasattr(adapter, '_started_instances') and
|
|
1984
|
+
adapter._started_instances):
|
|
1985
|
+
|
|
1986
|
+
logger.info("正在停止所有适配器...")
|
|
1987
|
+
|
|
1988
|
+
if threading.current_thread() is threading.main_thread():
|
|
1989
|
+
try:
|
|
1990
|
+
loop = asyncio.get_running_loop()
|
|
1991
|
+
if loop.is_running():
|
|
1992
|
+
# 在新线程中运行
|
|
1993
|
+
stop_thread = threading.Thread(
|
|
1994
|
+
target=lambda: asyncio.run(adapter.shutdown())
|
|
1995
|
+
)
|
|
1996
|
+
stop_thread.start()
|
|
1997
|
+
stop_thread.join(timeout=5)
|
|
1998
|
+
else:
|
|
1999
|
+
asyncio.run(adapter.shutdown())
|
|
2000
|
+
except RuntimeError:
|
|
2001
|
+
asyncio.run(adapter.shutdown())
|
|
2002
|
+
else:
|
|
2003
|
+
new_loop = asyncio.new_event_loop()
|
|
2004
|
+
asyncio.set_event_loop(new_loop)
|
|
2005
|
+
new_loop.run_until_complete(adapter.shutdown())
|
|
2006
|
+
|
|
2007
|
+
logger.info("适配器已全部停止")
|
|
2008
|
+
except Exception as e:
|
|
2009
|
+
logger.error(f"清理适配器资源时出错: {e}")
|
|
1090
2010
|
|
|
1091
2011
|
def main():
|
|
1092
2012
|
"""
|