imcp 0.0.3 → 0.0.5

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.
Files changed (93) hide show
  1. package/README.md +5 -6
  2. package/dist/cli/commands/install.js +2 -0
  3. package/dist/cli/commands/list.js +1 -0
  4. package/dist/cli/index.js +1 -2
  5. package/dist/core/ConfigurationLoader.d.ts +32 -0
  6. package/dist/core/ConfigurationLoader.js +213 -0
  7. package/dist/core/ConfigurationProvider.d.ts +2 -3
  8. package/dist/core/ConfigurationProvider.js +13 -182
  9. package/dist/core/InstallationService.d.ts +8 -0
  10. package/dist/core/InstallationService.js +124 -96
  11. package/dist/core/RequirementService.d.ts +1 -1
  12. package/dist/core/RequirementService.js +5 -9
  13. package/dist/core/constants.js +14 -1
  14. package/dist/core/installers/BaseInstaller.d.ts +5 -4
  15. package/dist/core/installers/BaseInstaller.js +17 -28
  16. package/dist/core/installers/ClientInstaller.js +159 -39
  17. package/dist/core/installers/CommandInstaller.d.ts +1 -0
  18. package/dist/core/installers/CommandInstaller.js +3 -0
  19. package/dist/core/installers/GeneralInstaller.d.ts +1 -0
  20. package/dist/core/installers/GeneralInstaller.js +3 -0
  21. package/dist/core/installers/InstallerFactory.d.ts +9 -7
  22. package/dist/core/installers/InstallerFactory.js +10 -8
  23. package/dist/core/installers/NpmInstaller.d.ts +1 -0
  24. package/dist/core/installers/NpmInstaller.js +3 -0
  25. package/dist/core/installers/PipInstaller.d.ts +6 -3
  26. package/dist/core/installers/PipInstaller.js +21 -8
  27. package/dist/core/installers/RequirementInstaller.d.ts +4 -3
  28. package/dist/core/installers/clients/ClientInstaller.d.ts +23 -0
  29. package/dist/core/installers/clients/ClientInstaller.js +573 -0
  30. package/dist/core/installers/clients/ExtensionInstaller.d.ts +26 -0
  31. package/dist/core/installers/clients/ExtensionInstaller.js +149 -0
  32. package/dist/core/installers/index.d.ts +8 -6
  33. package/dist/core/installers/index.js +8 -6
  34. package/dist/core/installers/requirements/BaseInstaller.d.ts +59 -0
  35. package/dist/core/installers/requirements/BaseInstaller.js +168 -0
  36. package/dist/core/installers/requirements/CommandInstaller.d.ts +37 -0
  37. package/dist/core/installers/requirements/CommandInstaller.js +173 -0
  38. package/dist/core/installers/requirements/GeneralInstaller.d.ts +33 -0
  39. package/dist/core/installers/requirements/GeneralInstaller.js +86 -0
  40. package/dist/core/installers/requirements/InstallerFactory.d.ts +54 -0
  41. package/dist/core/installers/requirements/InstallerFactory.js +97 -0
  42. package/dist/core/installers/requirements/NpmInstaller.d.ts +26 -0
  43. package/dist/core/installers/requirements/NpmInstaller.js +128 -0
  44. package/dist/core/installers/requirements/PipInstaller.d.ts +28 -0
  45. package/dist/core/installers/requirements/PipInstaller.js +128 -0
  46. package/{src/core/installers/RequirementInstaller.ts → dist/core/installers/requirements/RequirementInstaller.d.ts} +33 -38
  47. package/dist/core/installers/requirements/RequirementInstaller.js +3 -0
  48. package/dist/core/types.d.ts +4 -1
  49. package/dist/services/ServerService.js +1 -1
  50. package/dist/utils/clientUtils.d.ts +0 -6
  51. package/dist/utils/clientUtils.js +3 -2
  52. package/dist/utils/githubUtils.d.ts +11 -0
  53. package/dist/utils/githubUtils.js +88 -0
  54. package/dist/utils/osUtils.d.ts +17 -0
  55. package/dist/utils/osUtils.js +184 -0
  56. package/dist/web/public/css/modal.css +97 -3
  57. package/dist/web/public/index.html +21 -2
  58. package/dist/web/public/js/modal.js +177 -28
  59. package/dist/web/public/js/serverCategoryDetails.js +12 -10
  60. package/dist/web/public/js/serverCategoryList.js +20 -5
  61. package/dist/web/public/modal.html +27 -13
  62. package/dist/web/server.js +1 -1
  63. package/package.json +2 -1
  64. package/src/cli/commands/install.ts +4 -2
  65. package/src/cli/commands/list.ts +1 -0
  66. package/src/cli/index.ts +1 -1
  67. package/src/core/ConfigurationLoader.ts +251 -0
  68. package/src/core/ConfigurationProvider.ts +13 -195
  69. package/src/core/InstallationService.ts +140 -106
  70. package/src/core/RequirementService.ts +5 -10
  71. package/src/core/constants.ts +15 -1
  72. package/src/core/installers/{ClientInstaller.ts → clients/ClientInstaller.ts} +185 -46
  73. package/src/core/installers/clients/ExtensionInstaller.ts +162 -0
  74. package/src/core/installers/index.ts +9 -7
  75. package/src/core/installers/{BaseInstaller.ts → requirements/BaseInstaller.ts} +10 -118
  76. package/src/core/installers/{CommandInstaller.ts → requirements/CommandInstaller.ts} +7 -3
  77. package/src/core/installers/{GeneralInstaller.ts → requirements/GeneralInstaller.ts} +6 -2
  78. package/src/core/installers/{InstallerFactory.ts → requirements/InstallerFactory.ts} +11 -9
  79. package/src/core/installers/{NpmInstaller.ts → requirements/NpmInstaller.ts} +7 -4
  80. package/src/core/installers/{PipInstaller.ts → requirements/PipInstaller.ts} +26 -10
  81. package/src/core/installers/requirements/RequirementInstaller.ts +41 -0
  82. package/src/core/types.ts +4 -1
  83. package/src/services/ServerService.ts +1 -1
  84. package/src/utils/clientUtils.ts +4 -2
  85. package/src/utils/githubUtils.ts +103 -0
  86. package/src/utils/osUtils.ts +206 -15
  87. package/src/web/public/css/modal.css +97 -3
  88. package/src/web/public/index.html +21 -2
  89. package/src/web/public/js/modal.js +177 -28
  90. package/src/web/public/js/serverCategoryDetails.js +12 -10
  91. package/src/web/public/js/serverCategoryList.js +20 -5
  92. package/src/web/public/modal.html +27 -13
  93. package/src/web/server.ts +1 -1
