imcp 0.0.11 → 0.0.13
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/dist/cli/commands/uninstall.js +14 -6
- package/dist/core/ConfigurationProvider.d.ts +3 -1
- package/dist/core/ConfigurationProvider.js +85 -25
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +127 -61
- package/dist/core/MCPManager.d.ts +1 -0
- package/dist/core/MCPManager.js +30 -5
- package/dist/core/RequirementService.d.ts +4 -4
- package/dist/core/RequirementService.js +11 -7
- package/dist/core/ServerSchemaLoader.js +2 -2
- package/dist/core/ServerSchemaProvider.d.ts +1 -1
- package/dist/core/ServerSchemaProvider.js +15 -10
- package/dist/core/constants.d.ts +14 -1
- package/dist/core/constants.js +4 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
- package/dist/core/installers/requirements/PipInstaller.js +10 -5
- package/dist/core/types.d.ts +10 -0
- package/dist/services/ServerService.d.ts +12 -1
- package/dist/services/ServerService.js +39 -9
- package/dist/utils/githubAuth.js +0 -10
- package/dist/utils/githubUtils.d.ts +16 -0
- package/dist/utils/githubUtils.js +55 -39
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/public/css/detailsWidget.css +189 -57
- package/dist/web/public/css/modal.css +42 -0
- package/dist/web/public/css/serverDetails.css +35 -18
- package/dist/web/public/index.html +2 -0
- package/dist/web/public/js/detailsWidget.js +175 -60
- package/dist/web/public/js/modal.js +93 -29
- package/dist/web/public/js/notifications.js +34 -35
- package/dist/web/public/js/serverCategoryDetails.js +182 -120
- package/dist/web/server.js +38 -2
- package/package.json +3 -4
- package/src/cli/commands/uninstall.ts +16 -6
- package/src/core/ConfigurationProvider.ts +102 -25
- package/src/core/InstallationService.ts +176 -62
- package/src/core/MCPManager.ts +36 -5
- package/src/core/RequirementService.ts +12 -8
- package/src/core/ServerSchemaLoader.ts +48 -0
- package/src/core/ServerSchemaProvider.ts +137 -0
- package/src/core/constants.ts +16 -3
- package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
- package/src/core/installers/requirements/PipInstaller.ts +10 -5
- package/src/core/types.ts +11 -1
- package/src/services/ServerService.ts +41 -8
- package/src/utils/githubAuth.ts +14 -27
- package/src/utils/githubUtils.ts +84 -47
- package/src/utils/osUtils.ts +1 -1
- package/src/web/public/css/detailsWidget.css +235 -0
- package/src/web/public/css/modal.css +42 -0
- package/src/web/public/css/serverDetails.css +126 -0
- package/src/web/public/index.html +2 -0
- package/src/web/public/js/detailsWidget.js +264 -0
- package/src/web/public/js/modal.js +93 -29
- package/src/web/public/js/notifications.js +34 -35
- package/src/web/public/js/serverCategoryDetails.js +182 -120
- package/src/web/server.ts +52 -3
|
@@ -42,9 +42,9 @@ export class ServerSchemaProvider {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
async loadSchema(categoryName,
|
|
45
|
+
async loadSchema(categoryName, schemaFileName) {
|
|
46
46
|
try {
|
|
47
|
-
const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName,
|
|
47
|
+
const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, schemaFileName);
|
|
48
48
|
const content = await fs.readFile(schemaPath, 'utf8');
|
|
49
49
|
const schema = JSON.parse(content);
|
|
50
50
|
return {
|
|
@@ -53,10 +53,10 @@ export class ServerSchemaProvider {
|
|
|
53
53
|
}
|
|
54
54
|
catch (error) {
|
|
55
55
|
if (error.code === 'ENOENT') {
|
|
56
|
-
Logger.debug(`No schema file found for
|
|
56
|
+
Logger.debug(`No schema file found for ${schemaFileName} in category ${categoryName}`);
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
|
-
Logger.error(`Error loading schema
|
|
59
|
+
Logger.error(`Error loading schema ${schemaFileName} in category ${categoryName}:`, error);
|
|
60
60
|
throw error;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -71,15 +71,15 @@ export class ServerSchemaProvider {
|
|
|
71
71
|
const serverSchemas = new Map();
|
|
72
72
|
for (const file of serverFiles) {
|
|
73
73
|
if (file.endsWith('.json')) {
|
|
74
|
-
const serverName = path.basename(file, '.json');
|
|
75
74
|
try {
|
|
76
|
-
const schema = await this.loadSchema(categoryDir.name,
|
|
75
|
+
const schema = await this.loadSchema(categoryDir.name, file);
|
|
77
76
|
if (schema) {
|
|
78
|
-
|
|
77
|
+
// Store with the complete file name for direct lookup
|
|
78
|
+
serverSchemas.set(file, schema);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
Logger.error(`Error loading schema for
|
|
82
|
+
Logger.error(`Error loading schema for file ${file} in category ${categoryDir.name}:`, error);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -89,13 +89,18 @@ export class ServerSchemaProvider {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
async getSchema(categoryName,
|
|
92
|
+
async getSchema(categoryName, schemaFileName) {
|
|
93
93
|
return await this.withLock(async () => {
|
|
94
94
|
const categorySchemas = this.schemaMap.get(categoryName);
|
|
95
95
|
if (!categorySchemas) {
|
|
96
|
+
Logger.debug(`No schemas found for category ${categoryName}`);
|
|
96
97
|
return undefined;
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
const schema = categorySchemas.get(schemaFileName);
|
|
100
|
+
if (!schema) {
|
|
101
|
+
Logger.debug(`Schema ${schemaFileName} not found in category ${categoryName}`);
|
|
102
|
+
}
|
|
103
|
+
return schema;
|
|
99
104
|
});
|
|
100
105
|
}
|
|
101
106
|
async reloadSchemas() {
|
package/dist/core/constants.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const GITHUB_REPO: {
|
|
8
8
|
url: string;
|
|
9
|
+
repoName: string;
|
|
9
10
|
feedsPath: string;
|
|
11
|
+
feedAssetsName: string;
|
|
10
12
|
};
|
|
11
13
|
/**
|
|
12
14
|
* Local settings directory path based on OS
|
|
@@ -16,13 +18,24 @@ export declare const SETTINGS_DIR: string;
|
|
|
16
18
|
* Local feeds directory path
|
|
17
19
|
*/
|
|
18
20
|
export declare const LOCAL_FEEDS_DIR: string;
|
|
21
|
+
export declare const LOCAL_FEEDS_SCHEMA_DIR: string;
|
|
19
22
|
/**
|
|
20
23
|
* Supported client configurations.
|
|
21
24
|
* Key: Client name (e.g., 'vscode')
|
|
22
25
|
* Value: Client-specific settings or configuration details.
|
|
23
26
|
* TODO: Define actual client settings structure.
|
|
24
27
|
*/
|
|
25
|
-
export declare const SUPPORTED_CLIENTS: Record<string,
|
|
28
|
+
export declare const SUPPORTED_CLIENTS: Record<string, {
|
|
29
|
+
extension: {
|
|
30
|
+
extensionId: string;
|
|
31
|
+
leastVersion?: string;
|
|
32
|
+
repository?: string;
|
|
33
|
+
assetName?: string;
|
|
34
|
+
private?: boolean;
|
|
35
|
+
};
|
|
36
|
+
codeSettingPath: string;
|
|
37
|
+
codeInsiderSettingPath: string;
|
|
38
|
+
}>;
|
|
26
39
|
/**
|
|
27
40
|
* List of supported client names.
|
|
28
41
|
*/
|
package/dist/core/constants.js
CHANGED
|
@@ -8,7 +8,9 @@ import path from 'path';
|
|
|
8
8
|
*/
|
|
9
9
|
export const GITHUB_REPO = {
|
|
10
10
|
url: 'https://github.com/ai-microsoft/imcp-feed.git',
|
|
11
|
-
|
|
11
|
+
repoName: 'ai-microsoft/imcp-feed',
|
|
12
|
+
feedsPath: 'feeds',
|
|
13
|
+
feedAssetsName: 'imcp-feeds-${latest}.zip',
|
|
12
14
|
};
|
|
13
15
|
/**
|
|
14
16
|
* Local settings directory path based on OS
|
|
@@ -25,6 +27,7 @@ export const SETTINGS_DIR = (() => {
|
|
|
25
27
|
* Local feeds directory path
|
|
26
28
|
*/
|
|
27
29
|
export const LOCAL_FEEDS_DIR = path.join(SETTINGS_DIR, 'feeds');
|
|
30
|
+
export const LOCAL_FEEDS_SCHEMA_DIR = path.join(LOCAL_FEEDS_DIR, 'schemas');
|
|
28
31
|
const CODE_STRORAGE_DIR = (() => {
|
|
29
32
|
switch (process.platform) {
|
|
30
33
|
case 'win32':
|
|
@@ -128,6 +128,9 @@ export class ExtensionInstaller {
|
|
|
128
128
|
else {
|
|
129
129
|
// Install private extension from GitHub release using latest version
|
|
130
130
|
try {
|
|
131
|
+
if (!repository || !assetName) {
|
|
132
|
+
throw new Error(`Missing repository or assetName for private extension ${extensionId}`);
|
|
133
|
+
}
|
|
131
134
|
const { resolvedPath } = await handleGitHubRelease({ name: extensionId, version: 'latest', type: 'extension' }, { repository, assetName });
|
|
132
135
|
success = await this.installPrivateExtension(resolvedPath, isInsiders);
|
|
133
136
|
}
|
|
@@ -21,7 +21,7 @@ export class PipInstaller extends BaseInstaller {
|
|
|
21
21
|
}
|
|
22
22
|
supportCheckUpdates() {
|
|
23
23
|
/// temporarily disabling update check for pip as not able to get which pip of python is being used
|
|
24
|
-
return
|
|
24
|
+
return true;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Check if the Python package is already installed
|
|
@@ -41,7 +41,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
41
41
|
type: 'pip',
|
|
42
42
|
installed,
|
|
43
43
|
version: installedVersion,
|
|
44
|
-
inProgress: false
|
|
44
|
+
inProgress: false,
|
|
45
|
+
pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
catch (error) {
|
|
@@ -50,7 +51,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
50
51
|
type: 'pip',
|
|
51
52
|
installed: false,
|
|
52
53
|
error: error instanceof Error ? error.message : String(error),
|
|
53
|
-
inProgress: false
|
|
54
|
+
inProgress: false,
|
|
55
|
+
pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -106,12 +108,14 @@ export class PipInstaller extends BaseInstaller {
|
|
|
106
108
|
throw new Error('Invalid registry configuration');
|
|
107
109
|
}
|
|
108
110
|
}
|
|
111
|
+
// Store the pythonEnv in the status for future use
|
|
109
112
|
return {
|
|
110
113
|
name: requirement.name,
|
|
111
114
|
type: 'pip',
|
|
112
115
|
installed: true,
|
|
113
116
|
version: requirement.version,
|
|
114
|
-
inProgress: false
|
|
117
|
+
inProgress: false,
|
|
118
|
+
pythonEnv: this.getPythonCommand(options) // Store the python env
|
|
115
119
|
};
|
|
116
120
|
}
|
|
117
121
|
catch (error) {
|
|
@@ -120,7 +124,8 @@ export class PipInstaller extends BaseInstaller {
|
|
|
120
124
|
type: 'pip',
|
|
121
125
|
installed: false,
|
|
122
126
|
error: error instanceof Error ? error.message : String(error),
|
|
123
|
-
inProgress: false
|
|
127
|
+
inProgress: false,
|
|
128
|
+
pythonEnv: this.getPythonCommand(options) // Store the python env
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface RequirementStatus {
|
|
|
16
16
|
};
|
|
17
17
|
lastCheckTime?: string;
|
|
18
18
|
operationStatus?: OperationStatus;
|
|
19
|
+
pythonEnv?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface MCPServerStatus {
|
|
21
22
|
installedStatus: Record<string, OperationStatus>;
|
|
@@ -73,6 +74,7 @@ export interface UpdateRequirementOptions {
|
|
|
73
74
|
}
|
|
74
75
|
export interface ServerUninstallOptions {
|
|
75
76
|
removeData?: boolean;
|
|
77
|
+
targets?: string[];
|
|
76
78
|
}
|
|
77
79
|
export interface EnvVariableConfig {
|
|
78
80
|
Required: boolean;
|
|
@@ -99,6 +101,8 @@ export interface McpConfig {
|
|
|
99
101
|
description: string;
|
|
100
102
|
mode: 'stdio' | 'http';
|
|
101
103
|
dependencies?: DependencyConfig;
|
|
104
|
+
schemas?: string;
|
|
105
|
+
repository?: string;
|
|
102
106
|
installation: InstallationConfig;
|
|
103
107
|
}
|
|
104
108
|
export interface RegistryConfig {
|
|
@@ -127,9 +131,14 @@ export interface FeedConfiguration {
|
|
|
127
131
|
name: string;
|
|
128
132
|
displayName: string;
|
|
129
133
|
description: string;
|
|
134
|
+
repository?: string;
|
|
130
135
|
requirements: RequirementConfig[];
|
|
131
136
|
mcpServers: McpConfig[];
|
|
132
137
|
}
|
|
138
|
+
export interface ClientSettings {
|
|
139
|
+
codeSettingPath: string;
|
|
140
|
+
codeInsiderSettingPath: string;
|
|
141
|
+
}
|
|
133
142
|
export declare enum MCPEvent {
|
|
134
143
|
SERVER_INSTALLED = "server:installed",
|
|
135
144
|
SERVER_UNINSTALLED = "server:uninstalled",
|
|
@@ -143,6 +152,7 @@ export interface MCPEventData {
|
|
|
143
152
|
};
|
|
144
153
|
[MCPEvent.SERVER_UNINSTALLED]: {
|
|
145
154
|
serverName: string;
|
|
155
|
+
targets?: string[];
|
|
146
156
|
};
|
|
147
157
|
[MCPEvent.SERVER_STARTED]: {
|
|
148
158
|
server: MCPServerCategory;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ServerSchema } from '../core/ServerSchemaProvider.js';
|
|
1
2
|
import { MCPServerCategory, ServerInstallOptions, ServerCategoryListOptions, ServerOperationResult, ServerUninstallOptions } from '../core/types.js';
|
|
2
3
|
/**
|
|
3
4
|
* ServerService provides a unified interface for server management operations.
|
|
@@ -18,6 +19,10 @@ export declare class ServerService {
|
|
|
18
19
|
* @private
|
|
19
20
|
*/
|
|
20
21
|
private checkRequirementsForUpdate;
|
|
22
|
+
/**
|
|
23
|
+
* Gets the schema for a specific server in a category
|
|
24
|
+
*/
|
|
25
|
+
getServerSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined>;
|
|
21
26
|
/**
|
|
22
27
|
* Installs a specific mcp tool for a server.
|
|
23
28
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
@@ -27,11 +32,17 @@ export declare class ServerService {
|
|
|
27
32
|
* Installs a specific mcp tool for a server.
|
|
28
33
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
29
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Uninstall MCP server from specified client targets
|
|
37
|
+
* @param category The server category
|
|
38
|
+
* @param serverName The server name to uninstall
|
|
39
|
+
* @param options Uninstall options including target clients and data removal flags
|
|
40
|
+
*/
|
|
30
41
|
uninstallMcpServer(category: string, serverName: string, options?: ServerUninstallOptions): Promise<ServerOperationResult>;
|
|
31
42
|
/**
|
|
32
43
|
* Validates server names
|
|
33
44
|
*/
|
|
34
|
-
validateServerName(category: string,
|
|
45
|
+
validateServerName(category: string, names: string | string[]): Promise<boolean>;
|
|
35
46
|
/**
|
|
36
47
|
* Formats success/error messages for operations
|
|
37
48
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { Logger } from '../utils/logger.js';
|
|
4
|
+
import { getServerSchemaProvider } from '../core/ServerSchemaProvider.js';
|
|
4
5
|
import { mcpManager } from '../core/MCPManager.js';
|
|
5
6
|
import { UPDATE_CHECK_INTERVAL_MS } from '../core/constants.js';
|
|
6
7
|
import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
|
|
@@ -96,6 +97,20 @@ export class ServerService {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Gets the schema for a specific server in a category
|
|
102
|
+
*/
|
|
103
|
+
async getServerSchema(categoryName, serverName) {
|
|
104
|
+
try {
|
|
105
|
+
const provider = await getServerSchemaProvider();
|
|
106
|
+
const serverMcpConfig = await mcpManager.getServerMcpConfig(categoryName, serverName);
|
|
107
|
+
return await provider.getSchema(categoryName, serverMcpConfig?.schemas || `${serverName}.json`);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
Logger.error(`Failed to get schema for server ${serverName} in category ${categoryName}:`, error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
99
114
|
/**
|
|
100
115
|
* Installs a specific mcp tool for a server.
|
|
101
116
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
@@ -117,25 +132,40 @@ export class ServerService {
|
|
|
117
132
|
* Installs a specific mcp tool for a server.
|
|
118
133
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
119
134
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Uninstall MCP server from specified client targets
|
|
137
|
+
* @param category The server category
|
|
138
|
+
* @param serverName The server name to uninstall
|
|
139
|
+
* @param options Uninstall options including target clients and data removal flags
|
|
140
|
+
*/
|
|
141
|
+
async uninstallMcpServer(category, serverName, options = {}) {
|
|
142
|
+
Logger.debug(`Uninstalling MCP server: ${JSON.stringify({ category, serverName, options })}`);
|
|
143
|
+
try {
|
|
144
|
+
const result = await mcpManager.uninstallServer(category, serverName, options);
|
|
145
|
+
Logger.debug(`Uninstallation result: ${JSON.stringify(result)}`);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
Logger.error(`Failed to uninstall MCP server: ${serverName}`, error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
123
152
|
}
|
|
124
153
|
/**
|
|
125
154
|
* Validates server names
|
|
126
155
|
*/
|
|
127
|
-
async validateServerName(category,
|
|
128
|
-
|
|
156
|
+
async validateServerName(category, names) {
|
|
157
|
+
const serverNames = Array.isArray(names) ? names : [names];
|
|
158
|
+
Logger.debug(`Validating server names: ${JSON.stringify({ category, names: serverNames })}`);
|
|
129
159
|
// Check if category exists in feeds
|
|
130
160
|
const feedConfig = await mcpManager.getFeedConfiguration(category);
|
|
131
161
|
if (!feedConfig) {
|
|
132
162
|
Logger.debug(`Validation failed: Category "${category}" not found in feeds`);
|
|
133
163
|
return false;
|
|
134
164
|
}
|
|
135
|
-
// Check if
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
Logger.debug(`Validation failed:
|
|
165
|
+
// Check if all servers exist in the category's mcpServers
|
|
166
|
+
const invalidServers = serverNames.filter(name => !feedConfig.mcpServers.some(server => server.name === name));
|
|
167
|
+
if (invalidServers.length > 0) {
|
|
168
|
+
Logger.debug(`Validation failed: Servers "${invalidServers.join(', ')}" not found in category "${category}"`);
|
|
139
169
|
return false;
|
|
140
170
|
}
|
|
141
171
|
return true;
|
package/dist/utils/githubAuth.js
CHANGED
|
@@ -29,16 +29,6 @@ class GithubAuthError extends Error {
|
|
|
29
29
|
export async function checkGithubAuth() {
|
|
30
30
|
Logger.debug('Starting GitHub authentication check');
|
|
31
31
|
try {
|
|
32
|
-
// Check if git is installed
|
|
33
|
-
if (!await isToolInstalled('git')) {
|
|
34
|
-
Logger.log('Installing required Git...');
|
|
35
|
-
await installCLI('git');
|
|
36
|
-
// Verify git was installed correctly, with retry mechanism
|
|
37
|
-
if (!await isToolInstalled('git')) {
|
|
38
|
-
throw new Error('Failed to install Git. Please install it manually and try again.');
|
|
39
|
-
}
|
|
40
|
-
Logger.debug('Git installed successfully and verified');
|
|
41
|
-
}
|
|
42
32
|
// Check if gh CLI is installed
|
|
43
33
|
if (!await isToolInstalled('gh')) {
|
|
44
34
|
Logger.log('Installing required GitHub CLI...');
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { RegistryConfig, RequirementConfig } from '../core/types.js';
|
|
2
|
+
interface DownloadGithubReleaseResult {
|
|
3
|
+
version: string;
|
|
4
|
+
downloadPath: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Downloads a GitHub release asset
|
|
8
|
+
* @param repo GitHub repository in format owner/repo
|
|
9
|
+
* @param version Version to download, can be "latest"
|
|
10
|
+
* @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
|
|
11
|
+
* @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
|
|
12
|
+
* @param isFolder Whether to treat the downloaded asset as a folder (default: false)
|
|
13
|
+
* @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
|
|
14
|
+
* @returns Object containing version and download path
|
|
15
|
+
*/
|
|
16
|
+
export declare function downloadGithubRelease(repo: string, version: string, assetsName?: string, assetName?: string, isFolder?: boolean, targetDirectory?: string): Promise<DownloadGithubReleaseResult>;
|
|
2
17
|
/**
|
|
3
18
|
* Helper to handle GitHub release downloads
|
|
4
19
|
* @param requirement The requirement configuration
|
|
@@ -9,3 +24,4 @@ export declare function handleGitHubRelease(requirement: RequirementConfig, regi
|
|
|
9
24
|
resolvedVersion: string;
|
|
10
25
|
resolvedPath: string;
|
|
11
26
|
}>;
|
|
27
|
+
export {};
|
|
@@ -3,78 +3,94 @@ import util from 'util';
|
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { extractZipFile } from './clientUtils.js';
|
|
6
|
-
import { Logger } from './logger.js';
|
|
7
6
|
import { SETTINGS_DIR } from '../core/constants.js';
|
|
8
7
|
const execAsync = util.promisify(exec);
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @
|
|
9
|
+
* Downloads a GitHub release asset
|
|
10
|
+
* @param repo GitHub repository in format owner/repo
|
|
11
|
+
* @param version Version to download, can be "latest"
|
|
12
|
+
* @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
|
|
13
|
+
* @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
|
|
14
|
+
* @param isFolder Whether to treat the downloaded asset as a folder (default: false)
|
|
15
|
+
* @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
|
|
16
|
+
* @returns Object containing version and download path
|
|
14
17
|
*/
|
|
15
|
-
export async function
|
|
16
|
-
if (!
|
|
17
|
-
throw new Error('GitHub
|
|
18
|
+
export async function downloadGithubRelease(repo, version, assetsName, assetName, isFolder = false, targetDirectory) {
|
|
19
|
+
if (!repo) {
|
|
20
|
+
throw new Error('GitHub repository is required');
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const { repository, assetsName, assetName } = registry;
|
|
22
|
-
if (!repository) {
|
|
23
|
-
throw new Error('GitHub repository is required for GitHub release downloads');
|
|
22
|
+
if (!assetsName && !assetName) {
|
|
23
|
+
throw new Error('Either assetsName or assetName must be specified');
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const { stdout } = await execAsync(`gh release view --repo ${
|
|
25
|
+
const downloadsDir = targetDirectory || path.join(SETTINGS_DIR, 'downloads');
|
|
26
|
+
await fs.mkdir(downloadsDir, { recursive: true });
|
|
27
|
+
// Get latest version if needed
|
|
28
|
+
const { stdout } = await execAsync(`gh release view --repo ${repo} --json tagName --jq .tagName`);
|
|
29
29
|
const latestTag = stdout.trim();
|
|
30
30
|
let latestVersion = latestTag;
|
|
31
31
|
const tagWithVPrefix = latestVersion.startsWith('v');
|
|
32
32
|
if (tagWithVPrefix)
|
|
33
33
|
latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
|
|
34
|
-
|
|
34
|
+
const resolvedVersion = version.includes("latest") ? latestVersion : version;
|
|
35
|
+
// Resolve asset names
|
|
36
|
+
let resolvedAssetsName = '';
|
|
37
|
+
let resolvedAssetName = '';
|
|
35
38
|
if (assetsName) {
|
|
36
|
-
resolvedAssetsName = assetsName.replace('${latest}',
|
|
39
|
+
resolvedAssetsName = assetsName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
|
|
37
40
|
}
|
|
38
41
|
if (assetName) {
|
|
39
|
-
resolvedAssetName = assetName.replace('${latest}',
|
|
42
|
+
resolvedAssetName = assetName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!pattern) {
|
|
46
|
-
throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
|
|
44
|
+
// Validate zip requirement for isFolder
|
|
45
|
+
const pattern = resolvedAssetsName || resolvedAssetName;
|
|
46
|
+
if (isFolder && (!resolvedAssetsName || !resolvedAssetsName.endsWith('.zip'))) {
|
|
47
|
+
throw new Error('When isFolder is true, assetsName must be provided and end with .zip');
|
|
47
48
|
}
|
|
48
49
|
// Download the release asset
|
|
49
50
|
const downloadPath = path.join(downloadsDir, path.basename(pattern));
|
|
50
51
|
if (!await fileExists(downloadPath)) {
|
|
51
|
-
await execAsync(`gh release download ${tagWithVPrefix ? `v${
|
|
52
|
+
await execAsync(`gh release download ${tagWithVPrefix ? `v${resolvedVersion}` : resolvedVersion} --repo ${repo} --pattern "${pattern}" -O "${downloadPath}"`);
|
|
52
53
|
}
|
|
53
|
-
// Handle zip
|
|
54
|
-
if (downloadPath.endsWith('.zip')) {
|
|
54
|
+
// Handle zip extraction if needed
|
|
55
|
+
if (isFolder && downloadPath.endsWith('.zip')) {
|
|
55
56
|
const extractDir = path.join(downloadsDir, path.basename(pattern, '.zip'));
|
|
56
57
|
await fs.mkdir(extractDir, { recursive: true });
|
|
57
|
-
// Extract the zip file
|
|
58
58
|
await extractZipFile(downloadPath, { dir: extractDir });
|
|
59
|
-
let assetPath = '';
|
|
60
59
|
// If resolvedAssetName is specified, look for it in the extracted directory
|
|
61
60
|
if (resolvedAssetName) {
|
|
62
|
-
assetPath = path.join(extractDir, resolvedAssetName);
|
|
61
|
+
const assetPath = path.join(extractDir, resolvedAssetName);
|
|
63
62
|
try {
|
|
64
63
|
await fs.access(assetPath);
|
|
65
|
-
return {
|
|
64
|
+
return { version: resolvedVersion, downloadPath: assetPath };
|
|
66
65
|
}
|
|
67
66
|
catch (error) {
|
|
68
67
|
throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
return { version: resolvedVersion, downloadPath: extractDir };
|
|
71
|
+
}
|
|
72
|
+
return { version: resolvedVersion, downloadPath };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Helper to handle GitHub release downloads
|
|
76
|
+
* @param requirement The requirement configuration
|
|
77
|
+
* @param registry The GitHub release registry configuration
|
|
78
|
+
* @returns The path to the downloaded file
|
|
79
|
+
*/
|
|
80
|
+
export async function handleGitHubRelease(requirement, registry) {
|
|
81
|
+
if (!registry) {
|
|
82
|
+
throw new Error('GitHub release registry configuration is required');
|
|
83
|
+
}
|
|
84
|
+
const { repository, assetsName, assetName } = registry;
|
|
85
|
+
if (!repository) {
|
|
86
|
+
throw new Error('GitHub repository is required for GitHub release downloads');
|
|
76
87
|
}
|
|
77
|
-
|
|
88
|
+
const isZipAsset = assetsName?.endsWith('.zip') || false;
|
|
89
|
+
const result = await downloadGithubRelease(repository, requirement.version, assetsName, assetName, isZipAsset);
|
|
90
|
+
return {
|
|
91
|
+
resolvedVersion: result.version,
|
|
92
|
+
resolvedPath: result.downloadPath
|
|
93
|
+
};
|
|
78
94
|
}
|
|
79
95
|
async function fileExists(filePath) {
|
|
80
96
|
try {
|
package/dist/utils/osUtils.js
CHANGED
|
@@ -256,7 +256,7 @@ export function getPythonPackagePath(pythonExecutablePath) {
|
|
|
256
256
|
const venvRoot = path.dirname(dir);
|
|
257
257
|
return path.join(venvRoot, 'Lib', 'site-packages');
|
|
258
258
|
}
|
|
259
|
-
else
|
|
259
|
+
else {
|
|
260
260
|
// System Python or Conda on Windows
|
|
261
261
|
return path.join(dir, 'Lib', 'site-packages');
|
|
262
262
|
}
|