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.
Files changed (57) hide show
  1. package/dist/cli/commands/uninstall.js +14 -6
  2. package/dist/core/ConfigurationProvider.d.ts +3 -1
  3. package/dist/core/ConfigurationProvider.js +85 -25
  4. package/dist/core/InstallationService.d.ts +17 -0
  5. package/dist/core/InstallationService.js +127 -61
  6. package/dist/core/MCPManager.d.ts +1 -0
  7. package/dist/core/MCPManager.js +30 -5
  8. package/dist/core/RequirementService.d.ts +4 -4
  9. package/dist/core/RequirementService.js +11 -7
  10. package/dist/core/ServerSchemaLoader.js +2 -2
  11. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  12. package/dist/core/ServerSchemaProvider.js +15 -10
  13. package/dist/core/constants.d.ts +14 -1
  14. package/dist/core/constants.js +4 -1
  15. package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
  16. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  17. package/dist/core/types.d.ts +10 -0
  18. package/dist/services/ServerService.d.ts +12 -1
  19. package/dist/services/ServerService.js +39 -9
  20. package/dist/utils/githubAuth.js +0 -10
  21. package/dist/utils/githubUtils.d.ts +16 -0
  22. package/dist/utils/githubUtils.js +55 -39
  23. package/dist/utils/osUtils.js +1 -1
  24. package/dist/web/public/css/detailsWidget.css +189 -57
  25. package/dist/web/public/css/modal.css +42 -0
  26. package/dist/web/public/css/serverDetails.css +35 -18
  27. package/dist/web/public/index.html +2 -0
  28. package/dist/web/public/js/detailsWidget.js +175 -60
  29. package/dist/web/public/js/modal.js +93 -29
  30. package/dist/web/public/js/notifications.js +34 -35
  31. package/dist/web/public/js/serverCategoryDetails.js +182 -120
  32. package/dist/web/server.js +38 -2
  33. package/package.json +3 -4
  34. package/src/cli/commands/uninstall.ts +16 -6
  35. package/src/core/ConfigurationProvider.ts +102 -25
  36. package/src/core/InstallationService.ts +176 -62
  37. package/src/core/MCPManager.ts +36 -5
  38. package/src/core/RequirementService.ts +12 -8
  39. package/src/core/ServerSchemaLoader.ts +48 -0
  40. package/src/core/ServerSchemaProvider.ts +137 -0
  41. package/src/core/constants.ts +16 -3
  42. package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
  43. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  44. package/src/core/types.ts +11 -1
  45. package/src/services/ServerService.ts +41 -8
  46. package/src/utils/githubAuth.ts +14 -27
  47. package/src/utils/githubUtils.ts +84 -47
  48. package/src/utils/osUtils.ts +1 -1
  49. package/src/web/public/css/detailsWidget.css +235 -0
  50. package/src/web/public/css/modal.css +42 -0
  51. package/src/web/public/css/serverDetails.css +126 -0
  52. package/src/web/public/index.html +2 -0
  53. package/src/web/public/js/detailsWidget.js +264 -0
  54. package/src/web/public/js/modal.js +93 -29
  55. package/src/web/public/js/notifications.js +34 -35
  56. package/src/web/public/js/serverCategoryDetails.js +182 -120
  57. package/src/web/server.ts +52 -3
@@ -42,9 +42,9 @@ export class ServerSchemaProvider {
42
42
  }
43
43
  });
44
44
  }
45
- async loadSchema(categoryName, serverName) {
45
+ async loadSchema(categoryName, schemaFileName) {
46
46
  try {
47
- const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, `${serverName}.json`);
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 server ${serverName} in category ${categoryName}`);
56
+ Logger.debug(`No schema file found for ${schemaFileName} in category ${categoryName}`);
57
57
  return undefined;
58
58
  }
59
- Logger.error(`Error loading schema for server ${serverName} in category ${categoryName}:`, error);
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, serverName);
75
+ const schema = await this.loadSchema(categoryDir.name, file);
77
76
  if (schema) {
78
- serverSchemas.set(serverName, schema);
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 server ${serverName} in category ${categoryDir.name}:`, error);
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, serverName) {
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
- return categorySchemas.get(serverName);
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() {
@@ -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, any>;
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
  */
@@ -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
- feedsPath: 'feeds'
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 false;
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
  }
@@ -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, name: string): Promise<boolean>;
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
- async uninstallMcpServer(category, serverName, options = {} // Reuse ServerInstallOptions for env etc.
121
- ) {
122
- return mcpManager.uninstallServer(category, serverName, options);
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, name) {
128
- Logger.debug(`Validating server name: ${JSON.stringify({ category, name })}`);
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 server exists in the category's mcpServers
136
- const serverExists = feedConfig.mcpServers.some(server => server.name === name);
137
- if (!serverExists) {
138
- Logger.debug(`Validation failed: Server "${name}" not found in category "${category}"`);
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;
@@ -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
- * Helper to handle GitHub release downloads
11
- * @param requirement The requirement configuration
12
- * @param registry The GitHub release registry configuration
13
- * @returns The path to the downloaded file
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 handleGitHubRelease(requirement, registry) {
16
- if (!registry) {
17
- throw new Error('GitHub release registry configuration is required');
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
- const downloadsDir = path.join(SETTINGS_DIR, 'downloads');
20
- await fs.mkdir(downloadsDir, { recursive: true });
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
- let version = requirement.version;
26
- let resolvedAssetName = assetName || '';
27
- let resolvedAssetsName = assetsName || '';
28
- const { stdout } = await execAsync(`gh release view --repo ${repository} --json tagName --jq .tagName`);
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
- version = version.includes("latest") ? latestVersion : version;
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}', version).replace('${version}', version);
39
+ resolvedAssetsName = assetsName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
37
40
  }
38
41
  if (assetName) {
39
- resolvedAssetName = assetName.replace('${latest}', version).replace('${version}', version);
42
+ resolvedAssetName = assetName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
40
43
  }
41
- Logger.debug(`Downloading ${requirement.name} from GitHub release ${repository} version ${version}`);
42
- Logger.debug(`ResolvedAssetsName} ${resolvedAssetName}; ResolvedAsetName} ${resolvedAssetName}`);
43
- const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
44
- Logger.debug(`Resolved pattern: ${pattern}`);
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${version}` : version} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
52
+ await execAsync(`gh release download ${tagWithVPrefix ? `v${resolvedVersion}` : resolvedVersion} --repo ${repo} --pattern "${pattern}" -O "${downloadPath}"`);
52
53
  }
53
- // Handle zip file extraction if the downloaded file is a 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 { resolvedVersion: version, resolvedPath: assetPath };
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
- else {
72
- assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
73
- }
74
- // If no specific asset is required, return the extraction directory
75
- return { resolvedVersion: version, resolvedPath: extractDir };
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
- return { resolvedVersion: version, resolvedPath: downloadPath };
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 {
@@ -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 if (dir.toLowerCase().includes('python')) {
259
+ else {
260
260
  // System Python or Conda on Windows
261
261
  return path.join(dir, 'Lib', 'site-packages');
262
262
  }