@lobehub/lobehub 2.0.0-next.22 → 2.0.0-next.23

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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.23](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.22...v2.0.0-next.23)
6
+
7
+ <sup>Released on **2025-11-04**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix send message.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix send message, closes [#10041](https://github.com/lobehub/lobe-chat/issues/10041) [#9984](https://github.com/lobehub/lobe-chat/issues/9984) ([7cca60f](https://github.com/lobehub/lobe-chat/commit/7cca60f))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.22](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.21...v2.0.0-next.22)
6
31
 
7
32
  <sup>Released on **2025-11-04**</sup>
@@ -72,7 +72,7 @@
72
72
  "tsx": "^4.19.3",
73
73
  "typescript": "^5.7.3",
74
74
  "undici": "^7.9.0",
75
- "vite": "^7.1.12",
75
+ "vite": "^6.3.5",
76
76
  "vitest": "^3.2.4"
77
77
  },
78
78
  "pnpm": {
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix send message."
6
+ ]
7
+ },
8
+ "date": "2025-11-04",
9
+ "version": "2.0.0-next.23"
10
+ },
2
11
  {
3
12
  "children": {},
4
13
  "date": "2025-11-04",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.22",
3
+ "version": "2.0.0-next.23",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -10,7 +10,7 @@
10
10
  "@lobechat/model-runtime": "workspace:*",
11
11
  "@lobechat/prompts": "workspace:*",
12
12
  "dotenv": "^17.2.3",
13
- "ora": "^8.2.0"
13
+ "ora": "^9.0.0"
14
14
  },
15
15
  "devDependencies": {
16
16
  "tsx": "^4.19.2"
@@ -130,10 +130,10 @@ export const CreateMessageParamsSchema = z
130
130
  files: z.array(z.string()).optional(),
131
131
  fromModel: z.string().optional(),
132
132
  fromProvider: z.string().optional(),
133
- groupId: z.string().optional(),
133
+ groupId: z.string().nullable().optional(),
134
134
  targetId: z.string().nullable().optional(),
135
135
  threadId: z.string().nullable().optional(),
136
- topicId: z.string().optional(),
136
+ topicId: z.string().nullable().optional(),
137
137
  traceId: z.string().optional(),
138
138
  // Allow additional fields from UIChatMessage (many can be null)
139
139
  agentId: z.string().optional(),
@@ -182,7 +182,7 @@ export const CreateNewMessageParamsSchema = z
182
182
  parentId: z.string().optional(),
183
183
  groupId: z.string().nullable().optional(),
184
184
  // Context
185
- topicId: z.string().optional(),
185
+ topicId: z.string().nullable().optional(),
186
186
  threadId: z.string().nullable().optional(),
187
187
  targetId: z.string().nullable().optional(),
188
188
  // Model info
package/renovate.json CHANGED
@@ -15,28 +15,64 @@
15
15
  "workarounds:all"
16
16
  ],
17
17
  "ignoreDeps": [],
