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.
- package/README.md +5 -6
- package/dist/cli/commands/install.js +2 -0
- package/dist/cli/commands/list.js +1 -0
- package/dist/cli/index.js +1 -2
- package/dist/core/ConfigurationLoader.d.ts +32 -0
- package/dist/core/ConfigurationLoader.js +213 -0
- package/dist/core/ConfigurationProvider.d.ts +2 -3
- package/dist/core/ConfigurationProvider.js +13 -182
- package/dist/core/InstallationService.d.ts +8 -0
- package/dist/core/InstallationService.js +124 -96
- package/dist/core/RequirementService.d.ts +1 -1
- package/dist/core/RequirementService.js +5 -9
- package/dist/core/constants.js +14 -1
- package/dist/core/installers/BaseInstaller.d.ts +5 -4
- package/dist/core/installers/BaseInstaller.js +17 -28
- package/dist/core/installers/ClientInstaller.js +159 -39
- package/dist/core/installers/CommandInstaller.d.ts +1 -0
- package/dist/core/installers/CommandInstaller.js +3 -0
- package/dist/core/installers/GeneralInstaller.d.ts +1 -0
- package/dist/core/installers/GeneralInstaller.js +3 -0
- package/dist/core/installers/InstallerFactory.d.ts +9 -7
- package/dist/core/installers/InstallerFactory.js +10 -8
- package/dist/core/installers/NpmInstaller.d.ts +1 -0
- package/dist/core/installers/NpmInstaller.js +3 -0
- package/dist/core/installers/PipInstaller.d.ts +6 -3
- package/dist/core/installers/PipInstaller.js +21 -8
- package/dist/core/installers/RequirementInstaller.d.ts +4 -3
- package/dist/core/installers/clients/ClientInstaller.d.ts +23 -0
- package/dist/core/installers/clients/ClientInstaller.js +573 -0
- package/dist/core/installers/clients/ExtensionInstaller.d.ts +26 -0
- package/dist/core/installers/clients/ExtensionInstaller.js +149 -0
- package/dist/core/installers/index.d.ts +8 -6
- package/dist/core/installers/index.js +8 -6
- package/dist/core/installers/requirements/BaseInstaller.d.ts +59 -0
- package/dist/core/installers/requirements/BaseInstaller.js +168 -0
- package/dist/core/installers/requirements/CommandInstaller.d.ts +37 -0
- package/dist/core/installers/requirements/CommandInstaller.js +173 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +33 -0
- package/dist/core/installers/requirements/GeneralInstaller.js +86 -0
- package/dist/core/installers/requirements/InstallerFactory.d.ts +54 -0
- package/dist/core/installers/requirements/InstallerFactory.js +97 -0
- package/dist/core/installers/requirements/NpmInstaller.d.ts +26 -0
- package/dist/core/installers/requirements/NpmInstaller.js +128 -0
- package/dist/core/installers/requirements/PipInstaller.d.ts +28 -0
- package/dist/core/installers/requirements/PipInstaller.js +128 -0
- package/{src/core/installers/RequirementInstaller.ts → dist/core/installers/requirements/RequirementInstaller.d.ts} +33 -38
- package/dist/core/installers/requirements/RequirementInstaller.js +3 -0
- package/dist/core/types.d.ts +4 -1
- package/dist/services/ServerService.js +1 -1
- package/dist/utils/clientUtils.d.ts +0 -6
- package/dist/utils/clientUtils.js +3 -2
- package/dist/utils/githubUtils.d.ts +11 -0
- package/dist/utils/githubUtils.js +88 -0
- package/dist/utils/osUtils.d.ts +17 -0
- package/dist/utils/osUtils.js +184 -0
- package/dist/web/public/css/modal.css +97 -3
- package/dist/web/public/index.html +21 -2
- package/dist/web/public/js/modal.js +177 -28
- package/dist/web/public/js/serverCategoryDetails.js +12 -10
- package/dist/web/public/js/serverCategoryList.js +20 -5
- package/dist/web/public/modal.html +27 -13
- package/dist/web/server.js +1 -1
- package/package.json +2 -1
- package/src/cli/commands/install.ts +4 -2
- package/src/cli/commands/list.ts +1 -0
- package/src/cli/index.ts +1 -1
- package/src/core/ConfigurationLoader.ts +251 -0
- package/src/core/ConfigurationProvider.ts +13 -195
- package/src/core/InstallationService.ts +140 -106
- package/src/core/RequirementService.ts +5 -10
- package/src/core/constants.ts +15 -1
- package/src/core/installers/{ClientInstaller.ts → clients/ClientInstaller.ts} +185 -46
- package/src/core/installers/clients/ExtensionInstaller.ts +162 -0
- package/src/core/installers/index.ts +9 -7
- package/src/core/installers/{BaseInstaller.ts → requirements/BaseInstaller.ts} +10 -118
- package/src/core/installers/{CommandInstaller.ts → requirements/CommandInstaller.ts} +7 -3
- package/src/core/installers/{GeneralInstaller.ts → requirements/GeneralInstaller.ts} +6 -2
- package/src/core/installers/{InstallerFactory.ts → requirements/InstallerFactory.ts} +11 -9
- package/src/core/installers/{NpmInstaller.ts → requirements/NpmInstaller.ts} +7 -4
- package/src/core/installers/{PipInstaller.ts → requirements/PipInstaller.ts} +26 -10
- package/src/core/installers/requirements/RequirementInstaller.ts +41 -0
- package/src/core/types.ts +4 -1
- package/src/services/ServerService.ts +1 -1
- package/src/utils/clientUtils.ts +4 -2
- package/src/utils/githubUtils.ts +103 -0
- package/src/utils/osUtils.ts +206 -15
- package/src/web/public/css/modal.css +97 -3
- package/src/web/public/index.html +21 -2
- package/src/web/public/js/modal.js +177 -28
- package/src/web/public/js/serverCategoryDetails.js +12 -10
- package/src/web/public/js/serverCategoryList.js +20 -5
- package/src/web/public/modal.html +27 -13
- package/src/web/server.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { RequirementConfig, RequirementStatus, RegistryConfig } from '
|
|
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 '
|
|
5
|
-
import { extractZipFile } from '
|
|
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 '
|
|
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
|
|
23
|
-
abstract
|
|
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
|
|
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 '
|
|
1
|
+
import { RequirementConfig, RequirementStatus, OSType } from '../../types.js';
|
|
2
2
|
import { BaseInstaller } from './BaseInstaller.js';
|
|
3
|
-
import { getOSType, refreshPathEnv } from '
|
|
4
|
-
import { Logger } from '
|
|
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 '
|
|
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
|
|
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 '
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 '
|
|
2
|
+
import { RequirementConfig, RequirementStatus } from '../../types.js';
|
|
3
3
|
import { BaseInstaller } from './BaseInstaller.js';
|
|
4
|
-
import { compareVersions } from '
|
|
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
|
|
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 '
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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) {
|
package/src/utils/clientUtils.ts
CHANGED
|
@@ -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
|
+
}
|