@zhin.js/cli 1.0.13 → 1.0.15

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/cli",
3
- "version": "1.0.13",
4
- "description": "Zhin机器人框架CLI工具",
3
+ "version": "1.0.15",
4
+ "description": "Command-line interface for Zhin.js bot framework",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "bin": {
@@ -14,18 +14,18 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "commander": "^11.1.0",
18
- "chalk": "^5.3.0",
19
- "ora": "^7.0.1",
20
- "fs-extra": "^11.1.1",
21
- "cross-spawn": "^7.0.3",
22
- "dotenv": "^16.3.1",
23
- "glob": "^10.3.10",
24
- "inquirer": "^9.2.12",
25
- "@zhin.js/logger": "0.1.3"
17
+ "commander": "^14.0.2",
18
+ "chalk": "^5.6.2",
19
+ "ora": "^8.2.0",
20
+ "fs-extra": "^11.3.2",
21
+ "cross-spawn": "^7.0.6",
22
+ "dotenv": "^16.4.7",
23
+ "glob": "^11.0.0",
24
+ "inquirer": "^12.10.0",
25
+ "@zhin.js/logger": "0.1.4"
26
26
  },
27
27
  "peerDependencies": {
28
- "zhin.js": "1.0.19"
28
+ "zhin.js": "1.0.20"
29
29
  },