18
- "labels": [
19
- "dependencies"
20
- ],
18
+ "labels": ["dependencies"],
21
19
  "packageRules": [
20
+ // 1) Pinned deps: isolate (OK to use separate* here because there's no matchUpdateTypes)
21
+ {
22
+ "description": "Isolate PRs for pinned deps (exact x.y.z)",
23
+ "matchManagers": ["npm", "pnpm", "yarn", "bun"],
24
+ "matchDepTypes": [
25
+ "dependencies",
26
+ "devDependencies",
27
+ "optionalDependencies",
28
+ "peerDependencies"
29
+ ],
30
+ "matchCurrentValue": "^\\d+\\.\\d+\\.\\d+([+-][0-9A-Za-z.-]+)?$",
31
+ "groupName": null,
32
+ "separateMinorPatch": true,
33
+ "separateMajorMinor": true,
34
+ "separateMultipleMinor": true,
35
+ "separateMultipleMajor": true
36
+ },
37
+ // 2a) Non-pinned deps: override splitting so patch+minor can be combined
22
38
  {
23
- "groupName": "all non-minor dependencies",
24
- "groupSlug": "all-minor-patch",
25
- "matchPackageNames": [
26
- "*"
39
+ "description": "Non-pinned deps: allow patch+minor to group; keep majors separate",
40
+ "matchManagers": ["npm", "pnpm", "yarn", "bun"],
41
+ "matchDepTypes": [
42
+ "dependencies",
43
+ "devDependencies",
44
+ "optionalDependencies",
45
+ "peerDependencies"
27
46
  ],
28
- "matchUpdateTypes": [
29
- "patch"
30
- ]
47
+ "matchCurrentValue": "/(^[~^]|[<>=| -])/", // anything that looks like a range
48
+ "separateMinorPatch": false,
49
+ "separateMajorMinor": true
50
+ },
51
+ // 2b) Non-pinned deps: actually group patch+minor together
52
+ {
53
+ "description": "Non-pinned deps: group non-major updates",
54
+ "matchManagers": ["npm", "pnpm", "yarn", "bun"],
55
+ "matchDepTypes": [
56
+ "dependencies",
57
+ "devDependencies",
58
+ "optionalDependencies",
59
+ "peerDependencies"
60
+ ],
61
+ "matchCurrentValue": "/(^[~^]|[<>=| -])/",
62
+ "matchUpdateTypes": ["minor", "patch"], // only non-majors
63
+ "groupName": "deps (non-major)"
31
64
  }
32
65
  ],
33
- "postUpdateOptions": [
34
- "yarnDedupeHighest"
35
- ],
66
+ "postUpdateOptions": ["yarnDedupeHighest"],
36
67
  "prConcurrentLimit": 30,
37
68
  "prHourlyLimit": 0,
38
69
  "rangeStrategy": "bump",
39
70
  "rebaseWhen": "conflicted",
40
71
  "schedule": "on sunday before 6:00am",
72
+ "separateMajorMinor": true,
73
+ // Global defaults are fine; rule 2a overrides minor/patch splitting for ranged deps
74
+ "separateMinorPatch": true,
75
+ "separateMultipleMajor": true,
76
+ "separateMultipleMinor": true,
41
77
  "timezone": "UTC"
42
78
  }