@@ -1,10 +1,10 @@
1
- import { RequirementConfig, RequirementStatus, RegistryConfig } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, RegistryConfig, ServerInstallOptions } from '../../types.js';
2
2
  import path from 'path';
3
3
  import fs from 'fs/promises';
4
- import { SETTINGS_DIR } from '../constants.js';
5
- import { extractZipFile } from '../../utils/clientUtils.js';
4
+ import { SETTINGS_DIR } from '../../constants.js';
5
+ import { extractZipFile } from '../../../utils/clientUtils.js';
6
6
  import { RequirementInstaller } from './RequirementInstaller.js';
7
- import { Logger } from '../../utils/logger.js';
7
+ import { Logger } from '../../../utils/logger.js';
8
8
 
9
9
  /**
10
10
  * Abstract base class with common functionality for all requirement installers
@@ -19,8 +19,9 @@ export abstract class BaseInstaller implements RequirementInstaller {
19
19
  }
20
20
 
21
21
  abstract canHandle(requirement: RequirementConfig): boolean;
22
- abstract install(requirement: RequirementConfig): Promise<RequirementStatus>;
23
- abstract checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
22
+ abstract supportCheckUpdates(): boolean;
23
+ abstract install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
+ abstract checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
25
 
25
26
  /**
26
27
  * Check if updates are available for the requirement
@@ -82,107 +83,6 @@ export abstract class BaseInstaller implements RequirementInstaller {
82
83
  }
83
84
  }
84
85
 
85
- /**
86
- * Helper method to ensure downloads directory exists
87
- */
88
- protected async ensureDownloadsDir(): Promise<void> {
89
- await fs.mkdir(this.downloadsDir, { recursive: true });
90
- }
91
-
92
- /**
93
- * Helper to handle GitHub release downloads
94
- * @param requirement The requirement configuration
95
- * @param registry The GitHub release registry configuration
96
- * @returns The path to the downloaded file
97
- */
98
- protected async handleGitHubRelease(
99
- requirement: RequirementConfig,
100
- registry: RegistryConfig['githubRelease']
101
- ): Promise<{ resolvedVersion: string; resolvedPath: string }> {
102
- if (!registry) {
103
- throw new Error('GitHub release registry configuration is required');
104
- }
105
-
106
- await this.ensureDownloadsDir();
107
- const { repository, assetsName, assetName } = registry;
108
-
109
- if (!repository) {
110
- throw new Error('GitHub repository is required for GitHub release downloads');
111
- }
112
-
113
- let version = requirement.version;
114
- let resolvedAssetName = assetName || '';
115
- let resolvedAssetsName = assetsName || '';
116
-
117
- const { stdout } = await this.execPromise(`gh release view --repo ${repository} --json tagName --jq .tagName`);
118
- const latestTag = stdout.trim();
119
- // Handle latest version detection
120
- if (version.includes('${latest}') || version === 'latest') {
121
-
122
- let latestVersion = latestTag
123
- if (latestVersion.startsWith('v') && version.startsWith('v')) {
124
- latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
125
- // Replace ${latest} in version and asset names
126
- version = version.replace('${latest}', latestVersion);
127
- if (assetsName) {
128
- resolvedAssetsName = assetsName.replace('${latest}', latestVersion).replace('${version}', version);
129
- }
130
- if (assetName) {
131
- resolvedAssetName = assetName.replace('${latest}', latestVersion).replace('${version}', version);
132
- }
133
- }
134
- } else {
135
- if (assetsName) {
136
- resolvedAssetsName = assetsName.replace('${latest}', version).replace('${version}', version);
137
- }
138
- if (assetName) {
139
- resolvedAssetName = assetName.replace('${latest}', version).replace('${version}', version);
140
- }
141
- Logger.debug(`Downloading ${requirement.name} from GitHub release ${repository} version ${version}`);
142
- Logger.debug(`ResolvedAssetsName} ${resolvedAssetName}; ResolvedAsetName} ${resolvedAssetName}`);
143
- }
144
- const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
145
- Logger.debug(`Resolved pattern: ${pattern}`);
146
-
147
- if (!pattern) {
148
- throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
149
- }
150
-
151
- // Download the release asset
152
- const downloadPath = path.join(this.downloadsDir, path.basename(pattern));
153
- if (!await this.fileExists(downloadPath)) {
154
- await this.execPromise(`gh release download ${version.startsWith('v') ? version : `v${version}`} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
155
- }
156
-
157
- // Handle zip file extraction if the downloaded file is a zip
158
- if (downloadPath.endsWith('.zip')) {
159
- const extractDir = path.join(this.downloadsDir, path.basename(pattern, '.zip'));
160
- await fs.mkdir(extractDir, { recursive: true });
161
-
162
- // Extract the zip file
163
- await extractZipFile(downloadPath, { dir: extractDir });
164
- let assetPath = '';
165
- // If resolvedAssetName is specified, look for it in the extracted directory
166
- if (resolvedAssetName) {
167
- assetPath = path.join(extractDir, resolvedAssetName);
168
- try {
169
- await fs.access(assetPath);
170
- return { resolvedVersion: version, resolvedPath: assetPath };
171
- } catch (error) {
172
- throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
173
- }
174
- }
175
- else {
176
- assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
177
- }
178
-
179
- // If no specific asset is required, return the extraction directory
180
- return { resolvedVersion: version, resolvedPath: extractDir };
181
- }
182
-
183
- return { resolvedVersion: version, resolvedPath: downloadPath };
184
- }
185
-
186
86
  /**
187
87
  * Helper to handle artifact registry downloads
188
88
  * @param requirement The requirement configuration
@@ -254,15 +154,6 @@ export abstract class BaseInstaller implements RequirementInstaller {
254
154
  return localPath;
255
155
  }
256
156
 
257
- private async fileExists(filePath: string): Promise<boolean> {
258
- try {
259
- await fs.access(filePath)
260
- return true
261
- } catch {
262
- return false
263
- }
264
- }
265
-
266
157
  /**
267
158
  * Get the latest version available for a GitHub repository
268
159
  * @param repository The GitHub repository in format 'owner/repo'
@@ -301,8 +192,9 @@ export abstract class BaseInstaller implements RequirementInstaller {
301
192
  * @param packageName The name of the pip package
302
193
  * @returns The latest version
303
194
  */
304
- protected async getPipLatestVersion(packageName: string): Promise<string> {
305
- const { stdout } = await this.execPromise(`pip index versions ${packageName} --pre=0 | grep -oP "(?<=Latest:\\s)[^\\s]+" | head -1`);
195
+ protected async getPipLatestVersion(packageName: string, options?: ServerInstallOptions): Promise<string> {
196
+ const pythonCmd = options?.settings?.pythonEnv as string || 'python';
197
+ const { stdout } = await this.execPromise(`${pythonCmd} -m pip index versions ${packageName} --pre=0 | grep -oP "(?<=Latest:\\s)[^\\s]+" | head -1`);
306
198
  return stdout.trim();
307
199
  }
308
200
  }
@@ -1,7 +1,7 @@
1
- import { RequirementConfig, RequirementStatus, OSType } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, OSType } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
- import { getOSType, refreshPathEnv } from '../../utils/osUtils.js';
4
- import { Logger } from '../../utils/logger.js';
3
+ import { getOSType, refreshPathEnv } from '../../../utils/osUtils.js';
4
+ import { Logger } from '../../../utils/logger.js';
5
5
 
6
6
  /**
7
7
  * Mapping of command names to their package IDs on different platforms
@@ -15,6 +15,7 @@ interface CommandMapping {
15
15
  * Installer implementation for command-line tools
16
16
  */
17
17
  export class CommandInstaller extends BaseInstaller {
18
+
18
19
  /**
19
20
  * Mapping of command names to their package IDs
20
21
  * This handles special cases where the command name differs from the package ID
@@ -33,6 +34,9 @@ export class CommandInstaller extends BaseInstaller {
33
34
  return requirement.type === 'command';
34
35
  }
35
36
 
37
+ supportCheckUpdates(): boolean {
38
+ return false;
39
+ }
36
40
  /**
37
41
  * Get the mapped package ID for a command
38
42
  * @param commandName The command name to map
@@ -1,5 +1,6 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
+ import { handleGitHubRelease } from '../../../utils/githubUtils.js';
3
4
 
4
5
  /**
5
6
  * Installer implementation for general requirements (type 'other')
@@ -15,6 +16,9 @@ export class GeneralInstaller extends BaseInstaller {
15
16
  return requirement.type === 'other';
16
17
  }
17
18
 
19
+ supportCheckUpdates(): boolean {
20
+ return false;
21
+ }
18
22
  /**
19
23
  * Check if the requirement is already installed
20
24
  * For general installers, we can't easily check if something is installed
@@ -52,7 +56,7 @@ export class GeneralInstaller extends BaseInstaller {
52
56
  let installPath: string;
53
57
 
54
58
  if (requirement.registry.githubRelease) {
55
- const result = await this.handleGitHubRelease(requirement, requirement.registry.githubRelease);
59
+ const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
56
60
  installPath = result.resolvedPath;
57
61
  } else if (requirement.registry.artifacts) {
58
62
  installPath = await this.handleArtifactsRegistry(requirement, requirement.registry.artifacts);
@@ -1,4 +1,4 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { RequirementInstaller } from './RequirementInstaller.js';
3
3
  import { NpmInstaller } from './NpmInstaller.js';
4
4
  import { PipInstaller } from './PipInstaller.js';
@@ -59,9 +59,10 @@ export class InstallerFactory {
59
59
  /**
60
60
  * Install a requirement using the appropriate installer
61
61
  * @param requirement The requirement to install
62
+ * @param options Installation options including python environment
62
63
  * @returns The installation status
63
64
  */
64
- public async install(requirement: RequirementConfig): Promise<RequirementStatus> {
65
+ public async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
65
66
  const installer = this.getInstaller(requirement);
66
67
  if (!installer) {
67
68
  return {
@@ -73,15 +74,16 @@ export class InstallerFactory {
73
74
  };
74
75
  }
75
76
 
76
- return await installer.install(requirement);
77
+ return await installer.install(requirement, options);
77
78
  }
78
79
 
79
80
  /**
80
- * Check the installation status of a requirement
81
- * @param requirement The requirement to check
82
- * @returns The installation status
83
- */
84
- public async checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus> {
81
+ * Check the installation status of a requirement
82
+ * @param requirement The requirement to check
83
+ * @param options Installation options including python environment
84
+ * @returns The installation status
85
+ */
86
+ public async checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
85
87
  const installer = this.getInstaller(requirement);
86
88
  if (!installer) {
87
89
  return {
@@ -93,7 +95,7 @@ export class InstallerFactory {
93
95
  };
94
96
  }
95
97
 
96
- return await installer.checkInstallation(requirement);
98
+ return await installer.checkInstallation(requirement, options);
97
99
  }
98
100
  }
99
101
 
@@ -1,8 +1,8 @@
1
1
  import { stat } from 'fs';
2
- import { RequirementConfig, RequirementStatus } from '../types.js';
2
+ import { RequirementConfig, RequirementStatus } from '../../types.js';
3
3
  import { BaseInstaller } from './BaseInstaller.js';
4
- import { compareVersions } from '../../utils/versionUtils.js';
5
-
4
+ import { compareVersions } from '../../../utils/versionUtils.js';
5
+ import { handleGitHubRelease } from '../../../utils/githubUtils.js';
6
6
  /**
7
7
  * Installer implementation for NPM packages
8
8
  */
@@ -16,6 +16,9 @@ export class NpmInstaller extends BaseInstaller {
16
16
  return requirement.type === 'npm';
17
17
  }
18
18
 
19
+ supportCheckUpdates(): boolean {
20
+ return true;
21
+ }
19
22
  /**
20
23
  * Check if the NPM package is already installed
21
24
  * @param requirement The requirement to check
@@ -70,7 +73,7 @@ export class NpmInstaller extends BaseInstaller {
70
73
  let packageSource: string;
71
74
 
72
75
  if (requirement.registry.githubRelease) {
73
- const result = await this.handleGitHubRelease(requirement, requirement.registry.githubRelease);
76
+ const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
74
77
  packageSource = result.resolvedPath;
75
78
  resolvedVersion = result.resolvedVersion;
76
79
  } else if (requirement.registry.artifacts) {
@@ -1,10 +1,19 @@
1
- import { RequirementConfig, RequirementStatus } from '../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
+ import { handleGitHubRelease } from '../../../utils/githubUtils.js';
3
4
 
4
5
  /**
5
6
  * Installer implementation for Python packages using pip
6
7
  */
7
8
  export class PipInstaller extends BaseInstaller {
9
+ private getPythonCommand(options?: ServerInstallOptions): string {
10
+ return options?.settings?.pythonEnv as string || 'python';
11
+ }
12
+
13
+ private getPipCommand(options?: ServerInstallOptions): string {
14
+ const pythonCmd = this.getPythonCommand(options);
15
+ return `${pythonCmd} -m pip`;
16
+ }
8
17
  /**
9
18
  * Check if this installer can handle the given requirement type
10
19
  * @param requirement The requirement to check
@@ -14,14 +23,19 @@ export class PipInstaller extends BaseInstaller {
14
23
  return requirement.type === 'pip';
15
24
  }
16
25
 
26
+ supportCheckUpdates(): boolean {
27
+ /// temporarily disabling update check for pip as not able to get which pip of python is being used
28
+ return false;
29
+ }
17
30
  /**
18
31
  * Check if the Python package is already installed
19
32
  * @param requirement The requirement to check
20
33
  * @returns The status of the requirement
21
34
  */
22
- async checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus> {
35
+ async checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
23
36
  try {
24
- const { stdout, stderr } = await this.execPromise(`pip show ${requirement.name}`);
37
+ const pipCmd = this.getPipCommand(options);
38
+ const { stdout, stderr } = await this.execPromise(`${pipCmd} show ${requirement.name}`);
25
39
 
26
40
  // If we get an output and no error, the package is installed
27
41
  const installed = stdout.includes(requirement.name);
@@ -51,17 +65,19 @@ export class PipInstaller extends BaseInstaller {
51
65
  * @param requirement The requirement to install
52
66
  * @returns The status of the installation
53
67
  */
54
- async install(requirement: RequirementConfig): Promise<RequirementStatus> {
68
+ async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
55
69
  try {
56
- const status = await this.checkInstallation(requirement);
70
+ const status = await this.checkInstallation(requirement, options);
57
71
  if (status.installed) {
58
72
  return status;
59
73
  }
60
74
 
75
+ const pipCmd = this.getPipCommand(options);
76
+
61
77
  // If no registry is specified, use standard pip installation
62
78
  if (!requirement.registry) {
63
79
  // Standard pip installation
64
- const { stderr } = await this.execPromise(`pip install ${requirement.name}==${requirement.version}`);
80
+ const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
65
81
  if (stderr && stderr.toLowerCase().includes('error')) {
66
82
  throw new Error(stderr);
67
83
  }
@@ -70,11 +86,11 @@ export class PipInstaller extends BaseInstaller {
70
86
  let packageSource: string;
71
87
 
72
88
  if (requirement.registry.githubRelease) {
73
- const result = await this.handleGitHubRelease(requirement, requirement.registry.githubRelease);
89
+ const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
74
90
  packageSource = result.resolvedPath;
75
91
 
76
92
  // Install from the downloaded wheel or tar.gz file
77
- const { stderr } = await this.execPromise(`pip install "${packageSource}"`);
93
+ const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
78
94
  if (stderr && stderr.toLowerCase().includes('error')) {
79
95
  throw new Error(stderr);
80
96
  }
@@ -83,7 +99,7 @@ export class PipInstaller extends BaseInstaller {
83
99
 
84
100
  // Install using the custom index URL
85
101
  const { stderr } = await this.execPromise(
86
- `pip install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`
102
+ `${pipCmd} install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`
87
103
  );
88
104
  if (stderr && stderr.toLowerCase().includes('error')) {
89
105
  throw new Error(stderr);
@@ -92,7 +108,7 @@ export class PipInstaller extends BaseInstaller {
92
108
  packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
93
109
 
94
110
  // Install from the local path
95
- const { stderr } = await this.execPromise(`pip install "${packageSource}"`);
111
+ const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
96
112
  if (stderr && stderr.toLowerCase().includes('error')) {
97
113
  throw new Error(stderr);
98
114
  }
@@ -0,0 +1,41 @@
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
+
3
+ /**
4
+ * Interface for requirement installers.
5
+ * Implementations should handle specific requirement types.
6
+ */
7
+ export interface RequirementInstaller {
8
+ /**
9
+ * Check if this installer can handle the given requirement type
10
+ * @param requirement The requirement to check
11
+ * @returns True if this installer can handle the requirement
12
+ */
13
+ canHandle(requirement: RequirementConfig): boolean;
14
+
15
+
16
+ supportCheckUpdates(): boolean;
17
+
18
+ /**
19
+ * Install the requirement
20
+ * @param requirement The requirement to install
21
+ * @returns The status of the installation
22
+ */
23
+ install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
+
25
+ /**
26
+ * Check if the requirement is already installed
27
+ * @param requirement The requirement to check
28
+ * @returns The status of the requirement
29
+ */
30
+ checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
31
+
32
+ /**
33
+ * Check if updates are available for the requirement
34
+ * @param requirement The requirement to check
35
+ * @param currentStatus The current status of the requirement
36
+ * @returns The status of the requirement with update information
37
+ */
38
+ checkForUpdates(requirement: RequirementConfig, currentStatus: RequirementStatus): Promise<RequirementStatus>;
39
+ }
40
+
41
+ // Note: Do not re-export implementations from here to avoid circular dependencies
package/src/core/types.ts CHANGED
@@ -73,6 +73,8 @@ export interface ServerInstallOptions {
73
73
  env?: Record<string, string>; // Environment variables for installation
74
74
  targetClients?: string[]; // Target clients for configuration
75
75
  requirements?: RequirementConfig[];
76
+ args?: string[]; // Arguments for installation
77
+ settings?: Record<string, any>; // Settings for installation like python env
76
78
  }
77
79
 
78
80
  export interface UpdateRequirementOptions {
@@ -101,6 +103,7 @@ export interface DependencyConfig {
101
103
  requirements?: Array<{
102
104
  name: string;
103
105
  version: string;
106
+ order?: number;
104
107
  }>;
105
108
  mcpServers?: Array<{
106
109
  name: string;
@@ -133,7 +136,7 @@ export interface RegistryConfig {
133
136
 
134
137
  export interface RequirementConfig {
135
138
  name: string;
136
- type: 'npm' | 'pip' | 'command' | 'other'; // Add other requirement types if needed
139
+ type: 'npm' | 'pip' | 'command' | 'extension' | 'other'; // Add other requirement types if needed
137
140
  alias?: string; // Alias for the command type
138
141
  version: string;
139
142
  registry?: RegistryConfig;
@@ -88,7 +88,7 @@ export class ServerService {
88
88
  }
89
89
 
90
90
  // Check for updates
91
- const updatedStatus = await requirementService.checkRequirementForUpdates(requirement);
91
+ const updatedStatus = await requirementService.checkRequirementForUpdates(requirement, currentStatus);
92
92
 
93
93
  // If update information is found, update the configuration
94
94
  if (updatedStatus.availableUpdate && serverCategory.name) {
@@ -84,6 +84,8 @@ export function resolveNpmModulePath(pathString: string): string {
84
84
  * @param createIfNotExist Whether to create the file if it doesn't exist
85
85
  * @returns The parsed JSON content
86
86
  */
87
+ import stripJsonComments from 'strip-json-comments';
88
+
87
89
  export async function readJsonFile(filePath: string, createIfNotExist = false): Promise<any> {
88
90
  try {
89
91
  // Ensure directory exists
@@ -92,8 +94,8 @@ export async function readJsonFile(filePath: string, createIfNotExist = false):
92
94
  }
93
95
 
94
96
  const content = await fs.readFile(filePath, 'utf8');
95
- // Remove trailing commas from JSON content
96
- const sanitizedContent = content.replace(/,(\s*[}\]])/g, '$1');
97
+ // Remove comments and trailing commas from JSON content
98
+ const sanitizedContent = stripJsonComments(content).replace(/,(\s*[}\]])/g, '$1');
97
99
  return JSON.parse(sanitizedContent);
98
100
  } catch (error) {
99
101
  if ((error as NodeJS.ErrnoException).code === 'ENOENT' && createIfNotExist) {
@@ -0,0 +1,103 @@
1
+ import { RegistryConfig, RequirementConfig } from '../core/types.js';
2
+ import { exec } from 'child_process';
3
+ import util from 'util';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { extractZipFile } from './clientUtils.js';
7
+ import { Logger } from './logger.js';
8
+ import { SETTINGS_DIR } from '../core/constants.js';
9
+
10
+ const execAsync = util.promisify(exec);
11
+
12
+ /**
13
+ * Helper to handle GitHub release downloads
14
+ * @param requirement The requirement configuration
15
+ * @param registry The GitHub release registry configuration
16
+ * @returns The path to the downloaded file
17
+ */
18
+ export async function handleGitHubRelease(
19
+ requirement: RequirementConfig,
20
+ registry: RegistryConfig['githubRelease']
21
+ ): Promise<{ resolvedVersion: string; resolvedPath: string }> {
22
+ if (!registry) {
23
+ throw new Error('GitHub release registry configuration is required');
24
+ }
25
+
26
+ const downloadsDir = path.join(SETTINGS_DIR, 'downloads');
27
+ await fs.mkdir(downloadsDir, { recursive: true });
28
+ const { repository, assetsName, assetName } = registry;
29
+
30
+ if (!repository) {
31
+ throw new Error('GitHub repository is required for GitHub release downloads');
32
+ }
33
+
34
+ let version = requirement.version;
35
+ let resolvedAssetName = assetName || '';
36
+ let resolvedAssetsName = assetsName || '';
37
+
38
+ const { stdout } = await execAsync(`gh release view --repo ${repository} --json tagName --jq .tagName`);
39
+ const latestTag = stdout.trim();
40
+ let latestVersion = latestTag;
41
+
42
+ const tagWithVPrefix = latestVersion.startsWith('v');
43
+ if (tagWithVPrefix) latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
44
+ version = version.includes("latest") ? latestVersion : version;
45
+ if (assetsName) {
46
+ resolvedAssetsName = assetsName.replace('${latest}', version).replace('${version}', version);
47
+ }
48
+ if (assetName) {
49
+ resolvedAssetName = assetName.replace('${latest}', version).replace('${version}', version);
50
+ }
51
+
52
+ Logger.debug(`Downloading ${requirement.name} from GitHub release ${repository} version ${version}`);
53
+ Logger.debug(`ResolvedAssetsName} ${resolvedAssetName}; ResolvedAsetName} ${resolvedAssetName}`);
54
+ const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
55
+ Logger.debug(`Resolved pattern: ${pattern}`);
56
+
57
+ if (!pattern) {
58
+ throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
59
+ }
60
+
61
+ // Download the release asset
62
+ const downloadPath = path.join(downloadsDir, path.basename(pattern));
63
+ if (!await fileExists(downloadPath)) {
64
+ await execAsync(`gh release download ${tagWithVPrefix ? `v${version}` : version} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
65
+ }
66
+
67
+ // Handle zip file extraction if the downloaded file is a zip
68
+ if (downloadPath.endsWith('.zip')) {
69
+ const extractDir = path.join(downloadsDir, path.basename(pattern, '.zip'));
70
+ await fs.mkdir(extractDir, { recursive: true });
71
+
72
+ // Extract the zip file
73
+ await extractZipFile(downloadPath, { dir: extractDir });
74
+ let assetPath = '';
75
+ // If resolvedAssetName is specified, look for it in the extracted directory
76
+ if (resolvedAssetName) {
77
+ assetPath = path.join(extractDir, resolvedAssetName);
78
+ try {
79
+ await fs.access(assetPath);
80
+ return { resolvedVersion: version, resolvedPath: assetPath };
81
+ } catch (error) {
82
+ throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
83
+ }
84
+ }
85
+ else {
86
+ assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
87
+ }
88
+
89
+ // If no specific asset is required, return the extraction directory
90
+ return { resolvedVersion: version, resolvedPath: extractDir };
91
+ }
92
+
93
+ return { resolvedVersion: version, resolvedPath: downloadPath };
94
+ }
95
+
96
+ async function fileExists(filePath: string): Promise<boolean> {
97
+ try {
98
+ await fs.access(filePath)
99
+ return true
100
+ } catch {
101
+ return false
102
+ }
103
+ }