30
30
  "peerDependenciesMeta": {
31
31
  "zhin.js": {
@@ -35,9 +35,10 @@
35
35
  "devDependencies": {
36
36
  "@types/fs-extra": "^11.0.4",
37
37
  "@types/cross-spawn": "^6.0.6",
38
- "@types/node": "^20.0.0",
38
+ "@types/node": "^24.10.0",
39
39
  "@types/inquirer": "^9.0.7",
40
- "typescript": "^5.3.0"
40
+ "typescript": "^5.9.3",
41
+ "zhin.js": "1.0.20"
41
42
  },
42
43
  "repository": {
43
44
  "type": "git",
@@ -48,6 +49,13 @@
48
49
  "access": "public",
49
50
  "registry": "https://registry.npmjs.org"
50
51
  },
52
+ "keywords": [
53
+ "zhin",
54
+ "bot",
55
+ "cli",
56
+ "command-line",
57
+ "tool"
58
+ ],
51
59
  "scripts": {
52
60
  "build": "tsc",
53
61
  "clean": "rm -rf lib"
@@ -8,6 +8,7 @@ import { execSync } from 'node:child_process';
8
8
  interface NewPluginOptions {
9
9
  skipInstall?: boolean;
10
10
  isOfficial?: boolean;
11
+ type?: 'normal' | 'service' | 'adapter';
11
12
  }
12
13
 
13
14
  export const newCommand = new Command('new')
@@ -15,6 +16,7 @@ export const newCommand = new Command('new')
15
16
  .argument('[plugin-name]', '插件名称(如: my-plugin)')
16
17
  .option('--is-official', '是否为官方插件', false)
17
18
  .option('--skip-install', '跳过依赖安装', false)
19
+ .option('--type <type>', '插件类型 (normal|service|adapter)', 'normal')
18
20
  .action(async (pluginName: string, options: NewPluginOptions) => {
19
21
  try {
20
22
  let name = pluginName;
@@ -48,7 +50,25 @@ export const newCommand = new Command('new')
48
50
  process.exit(1);
49
51
  }
50
52
 
51
- logger.info(`正在创建插件包 ${name}...`);
53
+ // 询问插件类型(如果未指定)
54
+ if (!options.type) {
55
+ const { type } = await inquirer.prompt([
56
+ {
57
+ type: 'list',
58
+ name: 'type',
59
+ message: '请选择插件类型:',
60
+ choices: [
61
+ { name: '普通插件 (Normal)', value: 'normal' },
62
+ { name: '服务 (Service)', value: 'service' },
63
+ { name: '适配器 (Adapter)', value: 'adapter' }
64
+ ],
65
+ default: 'normal'
66
+ }
67
+ ]);
68
+ options.type = type;
69
+ }
70
+
71
+ logger.info(`正在创建${options.type === 'service' ? '服务' : options.type === 'adapter' ? '适配器' : '插件'}包 ${name}...`);
52
72
 
53
73
  // 创建插件包结构
54
74
  await createPluginPackage(pluginDir, name, options);
@@ -70,7 +90,13 @@ export const newCommand = new Command('new')
70
90
  logger.log(` pnpm publish`);
71
91
 
72
92
  } catch (error) {
73
- logger.error(`创建插件失败: ${error}`);
93
+ const errorMessage = error instanceof Error ? error.message : String(error);
94
+ const errorStack = error instanceof Error ? error.stack : undefined;
95
+
96
+ logger.error(`创建插件失败: ${errorMessage}`);
97
+ if (errorStack && process.env.DEBUG) {
98
+ logger.error(errorStack);
99
+ }
74
100
  process.exit(1);
75
101
  }
76
102
  });
@@ -85,6 +111,7 @@ async function createPluginPackage(pluginDir: string, pluginName: string, option
85
111
  await fs.ensureDir(path.join(pluginDir, 'client'));
86
112
  await fs.ensureDir(path.join(pluginDir, 'lib'));
87
113
  await fs.ensureDir(path.join(pluginDir, 'dist'));
114
+ await fs.ensureDir(path.join(pluginDir, 'tests'));
88
115
 
89
116
  // 创建 package.json
90
117
  const packageJson = {
@@ -117,6 +144,9 @@ async function createPluginPackage(pluginDir: string, pluginName: string, option
117
144
  'build:client': 'zhin-console build',
118
145
  dev: 'tsc --watch',
119
146
  clean: 'rm -rf lib dist',
147
+ test: 'vitest run',
148
+ 'test:watch': 'vitest',
149
+ 'test:coverage': 'vitest run --coverage',
120
150
  prepublishOnly: 'pnpm build'
121
151
  },
122
152
  keywords: [
@@ -141,7 +171,9 @@ async function createPluginPackage(pluginDir: string, pluginName: string, option
141
171
  "@zhin.js/client":"workspace:*",
142
172
  'lucide-react': 'latest',
143
173
  'radix-ui': 'latest',
144
- '@radix-ui/themes': 'latest'
174
+ '@radix-ui/themes': 'latest',
175
+ 'vitest': 'latest',
176
+ '@vitest/coverage-v8': 'latest'
145
177
  }
146
178
  };
147
179
 
@@ -337,6 +369,9 @@ dist/
337
369
 
338
370
  await fs.writeFile(path.join(pluginDir, '.gitignore'), gitignoreContent);
339
371
 
372
+ // 生成测试文件
373
+ await generateTestFiles(pluginDir, pluginName, capitalizedName, options);
374
+
340
375
  // 安装依赖
341
376
  if (!options.skipInstall) {
342
377
  logger.info('正在安装依赖...');
@@ -352,6 +387,516 @@ dist/
352
387
  }
353
388
  }
354
389
 
390
+ /**
391
+ * 生成测试文件
392
+ */
393
+ async function generateTestFiles(
394
+ pluginDir: string,
395
+ pluginName: string,
396
+ capitalizedName: string,
397
+ options: NewPluginOptions
398
+ ) {
399
+ const testsDir = path.join(pluginDir, 'tests');
400
+ const pluginType = options.type || 'plugin';
401
+
402
+ // 根据插件类型生成不同的测试文件
403
+ if (pluginType === 'service') {
404
+ await generateServiceTest(testsDir, pluginName, capitalizedName);
405
+ } else if (pluginType === 'adapter') {
406
+ await generateAdapterTest(testsDir, pluginName, capitalizedName);
407
+ } else {
408
+ await generatePluginTest(testsDir, pluginName, capitalizedName);
409
+ }
410
+
411
+ logger.success('✓ 测试文件已生成');
412
+ }
413
+
414
+ /**
415
+ * 生成普通插件测试
416
+ */
417
+ async function generatePluginTest(testsDir: string, pluginName: string, capitalizedName: string) {
418
+ const testContent = `import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
419
+ import { Plugin } from '@zhin.js/core'
420
+
421
+ describe('${capitalizedName} Plugin', () => {
422
+ let plugin: Plugin
423
+ let rootPlugin: Plugin
424
+
425
+ beforeEach(async () => {
426
+ rootPlugin = new Plugin('/test/root-plugin.ts')
427
+ plugin = new Plugin('/plugins/${pluginName}/src/index.ts', rootPlugin)
428
+ })
429
+
430
+ afterEach(async () => {
431
+ if (plugin && plugin.started) {
432
+ await plugin.stop()
433
+ }
434
+ })
435
+
436
+ describe('Plugin Instance', () => {
437
+ it('should create plugin instance', () => {
438
+ expect(plugin).toBeDefined()
439
+ expect(plugin).toBeInstanceOf(Plugin)
440
+ })
441
+
442
+ it('should have correct name', () => {
443
+ expect(plugin.name).toBe('${pluginName}')
444
+ })
445
+
446
+ it('should have parent plugin', () => {
447
+ expect(plugin.parent).toBe(rootPlugin)
448
+ })
449
+
450
+ it('should have logger', () => {
451
+ expect(plugin.logger).toBeDefined()
452
+ expect(typeof plugin.logger.info).toBe('function')
453
+ })
454
+ })
455
+
456
+ describe('Plugin Lifecycle', () => {
457
+ it('should start successfully', async () => {
458
+ await expect(plugin.start()).resolves.not.toThrow()
459
+ expect(plugin.started).toBe(true)
460
+ })
461
+
462
+ it('should stop successfully', async () => {
463
+ await plugin.start()
464
+ await expect(plugin.stop()).resolves.not.toThrow()
465
+ expect(plugin.started).toBe(false)
466
+ })
467
+
468
+ it('should emit mounted event on start', async () => {
469
+ const mountedSpy = vi.fn()
470
+ plugin.on('mounted', mountedSpy)
471
+
472
+ await plugin.start()
473
+
474
+ expect(mountedSpy).toHaveBeenCalled()
475
+ })
476
+
477
+ it('should emit dispose event on stop', async () => {
478
+ const disposeSpy = vi.fn()
479
+ plugin.on('dispose', disposeSpy)
480
+
481
+ await plugin.start()
482
+ await plugin.stop()
483
+
484
+ expect(disposeSpy).toHaveBeenCalled()
485
+ })
486
+ })
487
+
488
+ describe('Plugin Features', () => {
489
+ it('should register middleware', () => {
490
+ plugin.addMiddleware(async (event, next) => {
491
+ return next()
492
+ })
493
+
494
+ expect(plugin.middlewares.length).toBeGreaterThan(0)
495
+ })
496
+
497
+ it('should execute middleware', async () => {
498
+ const executionOrder: number[] = []
499
+
500
+ plugin.addMiddleware(async (event, next) => {
501
+ executionOrder.push(1)
502
+ await next()
503
+ })
504
+
505
+ plugin.addMiddleware(async (event, next) => {
506
+ executionOrder.push(2)
507
+ await next()
508
+ })
509
+
510
+ const mockEvent = {
511
+ $adapter: 'test',
512
+ $bot: 'test-bot',
513
+ $content: [],
514
+ $raw: 'test'
515
+ } as any
516
+
517
+ await plugin.middleware(mockEvent, async () => {})
518
+
519
+ expect(executionOrder).toEqual([1, 2])
520
+ })
521
+ })
522
+
523
+ describe('Custom Tests', () => {
524
+ // 在这里添加你的自定义测试
525
+ it('should pass custom test', () => {
526
+ expect(true).toBe(true)
527
+ })
528
+ })
529
+ })
530
+ `;
531
+
532
+ await fs.writeFile(path.join(testsDir, 'index.test.ts'), testContent);
533
+ }
534
+
535
+ /**
536
+ * 生成服务测试
537
+ */
538
+ async function generateServiceTest(testsDir: string, pluginName: string, capitalizedName: string) {
539
+ const testContent = `import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
540
+ import { Plugin } from '@zhin.js/core'
541
+
542
+ describe('${capitalizedName} Service', () => {
543
+ let plugin: Plugin
544
+ let service: any
545
+
546
+ beforeEach(async () => {
547
+ plugin = new Plugin('/test/service-plugin.ts')
548
+ // TODO: 初始化你的服务实例
549
+ // service = await createYourService(plugin)
550
+ })
551
+
552
+ afterEach(() => {
553
+ if (plugin && typeof (plugin as any).stop === 'function') {
554
+ (plugin as any).stop()
555
+ }
556
+ })
557
+
558
+ describe('Service Instance', () => {
559
+ it('should create service instance', () => {
560
+ // TODO: 取消注释并实现
561
+ // expect(service).toBeDefined()
562
+ // expect(service).not.toBeNull()
563
+ expect(true).toBe(true)
564
+ })
565
+
566
+ it('should have correct type', () => {
567
+ // TODO: 取消注释并实现
568
+ // expect(typeof service).toBe('object')
569
+ expect(true).toBe(true)
570
+ })
571
+ })
572
+
573
+ describe('Service Methods', () => {
574
+ it('should have required methods', () => {
575
+ // TODO: 添加你的服务方法测试
576
+ // expect(service).toHaveProperty('methodName')
577
+ // expect(typeof service.methodName).toBe('function')
578
+ expect(true).toBe(true)
579
+ })
580
+
581
+ it('should execute methods correctly', async () => {
582
+ // TODO: 测试方法执行
583
+ // const result = await service.methodName()
584
+ // expect(result).toBeDefined()
585
+ expect(true).toBe(true)
586
+ })
587
+ })
588
+
589
+ describe('Service Lifecycle', () => {
590
+ it('should handle initialization', async () => {
591
+ // TODO: 测试初始化
592
+ expect(true).toBe(true)
593
+ })
594
+
595
+ it('should handle cleanup on dispose', async () => {
596
+ // TODO: 测试清理逻辑
597
+ expect(true).toBe(true)
598
+ })
599
+ })
600
+
601
+ describe('Service Dependencies', () => {
602
+ it('should inject required dependencies', () => {
603
+ // TODO: 测试依赖注入
604
+ // plugin.provide({ name: 'dep', value: mockDep })
605
+ // const dep = plugin.inject('dep')
606
+ // expect(dep).toBeDefined()
607
+ expect(true).toBe(true)
608
+ })
609
+ })
610
+
611
+ describe('Custom Tests', () => {
612
+ // 在这里添加你的自定义测试
613
+ it('should pass custom test', () => {
614
+ expect(true).toBe(true)
615
+ })
616
+ })
617
+ })
618
+ `;
619
+
620
+ await fs.writeFile(path.join(testsDir, 'index.test.ts'), testContent);
621
+ }
622
+
623
+ /**
624
+ * 生成适配器测试
625
+ */
626
+ async function generateAdapterTest(testsDir: string, pluginName: string, capitalizedName: string) {
627
+ const testContent = `import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
628
+ import { Plugin, Adapter, Bot } from '@zhin.js/core'
629
+ import { EventEmitter } from 'events'
630
+
631
+ // TODO: 导入你的适配器和 Bot 类
632
+ // import { ${capitalizedName}Adapter, ${capitalizedName}Bot } from '../src/index'
633
+
634
+ // Mock Bot 类(用于测试)
635
+ class Mock${capitalizedName}Bot extends EventEmitter implements Bot {
636
+ adapter: string
637
+ unique: string
638
+ self_id: string
639
+ quote_self: boolean
640
+ forward_length: number
641
+ $connected: boolean = false
642
+
643
+ constructor(adapter: any, config: any) {
644
+ super()
645
+ this.adapter = '${pluginName}'
646
+ this.unique = config.name || 'mock-bot'
647
+ this.self_id = config.self_id || 'mock-bot-id'
648
+ this.quote_self = config.quote_self ?? true
649
+ this.forward_length = config.forward_length ?? 3
650
+ }
651
+
652
+ async connect() {
653
+ this.$connected = true
654
+ this.emit('online')
655
+ return true
656
+ }
657
+
658
+ async disconnect() {
659
+ this.$connected = false
660
+ this.emit('offline')
661
+ return true
662
+ }
663
+
664
+ async sendMessage(channel_id: string, content: any) {
665
+ return 'mock-message-id'
666
+ }
667
+
668
+ async recallMessage(message_id: string) {
669
+ return true
670
+ }
671
+ }
672
+
673
+ // Mock Adapter 类(用于测试)
674
+ class Mock${capitalizedName}Adapter extends Adapter<any, any> {
675
+ constructor(plugin: Plugin, name: string, config: any[]) {
676
+ super(plugin, name)
677
+ config.forEach(cfg => {
678
+ const bot = this.createBot(cfg)
679
+ this.bots.set(bot.unique, bot)
680
+ })
681
+ }
682
+
683
+ createBot(config: any): Bot {
684
+ return new Mock${capitalizedName}Bot(this, config)
685
+ }
686
+ }
687
+
688
+ describe('${capitalizedName} Adapter', () => {
689
+ let plugin: Plugin
690
+ let adapter: Mock${capitalizedName}Adapter
691
+
692
+ beforeEach(() => {
693
+ plugin = new Plugin('/test/adapter-plugin.ts')
694
+ adapter = new Mock${capitalizedName}Adapter(plugin, '${pluginName}', [
695
+ { name: 'test-bot', token: 'test-token' }
696
+ ])
697
+ })
698
+
699
+ afterEach(async () => {
700
+ if (adapter) {
701
+ await adapter.stop()
702
+ }
703
+ })
704
+
705
+ describe('Adapter Instance', () => {
706
+ it('should create adapter instance', () => {
707
+ expect(adapter).toBeDefined()
708
+ expect(adapter).toBeInstanceOf(Adapter)
709
+ })
710
+
711
+ it('should have correct name', () => {
712
+ expect(adapter.name).toBe('${pluginName}')
713
+ })
714
+
715
+ it('should have plugin reference', () => {
716
+ expect(adapter.plugin).toBe(plugin)
717
+ })
718
+
719
+ it('should have logger', () => {
720
+ expect(adapter.logger).toBeDefined()
721
+ expect(typeof adapter.logger.info).toBe('function')
722
+ })
723
+
724
+ it('should initialize with bots', () => {
725
+ expect(adapter.bots).toBeInstanceOf(Map)
726
+ expect(adapter.bots.size).toBeGreaterThan(0)
727
+ })
728
+ })
729
+
730
+ describe('Bot Management', () => {
731
+ it('should create bots from config', () => {
732
+ expect(adapter.bots.size).toBe(1)
733
+ const bot = adapter.bots.values().next().value
734
+ expect(bot).toBeDefined()
735
+ expect(bot.adapter).toBe('${pluginName}')
736
+ })
737
+
738
+ it('should have createBot method', () => {
739
+ expect(typeof adapter.createBot).toBe('function')
740
+ })
741
+
742
+ it('should create bot with correct properties', () => {
743
+ const bot = adapter.bots.values().next().value
744
+ expect(bot.unique).toBeDefined()
745
+ expect(bot.self_id).toBeDefined()
746
+ })
747
+ })
748
+
749
+ describe('Adapter Lifecycle', () => {
750
+ it('should have start method', () => {
751
+ expect(typeof adapter.start).toBe('function')
752
+ })
753
+
754
+ it('should have stop method', () => {
755
+ expect(typeof adapter.stop).toBe('function')
756
+ })
757
+
758
+ it('should start successfully', async () => {
759
+ await expect(adapter.start()).resolves.not.toThrow()
760
+ })
761
+
762
+ it('should stop successfully', async () => {
763
+ await adapter.start()
764
+ await expect(adapter.stop()).resolves.not.toThrow()
765
+ })
766
+
767
+ it('should add to plugin adapters on start', async () => {
768
+ await adapter.start()
769
+ expect(plugin.adapters).toContain(adapter)
770
+ })
771
+
772
+ it('should remove from plugin adapters on stop', async () => {
773
+ await adapter.start()
774
+ await adapter.stop()
775
+ expect(plugin.adapters).not.toContain(adapter)
776
+ })
777
+
778
+ it('should clear bots on stop', async () => {
779
+ await adapter.start()
780
+ await adapter.stop()
781
+ expect(adapter.bots.size).toBe(0)
782
+ })
783
+ })
784
+
785
+ describe('Event Handling', () => {
786
+ it('should listen to call.recallMessage event', () => {
787
+ const listeners = adapter.listeners('call.recallMessage')
788
+ expect(listeners.length).toBeGreaterThan(0)
789
+ })
790
+
791
+ it('should listen to call.sendMessage event', () => {
792
+ const listeners = adapter.listeners('call.sendMessage')
793
+ expect(listeners.length).toBeGreaterThan(0)
794
+ })
795
+
796
+ it('should listen to message.receive event', () => {
797
+ const listeners = adapter.listeners('message.receive')
798
+ expect(listeners.length).toBeGreaterThan(0)
799
+ })
800
+
801
+ it('should remove all listeners on stop', async () => {
802
+ await adapter.start()
803
+ await adapter.stop()
804
+
805
+ expect(adapter.listenerCount('call.recallMessage')).toBe(0)
806
+ expect(adapter.listenerCount('call.sendMessage')).toBe(0)
807
+ expect(adapter.listenerCount('message.receive')).toBe(0)
808
+ })
809
+ })
810
+
811
+ describe('Message Sending', () => {
812
+ it('should handle sendMessage event', async () => {
813
+ const bot = adapter.bots.values().next().value
814
+ const sendSpy = vi.spyOn(bot, 'sendMessage')
815
+
816
+ await adapter.emit('call.sendMessage', bot.unique, 'test-channel', 'test message')
817
+
818
+ expect(sendSpy).toHaveBeenCalled()
819
+ })
820
+
821
+ it('should throw error when bot not found for sending', async () => {
822
+ await expect(
823
+ adapter.emit('call.sendMessage', 'non-existent-bot', 'channel', 'message')
824
+ ).rejects.toThrow()
825
+ })
826
+ })
827
+
828
+ describe('Message Receiving', () => {
829
+ it('should process received messages through middleware', async () => {
830
+ const bot = adapter.bots.values().next().value
831
+
832
+ const middlewareSpy = vi.fn()
833
+ plugin.addMiddleware(async (event, next) => {
834
+ middlewareSpy(event)
835
+ return next()
836
+ })
837
+
838
+ const mockMessage = {
839
+ adapter: '${pluginName}',
840
+ bot: bot.unique,
841
+ channel: { id: 'test-channel', type: 'text' },
842
+ sender: { id: 'test-user' },
843
+ content: 'test message',
844
+ timestamp: Date.now()
845
+ }
846
+
847
+ await adapter.emit('message.receive', mockMessage)
848
+
849
+ expect(middlewareSpy).toHaveBeenCalled()
850
+ })
851
+ })
852
+
853
+ describe('Bot Methods', () => {
854
+ let bot: Mock${capitalizedName}Bot
855
+
856
+ beforeEach(() => {
857
+ bot = adapter.bots.values().next().value as Mock${capitalizedName}Bot
858
+ })
859
+
860
+ it('should have connect method', () => {
861
+ expect(typeof bot.connect).toBe('function')
862
+ })
863
+
864
+ it('should have disconnect method', () => {
865
+ expect(typeof bot.disconnect).toBe('function')
866
+ })
867
+
868
+ it('should have sendMessage method', () => {
869
+ expect(typeof bot.sendMessage).toBe('function')
870
+ })
871
+
872
+ it('should have recallMessage method', () => {
873
+ expect(typeof bot.recallMessage).toBe('function')
874
+ })
875
+
876
+ it('should connect successfully', async () => {
877
+ await bot.connect()
878
+ expect(bot.$connected).toBe(true)
879
+ })
880
+
881
+ it('should disconnect successfully', async () => {
882
+ await bot.connect()
883
+ await bot.disconnect()
884
+ expect(bot.$connected).toBe(false)
885
+ })
886
+ })
887
+
888
+ describe('Custom Tests', () => {
889
+ // 在这里添加你的自定义测试
890
+ it('should pass custom test', () => {
891
+ expect(true).toBe(true)
892
+ })
893
+ })
894
+ })
895
+ `;
896
+
897
+ await fs.writeFile(path.join(testsDir, 'index.test.ts'), testContent);
898
+ }
899
+
355
900
  async function addPluginToApp(pluginName: string, isOfficial?: boolean) {
356
901
  try {
357
902
  const rootPackageJsonPath = path.resolve(process.cwd(), 'package.json');
@@ -389,7 +934,8 @@ async function addPluginToApp(pluginName: string, isOfficial?: boolean) {
389
934
  } catch (error) {
390
935
  logger.warn('⚠ 依赖更新失败,请手动执行 pnpm install');
391
936
  }
392
- } catch (error) {
393
- logger.warn(`⚠ 添加到 package.json 失败: ${error}`);
394
- }
937
+ } catch (error) {
938
+ const errorMessage = error instanceof Error ? error.message : String(error);
939
+ logger.warn(`⚠ 添加到 package.json 失败: ${errorMessage}`);
940
+ }
395
941
  }