@@ -0,0 +1,541 @@
1
+ import { DeploymentOption, SystemDependency } from '@lobehub/market-sdk';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ // Import after mock setup
5
+ import { mcpSystemDepsCheckService } from './MCPSystemDepsCheckService';
6
+ import { InstallationChecker } from './types';
7
+
8
+ // Hoist the mock to ensure it's available in the factory
9
+ const { mockExecPromise } = vi.hoisted(() => {
10
+ return {
11
+ mockExecPromise: vi.fn(),
12
+ };
13
+ });
14
+
15
+ // Mock node:child_process
16
+ vi.mock('node:child_process');
17
+
18
+ // Mock node:util to return our hoisted mock when promisify is called
19
+ vi.mock('node:util', () => ({
20
+ default: {
21
+ promisify: () => mockExecPromise,
22
+ },
23
+ promisify: () => mockExecPromise,
24
+ }));
25
+
26
+ describe('MCPSystemDepsCheckService', () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+
31
+ describe('registerChecker', () => {
32
+ it('should register an installation checker', () => {
33
+ const mockChecker: InstallationChecker = {
34
+ checkPackageInstalled: vi.fn(),
35
+ };
36
+
37
+ mcpSystemDepsCheckService.registerChecker('npm', mockChecker);
38
+
39
+ // Verify checker is registered by using it in checkDeployOption
40
+ expect(() => mcpSystemDepsCheckService.registerChecker('npm', mockChecker)).not.toThrow();
41
+ });
42
+ });
43
+
44
+ describe('checkSystemDependency', () => {
45
+ it('should successfully check installed dependency without version requirement', async () => {
46
+ const mockDependency: SystemDependency = {
47
+ name: 'node',
48
+ checkCommand: 'node --version',
49
+ };
50
+
51
+ mockExecPromise.mockResolvedValue({ stdout: 'v18.16.0\n', stderr: '' });
52
+
53
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
54
+
55
+ expect(result).toEqual({
56
+ installed: true,
57
+ meetRequirement: true,
58
+ name: 'node',
59
+ requiredVersion: undefined,
60
+ version: 'v18.16.0',
61
+ installInstructions: undefined,
62
+ });
63
+ });
64
+
65
+ it('should parse version when versionParsingRequired is true', async () => {
66
+ const mockDependency: SystemDependency = {
67
+ name: 'python',
68
+ checkCommand: 'python --version',
69
+ versionParsingRequired: true,
70
+ };
71
+
72
+ mockExecPromise.mockResolvedValue({ stdout: 'Python 3.10.5\n', stderr: '' });
73
+
74
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
75
+
76
+ expect(result.version).toBe('3.10.5');
77
+ expect(result.installed).toBe(true);
78
+ });
79
+
80
+ it('should handle version with v prefix in parsing', async () => {
81
+ const mockDependency: SystemDependency = {
82
+ name: 'node',
83
+ checkCommand: 'node --version',
84
+ versionParsingRequired: true,
85
+ };
86
+
87
+ mockExecPromise.mockResolvedValue({ stdout: 'v20.1.0\n', stderr: '' });
88
+
89
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
90
+
91
+ expect(result.version).toBe('v20.1.0');
92
+ expect(result.installed).toBe(true);
93
+ });
94
+
95
+ it('should check version with >= operator', async () => {
96
+ const mockDependency: SystemDependency = {
97
+ name: 'node',
98
+ checkCommand: 'node --version',
99
+ requiredVersion: '>=18.0.0',
100
+ versionParsingRequired: true,
101
+ };
102
+
103
+ mockExecPromise.mockResolvedValue({ stdout: 'v20.1.0\n', stderr: '' });
104
+
105
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
106
+
107
+ expect(result.meetRequirement).toBe(true);
108
+ expect(result.installed).toBe(true);
109
+ expect(result.version).toBe('v20.1.0');
110
+ });
111
+
112
+ it('should fail version check with >= operator when version is lower', async () => {
113
+ const mockDependency: SystemDependency = {
114
+ name: 'node',
115
+ checkCommand: 'node --version',
116
+ requiredVersion: '>=20.0.0',
117
+ versionParsingRequired: true,
118
+ };
119
+
120
+ mockExecPromise.mockResolvedValue({ stdout: 'v18.16.0\n', stderr: '' });
121
+
122
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
123
+
124
+ expect(result.meetRequirement).toBe(false);
125
+ expect(result.installed).toBe(true);
126
+ });
127
+
128
+ it('should check version with > operator', async () => {
129
+ const mockDependency: SystemDependency = {
130
+ name: 'python',
131
+ checkCommand: 'python --version',
132
+ requiredVersion: '>3.0.0',
133
+ versionParsingRequired: true,
134
+ };
135
+
136
+ mockExecPromise.mockResolvedValue({ stdout: 'Python 3.10.5\n', stderr: '' });
137
+
138
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
139
+
140
+ expect(result.meetRequirement).toBe(true);
141
+ });
142
+
143
+ it('should check version with <= operator', async () => {
144
+ const mockDependency: SystemDependency = {
145
+ name: 'tool',
146
+ checkCommand: 'tool --version',
147
+ requiredVersion: '<=2.0.0',
148
+ versionParsingRequired: true,
149
+ };
150
+
151
+ mockExecPromise.mockResolvedValue({ stdout: '1.5.0\n', stderr: '' });
152
+
153
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
154
+
155
+ expect(result.meetRequirement).toBe(true);
156
+ });
157
+
158
+ it('should check version with < operator', async () => {
159
+ const mockDependency: SystemDependency = {
160
+ name: 'tool',
161
+ checkCommand: 'tool --version',
162
+ requiredVersion: '<2.0.0',
163
+ versionParsingRequired: true,
164
+ };
165
+
166
+ mockExecPromise.mockResolvedValue({ stdout: '2.5.0\n', stderr: '' });
167
+
168
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
169
+
170
+ expect(result.meetRequirement).toBe(false);
171
+ });
172
+
173
+ it('should check version with = operator (default)', async () => {
174
+ const mockDependency: SystemDependency = {
175
+ name: 'tool',
176
+ checkCommand: 'tool --version',
177
+ requiredVersion: '3.0.0',
178
+ versionParsingRequired: true,
179
+ };
180
+
181
+ mockExecPromise.mockResolvedValue({ stdout: '3.0.0\n', stderr: '' });
182
+
183
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
184
+
185
+ expect(result.meetRequirement).toBe(true);
186
+ });
187
+
188
+ it('should use default check command when not provided', async () => {
189
+ const mockDependency: SystemDependency = {
190
+ name: 'git',
191
+ };
192
+
193
+ mockExecPromise.mockResolvedValue({ stdout: 'git version 2.39.0\n', stderr: '' });
194
+
195
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
196
+
197
+ expect(result.installed).toBe(true);
198
+ });
199
+
200
+ it('should handle stderr without stdout as error', async () => {
201
+ const mockDependency: SystemDependency = {
202
+ name: 'invalid-tool',
203
+ checkCommand: 'invalid-tool --version',
204
+ };
205
+
206
+ mockExecPromise.mockResolvedValue({ stdout: '', stderr: 'command not found' });
207
+
208
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
209
+
210
+ expect(result.installed).toBe(false);
211
+ expect(result.meetRequirement).toBe(false);
212
+ expect(result.error).toBe('command not found');
213
+ });
214
+
215
+ it('should handle command execution error', async () => {
216
+ const mockDependency: SystemDependency = {
217
+ name: 'missing-tool',
218
+ checkCommand: 'missing-tool --version',
219
+ };
220
+
221
+ mockExecPromise.mockRejectedValue(new Error('command not found'));
222
+
223
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
224
+
225
+ expect(result.installed).toBe(false);
226
+ expect(result.meetRequirement).toBe(false);
227
+ expect(result.error).toBe('command not found');
228
+ });
229
+
230
+ it('should handle unknown error types', async () => {
231
+ const mockDependency: SystemDependency = {
232
+ name: 'tool',
233
+ checkCommand: 'tool --version',
234
+ };
235
+
236
+ mockExecPromise.mockRejectedValue('string error');
237
+
238
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
239
+
240
+ expect(result.installed).toBe(false);
241
+ expect(result.error).toBe('Unknown error');
242
+ });
243
+
244
+ it('should include install instructions on macOS', async () => {
245
+ const originalPlatform = process.platform;
246
+ Object.defineProperty(process, 'platform', {
247
+ value: 'darwin',
248
+ });
249
+
250
+ const mockDependency: SystemDependency = {
251
+ name: 'brew',
252
+ checkCommand: 'brew --version',
253
+ installInstructions: {
254
+ macos: 'Install via Homebrew',
255
+ linux: 'Use apt-get',
256
+ manual: 'Download from website',
257
+ },
258
+ } as any;
259
+
260
+ mockExecPromise.mockRejectedValue(new Error('not found'));
261
+
262
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
263
+
264
+ expect(result.installInstructions).toEqual({
265
+ current: 'Install via Homebrew',
266
+ manual: 'Download from website',
267
+ });
268
+
269
+ Object.defineProperty(process, 'platform', {
270
+ value: originalPlatform,
271
+ });
272
+ });
273
+
274
+ it('should include install instructions on Linux', async () => {
275
+ const originalPlatform = process.platform;
276
+ Object.defineProperty(process, 'platform', {
277
+ value: 'linux',
278
+ });
279
+
280
+ const mockDependency: SystemDependency = {
281
+ name: 'tool',
282
+ checkCommand: 'tool --version',
283
+ installInstructions: {
284
+ linux_debian: 'apt-get install tool',
285
+ manual: 'Manual install',
286
+ },
287
+ } as any;
288
+
289
+ mockExecPromise.mockRejectedValue(new Error('not found'));
290
+
291
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
292
+
293
+ expect(result.installInstructions).toEqual({
294
+ current: 'apt-get install tool',
295
+ manual: 'Manual install',
296
+ });
297
+
298
+ Object.defineProperty(process, 'platform', {
299
+ value: originalPlatform,
300
+ });
301
+ });
302
+
303
+ it('should fallback to linux instruction when linux_debian is not available', async () => {
304
+ const originalPlatform = process.platform;
305
+ Object.defineProperty(process, 'platform', {
306
+ value: 'linux',
307
+ });
308
+
309
+ const mockDependency: SystemDependency = {
310
+ name: 'tool',
311
+ checkCommand: 'tool --version',
312
+ installInstructions: {
313
+ linux: 'Generic linux install',
314
+ manual: 'Manual install',
315
+ },
316
+ } as any;
317
+
318
+ mockExecPromise.mockRejectedValue(new Error('not found'));
319
+
320
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
321
+
322
+ expect(result.installInstructions?.current).toBe('Generic linux install');
323
+
324
+ Object.defineProperty(process, 'platform', {
325
+ value: originalPlatform,
326
+ });
327
+ });
328
+
329
+ it('should include install instructions on Windows', async () => {
330
+ const originalPlatform = process.platform;
331
+ Object.defineProperty(process, 'platform', {
332
+ value: 'win32',
333
+ });
334
+
335
+ const mockDependency: SystemDependency = {
336
+ name: 'tool',
337
+ checkCommand: 'tool --version',
338
+ installInstructions: {
339
+ windows: 'Install via Chocolatey',
340
+ manual: 'Download from website',
341
+ },
342
+ } as any;
343
+
344
+ mockExecPromise.mockRejectedValue(new Error('not found'));
345
+
346
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
347
+
348
+ expect(result.installInstructions).toEqual({
349
+ current: 'Install via Chocolatey',
350
+ manual: 'Download from website',
351
+ });
352
+
353
+ Object.defineProperty(process, 'platform', {
354
+ value: originalPlatform,
355
+ });
356
+ });
357
+ });
358
+
359
+ describe('checkDeployOption', () => {
360
+ it('should use installation checker when available', async () => {
361
+ const mockChecker: InstallationChecker = {
362
+ checkPackageInstalled: vi.fn().mockResolvedValue({
363
+ installed: true,
364
+ packageName: 'test-package',
365
+ }),
366
+ };
367
+
368
+ mcpSystemDepsCheckService.registerChecker('npm', mockChecker);
369
+
370
+ const mockOption: DeploymentOption = {
371
+ installationMethod: 'npm',
372
+ installationDetails: { packageName: 'test-package' },
373
+ connection: {
374
+ command: 'node',
375
+ args: ['index.js'],
376
+ },
377
+ } as any;
378
+
379
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
380
+
381
+ expect(mockChecker.checkPackageInstalled).toHaveBeenCalledWith({
382
+ packageName: 'test-package',
383
+ });
384
+ expect(result.packageInstalled).toBe(true);
385
+ });
386
+
387
+ it('should set connection type to http when url is provided', async () => {
388
+ const mockOption: DeploymentOption = {
389
+ installationMethod: 'manual',
390
+ installationDetails: {},
391
+ connection: {
392
+ url: 'http://localhost:3000',
393
+ },
394
+ } as any;
395
+
396
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
397
+
398
+ expect(result.connection.type).toBe('http');
399
+ expect(result.connection.url).toBe('http://localhost:3000');
400
+ });
401
+
402
+ it('should detect configuration requirements from required array', async () => {
403
+ const mockOption: DeploymentOption = {
404
+ installationMethod: 'npm',
405
+ installationDetails: { packageName: 'test-package' },
406
+ connection: {
407
+ command: 'node',
408
+ args: ['index.js'],
409
+ configSchema: {
410
+ type: 'object',
411
+ required: ['apiKey'],
412
+ properties: {
413
+ apiKey: { type: 'string' },
414
+ },
415
+ },
416
+ },
417
+ } as any;
418
+
419
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
420
+
421
+ expect(result.needsConfig).toBe(true);
422
+ expect(result.configSchema).toBeDefined();
423
+ });
424
+
425
+ it('should detect configuration requirements from property-level required flag', async () => {
426
+ const mockOption: DeploymentOption = {
427
+ installationMethod: 'npm',
428
+ installationDetails: { packageName: 'test-package' },
429
+ connection: {
430
+ command: 'node',
431
+ args: ['index.js'],
432
+ configSchema: {
433
+ type: 'object',
434
+ properties: {
435
+ apiKey: { type: 'string', required: true },
436
+ },
437
+ },
438
+ },
439
+ } as any;
440
+
441
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
442
+
443
+ expect(result.needsConfig).toBe(true);
444
+ });
445
+
446
+ it('should not require config when schema has no required fields', async () => {
447
+ const mockOption: DeploymentOption = {
448
+ installationMethod: 'npm',
449
+ installationDetails: { packageName: 'test-package' },
450
+ connection: {
451
+ command: 'node',
452
+ args: ['index.js'],
453
+ configSchema: {
454
+ type: 'object',
455
+ properties: {
456
+ optional: { type: 'string' },
457
+ },
458
+ },
459
+ },
460
+ } as any;
461
+
462
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
463
+
464
+ expect(result.needsConfig).toBe(false);
465
+ });
466
+
467
+ it('should not require config when schema has empty required array', async () => {
468
+ const mockOption: DeploymentOption = {
469
+ installationMethod: 'npm',
470
+ installationDetails: { packageName: 'test-package' },
471
+ connection: {
472
+ command: 'node',
473
+ args: ['index.js'],
474
+ configSchema: {
475
+ type: 'object',
476
+ required: [],
477
+ properties: {
478
+ optional: { type: 'string' },
479
+ },
480
+ },
481
+ },
482
+ } as any;
483
+
484
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
485
+
486
+ expect(result.needsConfig).toBe(false);
487
+ });
488
+
489
+ it('should include isRecommended flag from deployment option', async () => {
490
+ const mockOption: DeploymentOption = {
491
+ installationMethod: 'npm',
492
+ installationDetails: { packageName: 'test-package' },
493
+ isRecommended: true,
494
+ connection: {
495
+ command: 'node',
496
+ args: ['index.js'],
497
+ },
498
+ } as any;
499
+
500
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
501
+
502
+ expect(result.isRecommended).toBe(true);
503
+ });
504
+
505
+ it('should handle multiple system dependencies', async () => {
506
+ const mockOption: DeploymentOption = {
507
+ installationMethod: 'npm',
508
+ installationDetails: { packageName: 'test-package' },
509
+ systemDependencies: [
510
+ {
511
+ name: 'node',
512
+ checkCommand: 'node --version',
513
+ requiredVersion: '>=18.0.0',
514
+ versionParsingRequired: true,
515
+ },
516
+ {
517
+ name: 'python',
518
+ checkCommand: 'python --version',
519
+ requiredVersion: '>=3.0.0',
520
+ versionParsingRequired: true,
521
+ },
522
+ ],
523
+ connection: {
524
+ command: 'node',
525
+ args: ['index.js'],
526
+ },
527
+ } as any;
528
+
529
+ mockExecPromise
530
+ .mockResolvedValueOnce({ stdout: 'v20.1.0\n', stderr: '' })
531
+ .mockResolvedValueOnce({ stdout: 'Python 3.10.5\n', stderr: '' });
532
+
533
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
534
+
535
+ expect(result.systemDependencies).toHaveLength(2);
536
+ expect(result.allDependenciesMet).toBe(true);
537
+ expect(result.systemDependencies[0]!.name).toBe('node');
538
+ expect(result.systemDependencies[1]!.name).toBe('python');
539
+ });
540
+ });
541
+ });