imcp 0.0.14 → 0.0.16

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 (136) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +1 -0
  2. package/dist/core/ConfigurationProvider.js +15 -0
  3. package/dist/core/InstallationService.js +2 -7
  4. package/dist/core/MCPManager.d.ts +11 -2
  5. package/dist/core/MCPManager.js +24 -1
  6. package/dist/core/RequirementService.js +2 -8
  7. package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
  8. package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
  9. package/dist/core/installers/clients/ClientInstaller.d.ts +16 -9
  10. package/dist/core/installers/clients/ClientInstaller.js +80 -527
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +37 -0
  13. package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
  14. package/dist/core/installers/clients/ClineInstaller.js +124 -0
  15. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
  16. package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
  17. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
  18. package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
  19. package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
  20. package/dist/core/installers/requirements/BaseInstaller.js +5 -116
  21. package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
  22. package/dist/core/installers/requirements/CommandInstaller.js +7 -0
  23. package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
  24. package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
  25. package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
  26. package/dist/core/installers/requirements/NpmInstaller.js +150 -58
  27. package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
  28. package/dist/core/installers/requirements/PipInstaller.js +66 -28
  29. package/dist/core/onboard/FeedOnboardService.d.ts +50 -13
  30. package/dist/core/onboard/FeedOnboardService.js +263 -88
  31. package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
  32. package/dist/core/onboard/OnboardProcessor.js +290 -0
  33. package/dist/core/onboard/OnboardStatus.d.ts +49 -0
  34. package/dist/core/onboard/OnboardStatus.js +10 -0
  35. package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
  36. package/dist/core/onboard/OnboardStatusManager.js +176 -0
  37. package/dist/core/types.d.ts +4 -5
  38. package/dist/core/validators/FeedValidator.d.ts +8 -1
  39. package/dist/core/validators/FeedValidator.js +60 -7
  40. package/dist/core/validators/IServerValidator.d.ts +19 -0
  41. package/dist/core/validators/IServerValidator.js +2 -0
  42. package/dist/core/validators/SSEServerValidator.d.ts +15 -0
  43. package/dist/core/validators/SSEServerValidator.js +39 -0
  44. package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
  45. package/dist/core/validators/ServerValidatorFactory.js +45 -0
  46. package/dist/core/validators/StdioServerValidator.d.ts +46 -0
  47. package/dist/core/validators/StdioServerValidator.js +229 -0
  48. package/dist/services/InstallRequestValidator.d.ts +1 -1
  49. package/dist/services/ServerService.d.ts +9 -6
  50. package/dist/services/ServerService.js +18 -7
  51. package/dist/utils/adoUtils.d.ts +29 -0
  52. package/dist/utils/adoUtils.js +252 -0
  53. package/dist/utils/clientUtils.d.ts +0 -7
  54. package/dist/utils/clientUtils.js +0 -42
  55. package/dist/utils/githubUtils.d.ts +10 -0
  56. package/dist/utils/githubUtils.js +22 -0
  57. package/dist/utils/macroExpressionUtils.d.ts +38 -0
  58. package/dist/utils/macroExpressionUtils.js +116 -0
  59. package/dist/utils/osUtils.d.ts +4 -20
  60. package/dist/utils/osUtils.js +78 -23
  61. package/dist/web/contract/serverContract.d.ts +3 -1
  62. package/dist/web/public/css/notifications.css +48 -17
  63. package/dist/web/public/css/onboard.css +66 -3
  64. package/dist/web/public/index.html +84 -16
  65. package/dist/web/public/js/api.js +3 -6
  66. package/dist/web/public/js/flights/flights.js +127 -0
  67. package/dist/web/public/js/modal/installation.js +5 -5
  68. package/dist/web/public/js/modal/modalSetup.js +3 -2
  69. package/dist/web/public/js/notifications.js +66 -27
  70. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  71. package/dist/web/public/js/onboard/formProcessor.js +810 -255
  72. package/dist/web/public/js/onboard/index.js +328 -85
  73. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  74. package/dist/web/public/js/onboard/state.js +61 -17
  75. package/dist/web/public/js/onboard/templates.js +217 -249
  76. package/dist/web/public/js/onboard/uiHandlers.js +679 -117
  77. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  78. package/dist/web/public/js/serverCategoryList.js +15 -2
  79. package/dist/web/public/onboard.html +191 -45
  80. package/dist/web/public/styles.css +91 -1
  81. package/dist/web/server.d.ts +0 -10
  82. package/dist/web/server.js +131 -22
  83. package/package.json +2 -2
  84. package/src/core/ConfigurationProvider.ts +15 -0
  85. package/src/core/InstallationService.ts +2 -7
  86. package/src/core/MCPManager.ts +26 -1
  87. package/src/core/RequirementService.ts +2 -9
  88. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  89. package/src/core/installers/clients/ClientInstaller.ts +97 -608
  90. package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
  91. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  92. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  93. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  94. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  95. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  96. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  97. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  98. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  99. package/src/core/onboard/FeedOnboardService.ts +346 -0
  100. package/src/core/onboard/OnboardProcessor.ts +305 -0
  101. package/src/core/onboard/OnboardStatus.ts +55 -0
  102. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  103. package/src/core/types.ts +4 -5
  104. package/src/core/validators/FeedValidator.ts +79 -0
  105. package/src/core/validators/IServerValidator.ts +21 -0
  106. package/src/core/validators/SSEServerValidator.ts +43 -0
  107. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  108. package/src/core/validators/StdioServerValidator.ts +259 -0
  109. package/src/services/InstallRequestValidator.ts +1 -1
  110. package/src/services/ServerService.ts +22 -7
  111. package/src/utils/adoUtils.ts +291 -0
  112. package/src/utils/clientUtils.ts +0 -44
  113. package/src/utils/githubUtils.ts +24 -0
  114. package/src/utils/macroExpressionUtils.ts +121 -0
  115. package/src/utils/osUtils.ts +89 -24
  116. package/src/web/contract/serverContract.ts +74 -0
  117. package/src/web/public/css/notifications.css +48 -17
  118. package/src/web/public/css/onboard.css +107 -0
  119. package/src/web/public/index.html +84 -16
  120. package/src/web/public/js/api.js +3 -6
  121. package/src/web/public/js/flights/flights.js +127 -0
  122. package/src/web/public/js/modal/installation.js +5 -5
  123. package/src/web/public/js/modal/modalSetup.js +3 -2
  124. package/src/web/public/js/notifications.js +66 -27
  125. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  126. package/src/web/public/js/onboard/formProcessor.js +864 -0
  127. package/src/web/public/js/onboard/index.js +374 -0
  128. package/src/web/public/js/onboard/publishHandler.js +132 -0
  129. package/src/web/public/js/onboard/state.js +76 -0
  130. package/src/web/public/js/onboard/templates.js +343 -0
  131. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  132. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  133. package/src/web/public/js/serverCategoryList.js +15 -2
  134. package/src/web/public/onboard.html +296 -0
  135. package/src/web/public/styles.css +91 -1
  136. package/src/web/server.ts +167 -58
@@ -1,4 +1,4 @@
1
- import { RequirementConfig, RequirementStatus, OSType } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, OSType, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { getOSType, refreshPathEnv } from '../../../utils/osUtils.js';
4
4
  import { Logger } from '../../../utils/logger.js';
@@ -16,6 +16,14 @@ interface CommandMapping {
16
16
  */
17
17
  export class CommandInstaller extends BaseInstaller {
18
18
 
19
+ /**
20
+ * Constructor for the CommandInstaller
21
+ * @param execPromise Function to execute commands
22
+ */
23
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined> {
24
+ throw new Error('Method not implemented.');
25
+ }
26
+
19
27
  /**
20
28
  * Mapping of command names to their package IDs
21
29
  * This handles special cases where the command name differs from the package ID
@@ -1,12 +1,21 @@
1
- import { RequirementConfig, RequirementStatus } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { handleGitHubRelease } from '../../../utils/githubUtils.js';
4
+ import { handleArtifact } from '../../../utils/adoUtils.js';
4
5
 
5
6
  /**
6
7
  * Installer implementation for general requirements (type 'other')
7
8
  * This installer handles requirements that don't fit into specific package manager categories
8
9
  */
9
10
  export class GeneralInstaller extends BaseInstaller {
11
+
12
+ /**
13
+ * Constructor for the GeneralInstaller
14
+ * @param execPromise Function to execute commands
15
+ */
16
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined> {
17
+ throw new Error('Method not implemented.');
18
+ }
10
19
  /**
11
20
  * Check if this installer can handle the given requirement type
12
21
  * @param requirement The requirement to check
@@ -59,9 +68,7 @@ export class GeneralInstaller extends BaseInstaller {
59
68
  const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
60
69
  installPath = result.resolvedPath;
61
70
  } else if (requirement.registry.artifacts) {
62
- installPath = await this.handleArtifactsRegistry(requirement, requirement.registry.artifacts);
63
- } else if (requirement.registry.local) {
64
- installPath = await this.handleLocalRegistry(requirement, requirement.registry.local);
71
+ await handleArtifact(requirement, requirement.registry.artifacts);
65
72
  } else {
66
73
  throw new Error('Invalid registry configuration');
67
74
  }
@@ -1,8 +1,13 @@
1
- import { stat } from 'fs';
2
- import { RequirementConfig, RequirementStatus } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
3
2
  import { BaseInstaller } from './BaseInstaller.js';
4
3
  import { compareVersions } from '../../../utils/versionUtils.js';
5
- import { handleGitHubRelease } from '../../../utils/githubUtils.js';
4
+ import { handleGitHubRelease, getGitHubLatestVersion } from '../../../utils/githubUtils.js';
5
+ // Assuming getArtifactLatestVersion will be available in adoUtils.ts
6
+ import { handleArtifact as handleAdoArtifact, AdoArtifactResult, getArtifactLatestVersion } from '../../../utils/adoUtils.js';
7
+ import path from 'path';
8
+ import fs from 'fs/promises';
9
+ import { SETTINGS_DIR } from '../../constants.js'; // Corrected path
10
+ import { Logger } from '../../../utils/logger.js';
6
11
  /**
7
12
  * Installer implementation for NPM packages
8
13
  */
@@ -19,105 +24,217 @@ export class NpmInstaller extends BaseInstaller {
19
24
  supportCheckUpdates(): boolean {
20
25
  return true;
21
26
  }
27
+
28
+ /**
29
+ * Get the latest version available for the NPM package.
30
+ * @param requirement The requirement to check.
31
+ * @param options Optional server install options.
32
+ * @returns The latest version string, or undefined if not found or not applicable.
33
+ */
34
+ async getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined> {
35
+ if (requirement.registry) {
36
+ if (requirement.registry.githubRelease) {
37
+ return getGitHubLatestVersion(this.execPromise, requirement.registry.githubRelease.repository);
38
+ } else if (requirement.registry.artifacts) {
39
+ // Assuming getArtifactLatestVersion exists and has a compatible signature
40
+ // This might need adjustment based on the actual implementation of getArtifactLatestVersion
41
+ const targetDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
42
+ return getArtifactLatestVersion(requirement, requirement.registry.artifacts, options, targetDir);
43
+ }
44
+ }
45
+ // Default: get common latest version from npm registry
46
+ const { stdout } = await this.execPromise(`npm view ${requirement.name} version`);
47
+ return stdout.trim();
48
+ }
49
+
22
50
  /**
23
- * Check if the NPM package is already installed
51
+ * Generates a dedicated folder path for a requirement.
52
+ * @param requirement The requirement configuration.
53
+ * @returns The path to the requirement's dedicated folder.
54
+ * @private
55
+ */
56
+ private _getRequirementFolderPath(requirement: RequirementConfig): string {
57
+ return path.join(
58
+ SETTINGS_DIR,
59
+ 'npm_requirements',
60
+ requirement.name,
61
+ requirement.version.includes('latest') ? 'latest' : requirement.version);
62
+ }
63
+
64
+ /**
65
+ * Check if the NPM package is already installed in its dedicated folder.
24
66
  * @param requirement The requirement to check
67
+ * @param options Installation options, may contain folderName.
25
68
  * @returns The status of the requirement
26
69
  */
27
- async checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus> {
70
+ async checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
71
+ const requirementDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
72
+ const requirementName = this._getRequirementName(requirement);
73
+ Logger.debug(`Checking installation for ${requirementName} in ${requirementDir}`);
28
74
  try {
29
- const { stdout } = await this.execPromise(`npm list -g ${requirement.name} --json`);
30
- const pkgInfo = JSON.parse(stdout);
31
- const installedVersion = pkgInfo?.dependencies?.[requirement.name]?.version;
75
+ const installedVersion = await this._getInstalledVersion(requirementName, requirementDir);
32
76
 
77
+ if (installedVersion) {
78
+ return {
79
+ name: requirement.name,
80
+ type: 'npm',
81
+ installed: true,
82
+ version: installedVersion,
83
+ npmPath: requirementDir,
84
+ inProgress: false,
85
+ };
86
+ }
87
+ // If package not found in dependencies or version is missing
33
88
  return {
34
89
  name: requirement.name,
35
90
  type: 'npm',
36
- installed: !!installedVersion,
37
- version: installedVersion,
38
- inProgress: false
91
+ installed: false,
92
+ inProgress: false,
93
+ error: `Package ${requirement.name} not found or version missing in npm list output in ${requirementDir}.`,
39
94
  };
40
95
  } catch (error) {
96
+ // npm list command likely failed (e.g., package not installed, or requirementDir not an npm project)
97
+ Logger.debug(`Error checking installation for ${requirement.name} in ${requirementDir}: ${error instanceof Error ? error.message : String(error)}`);
41
98
  return {
42
99
  name: requirement.name,
43
100
  type: 'npm',
44
101
  installed: false,
102
+ inProgress: false,
45
103
  error: error instanceof Error ? error.message : String(error),
46
- inProgress: false
47
104
  };
48
105
  }
49
106
  }
50
107
 
51
108
  /**
52
- * Install the NPM package
53
- * @param requirement The requirement to install
54
- * @returns The status of the installation
109
+ * Retrieves the installed version of an NPM package from a given directory.
110
+ * @param requirementName The name of the NPM package.
111
+ * @param directory The directory to check for the package.
112
+ * @returns The installed version string, or undefined if not found or an error occurs.
113
+ * @private
55
114
  */
56
- async install(requirement: RequirementConfig): Promise<RequirementStatus> {
115
+ private async _getInstalledVersion(requirementName: string, directory: string): Promise<string | undefined> {
57
116
  try {
58
- const status = await this.checkInstallation(requirement);
59
- if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0) {
60
- return status;
117
+ const command = `npm list ${requirementName} --depth=0 --json --prefix "${directory}"`;
118
+ Logger.debug(`Getting installed version for ${requirementName} in ${directory} with command: ${command}`);
119
+ const { stdout } = await this.execPromise(command);
120
+ const listOutput = JSON.parse(stdout);
121
+
122
+ if (listOutput.dependencies && listOutput.dependencies[requirementName] && listOutput.dependencies[requirementName].version) {
123
+ return listOutput.dependencies[requirementName].version;
61
124
  }
125
+ Logger.debug(`Package ${requirementName} not found in npm list output in ${directory}.`);
126
+ return undefined;
127
+ } catch (error) {
128
+ Logger.debug(`Error getting installed version for ${requirementName} in ${directory}: ${error instanceof Error ? error.message : String(error)}`);
129
+ return undefined; // Return undefined on error to indicate version couldn't be retrieved
130
+ }
131
+ }
62
132
 
63
- let resolvedVersion = requirement.version;
64
133
 
65
- // If no registry is specified, use standard npm installation
66
- if (!requirement.registry) {
67
- const { stderr } = await this.execPromise(`npm install -g ${requirement.name}@${requirement.version}`);
68
- if (stderr && !stderr.includes('added')) {
69
- throw new Error(stderr);
70
- }
134
+ /**
135
+ * Get the name of the requirement, including registry information if applicable.
136
+ * @param requirement The requirement configuration.
137
+ * @returns The formatted requirement name.
138
+ * @private
139
+ */
140
+ private _getRequirementName(requirement: RequirementConfig): string {
141
+ return requirement.registry?.artifacts?.registryName ? `@${requirement.registry.artifacts.registryName}/${requirement.name}` : requirement.name;
142
+ }
143
+
144
+
145
+ /**
146
+ * Installs an NPM package into a dedicated local folder.
147
+ * @param requirement The requirement to install.
148
+ * @param packageSource This can be name@version, path to tgz, or path to folder.
149
+ * @param targetDir Target directory for installation.
150
+ * @returns The installed version of the package.
151
+ */
152
+ private async _installPackage(
153
+ requirement: RequirementConfig,
154
+ packageSource: string,
155
+ targetDir: string
156
+ ): Promise<{ version: string }> {
157
+ Logger.debug(`Installing NPM package from "${packageSource}" into "${targetDir}"`);
158
+ await fs.mkdir(targetDir, { recursive: true });
159
+
160
+ const installCommand = `npm install ${packageSource} --prefix "${targetDir}"`;
161
+ Logger.debug(`Executing install command: ${installCommand}`);
162
+ const requirementName = this._getRequirementName(requirement);
163
+
164
+ try {
165
+ const { stdout: installStdout, stderr: installStderr } = await this.execPromise(installCommand);
166
+ Logger.debug(`NPM install stdout for ${packageSource} in ${targetDir}: ${installStdout}`);
167
+ if (installStderr && !installStderr.toLowerCase().includes('added') && !installStderr.toLowerCase().includes('updated') && !installStderr.toLowerCase().includes('found 0 vulnerabilities')) {
168
+ // Log stderr if it's not just typical success noise
169
+ Logger.log(`NPM install stderr for ${packageSource} in ${targetDir}: ${installStderr}`);
170
+ }
171
+
172
+
173
+ const installedVersion = await this._getInstalledVersion(requirementName, targetDir);
174
+
175
+ if (installedVersion) {
176
+ Logger.log(`Successfully installed and verified ${requirementName}@${installedVersion} into ${targetDir}`);
177
+ return { version: installedVersion };
71
178
  } else {
72
- // Handle different registry types
73
- let packageSource: string;
179
+ throw new Error(`Successfully ran npm install for ${packageSource}, but ${requirement.name} version could not be determined via npm list in ${targetDir}.`);
180
+ }
181
+
182
+ } catch (error) {
183
+ Logger.error(`Failed to install or verify NPM package ${packageSource} into ${targetDir}`, error);
184
+ throw error;
185
+ }
186
+ }
187
+
74
188
 
189
+ /**
190
+ * Install the NPM package.
191
+ * @param requirement The requirement to install.
192
+ * @param options Installation options, including pythonCommand for ADO (though not used by npm) and folderName.
193
+ * @returns The status of the installation.
194
+ */
195
+ async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
196
+ const targetDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
197
+ await fs.mkdir(targetDir, { recursive: true });
198
+
199
+ try {
200
+ const status = await this.checkInstallation(requirement, { settings: { folderName: targetDir } });
201
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
202
+ Logger.log(`${requirement.name}@${status.version} already installed in ${targetDir}.`);
203
+ return status;
204
+ }
205
+
206
+ let resolvedVersion = requirement.version;
207
+ let packageToInstall: string = `${requirement.name}@${requirement.version}`;
208
+
209
+ if (requirement.registry) {
75
210
  if (requirement.registry.githubRelease) {
76
211
  const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
77
- packageSource = result.resolvedPath;
212
+ packageToInstall = result.resolvedPath;
78
213
  resolvedVersion = result.resolvedVersion;
79
214
  } else if (requirement.registry.artifacts) {
80
- // For npm with artifacts, configure npm to use the specified registry URL
81
- const registryUrl = requirement.registry.artifacts.registryUrl;
82
- await this.execPromise(`npm config set registry ${registryUrl}`);
83
-
84
- // Now install the package with the configured registry
85
- const { stderr } = await this.execPromise(`npm install -g ${requirement.name}@${requirement.version}`);
86
- if (stderr && !stderr.includes('added')) {
87
- // Reset the registry to the default npm registry
88
- await this.execPromise('npm config set registry https://registry.npmjs.org/');
89
- throw new Error(stderr);
90
- }
91
-
92
- // Reset the registry to the default npm registry
93
- await this.execPromise('npm config set registry https://registry.npmjs.org/');
94
- return {
95
- name: requirement.name,
96
- type: 'npm',
97
- installed: true,
98
- version: requirement.version,
99
- inProgress: false
100
- };
101
- } else if (requirement.registry.local) {
102
- packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
215
+ const adoResult: AdoArtifactResult = await handleAdoArtifact(
216
+ requirement,
217
+ requirement.registry.artifacts,
218
+ options?.settings?.pythonCommand,
219
+ targetDir
220
+ );
221
+ packageToInstall = `${adoResult.package}@${adoResult.version}`;
222
+ resolvedVersion = adoResult.version;
103
223
  } else {
104
- throw new Error('Invalid registry configuration');
105
- }
106
-
107
- // Install from the package source
108
- const { stderr } = await this.execPromise(`npm install -g "${packageSource}"`);
109
- if (stderr && !stderr.includes('added')) {
110
- const status = await this.checkInstallation(requirement);
111
- if (!status.installed) throw new Error(stderr);
224
+ throw new Error('Invalid registry configuration for npm.');
112
225
  }
113
226
  }
227
+ const finalInstallResult = await this._installPackage(requirement, packageToInstall, targetDir);
228
+ resolvedVersion = finalInstallResult.version;
229
+
114
230
 
115
231
  return {
116
232
  name: requirement.name,
117
233
  type: 'npm',
118
234
  installed: true,
119
235
  version: resolvedVersion,
120
- inProgress: false
236
+ inProgress: false,
237
+ npmPath: targetDir
121
238
  };
122
239
  } catch (error) {
123
240
  return {
@@ -1,6 +1,10 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
- import { handleGitHubRelease } from '../../../utils/githubUtils.js';
3
+ import { handleGitHubRelease, getGitHubLatestVersion } from '../../../utils/githubUtils.js';
4
+ // Assuming getArtifactLatestVersion will be available in adoUtils.ts
5
+ import { handleArtifact as handleAdoArtifact, getArtifactLatestVersion } from '../../../utils/adoUtils.js';
6
+ import { compareVersions } from '../../../utils/versionUtils.js';
7
+ import { Logger } from '../../../utils/logger.js';
4
8
 
5
9
  /**
6
10
  * Installer implementation for Python packages using pip
@@ -14,6 +18,7 @@ export class PipInstaller extends BaseInstaller {
14
18
  const pythonCmd = this.getPythonCommand(options);
15
19
  return `${pythonCmd} -m pip`;
16
20
  }
21
+
17
22
  /**
18
23
  * Check if this installer can handle the given requirement type
19
24
  * @param requirement The requirement to check
@@ -24,12 +29,56 @@ export class PipInstaller extends BaseInstaller {
24
29
  }
25
30
 
26
31
  supportCheckUpdates(): boolean {
27
- /// temporarily disabling update check for pip as not able to get which pip of python is being used
28
32
  return true;
29
33
  }
34
+
35
+ /**
36
+ * Get the latest version available for the pip package.
37
+ * @param requirement The requirement to check.
38
+ * @param options Optional server install options.
39
+ * @returns The latest version string, or undefined if not found or not applicable.
40
+ */
41
+ async getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined> {
42
+ if (requirement.registry) {
43
+ if (requirement.registry.githubRelease) {
44
+ return getGitHubLatestVersion(this.execPromise, requirement.registry.githubRelease.repository);
45
+ } else if (requirement.registry.artifacts) {
46
+ // Assuming getArtifactLatestVersion exists and has a compatible signature
47
+ return getArtifactLatestVersion(requirement, requirement.registry.artifacts, options);
48
+ }
49
+ }
50
+ // Default: get common latest version from pip index
51
+ const pipCmd = this.getPipCommand(options);
52
+ const { stdout } = await this.execPromise(`${pipCmd} index versions ${requirement.name} --pre=0`);
53
+ // Parse output to find the latest version. Example output:
54
+ // mypackage (1.0.0)
55
+ // Available versions: 1.0.0, 0.9.0
56
+ // LATEST: 1.0.0
57
+ // Or for some packages:
58
+ // mypackage
59
+ // VERSIONS: 1.0.0, 0.9.0
60
+ // Latest: 1.0.0
61
+ const latestMatch = stdout.match(/(?:LATEST|Latest):\s*([^\s]+)/);
62
+ if (latestMatch && latestMatch[1]) {
63
+ return latestMatch[1];
64
+ }
65
+ // Fallback if LATEST line is not found, try to get the first version from "Available versions" or "VERSIONS"
66
+ const versionsMatch = stdout.match(/(?:Available versions|VERSIONS):\s*([^\n]+)/);
67
+ if (versionsMatch && versionsMatch[1]) {
68
+ const versions = versionsMatch[1].split(',').map(v => v.trim());
69
+ if (versions.length > 0) {
70
+ // Assuming versions are listed in a somewhat reasonable order,
71
+ // or we might need more sophisticated version sorting here.
72
+ return versions[0];
73
+ }
74
+ }
75
+ return undefined; // Or throw an error if version cannot be determined
76
+ }
77
+
30
78
  /**
31
79
  * Check if the Python package is already installed
32
80
  * @param requirement The requirement to check
81
+ * @param options Optional server install options
33
82
  * @returns The status of the requirement
34
83
  */
35
84
  async checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
@@ -37,18 +86,17 @@ export class PipInstaller extends BaseInstaller {
37
86
  const pipCmd = this.getPipCommand(options);
38
87
  const { stdout, stderr } = await this.execPromise(`${pipCmd} show ${requirement.name}`);
39
88
 
40
- // If we get an output and no error, the package is installed
41
- const installed = stdout.includes(requirement.name);
89
+ const installed = stdout.includes(requirement.name.toLowerCase());
42
90
  const versionMatch = stdout.match(/Version: (.+)/);
43
91
  const installedVersion = versionMatch ? versionMatch[1] : undefined;
44
92
 
45
93
  return {
46
94
  name: requirement.name,
47
95
  type: 'pip',
48
- installed,
96
+ installed: installed,
49
97
  version: installedVersion,
50
98
  inProgress: false,
51
- pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
99
+ pythonEnv: this.getPythonCommand(options)
52
100
  };
53
101
  } catch (error) {
54
102
  return {
@@ -57,7 +105,7 @@ export class PipInstaller extends BaseInstaller {
57
105
  installed: false,
58
106
  error: error instanceof Error ? error.message : String(error),
59
107
  inProgress: false,
60
- pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
108
+ pythonEnv: this.getPythonCommand(options)
61
109
  };
62
110
  }
63
111
  }
@@ -65,68 +113,59 @@ export class PipInstaller extends BaseInstaller {
65
113
  /**
66
114
  * Install the Python package
67
115
  * @param requirement The requirement to install
116
+ * @param options Optional server install options
68
117
  * @returns The status of the installation
69
118
  */
70
119
  async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
71
120
  try {
72
121
  const status = await this.checkInstallation(requirement, options);
73
- if (status.installed) {
122
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
123
+ Logger.log(`${requirement.name}==${status.version} already installed for ${this.getPythonCommand}.`);
74
124
  return status;
75
125
  }
76
126
 
77
127
  const pipCmd = this.getPipCommand(options);
78
128
 
79
- // If no registry is specified, use standard pip installation
80
129
  if (!requirement.registry) {
81
- // Standard pip installation
82
130
  const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
83
131
  if (stderr && stderr.toLowerCase().includes('error')) {
84
132
  throw new Error(stderr);
85
133
  }
86
134
  } else {
87
- // Handle different registry types
88
135
  let packageSource: string;
89
136
 
90
137
  if (requirement.registry.githubRelease) {
91
138
  const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
92
139
  packageSource = result.resolvedPath;
93
-
94
- // Install from the downloaded wheel or tar.gz file
95
140
  const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
96
141
  if (stderr && stderr.toLowerCase().includes('error')) {
97
142
  throw new Error(stderr);
98
143
  }
99
144
  } else if (requirement.registry.artifacts) {
100
- const registryUrl = requirement.registry.artifacts.registryUrl;
145
+ const pythonCmd = this.getPythonCommand(options);
146
+ const adoArtifactResult = await handleAdoArtifact(requirement, requirement.registry.artifacts, pythonCmd);
101
147
 
102
- // Install using the custom index URL
103
148
  const { stderr } = await this.execPromise(
104
- `${pipCmd} install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`
149
+ `${pipCmd} install ${adoArtifactResult.package} --extra-index-url ${adoArtifactResult.registryUrl}`
105
150
  );
106
151
  if (stderr && stderr.toLowerCase().includes('error')) {
107
- throw new Error(stderr);
108
- }
109
- } else if (requirement.registry.local) {
110
- packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
111
-
112
- // Install from the local path
113
- const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
114
- if (stderr && stderr.toLowerCase().includes('error')) {
115
- throw new Error(stderr);
152
+ const checkStatus = await this.checkInstallation(requirement, options);
153
+ if (!checkStatus.installed) {
154
+ throw new Error(`Pip installation failed with: ${stderr}`);
155
+ }
116
156
  }
117
157
  } else {
118
158
  throw new Error('Invalid registry configuration');
119
159
  }
120
160
  }
121
161
 
122
- // Store the pythonEnv in the status for future use
123
162
  return {
124
163
  name: requirement.name,
125
164
  type: 'pip',
126
165
  installed: true,
127
- version: requirement.version,
166
+ version: requirement.version, // This might need to be updated to actual installed version
128
167
  inProgress: false,
129
- pythonEnv: this.getPythonCommand(options) // Store the python env
168
+ pythonEnv: this.getPythonCommand(options)
130
169
  };
131
170
  } catch (error) {
132
171
  return {
@@ -135,7 +174,7 @@ export class PipInstaller extends BaseInstaller {
135
174
  installed: false,
136
175
  error: error instanceof Error ? error.message : String(error),
137
176
  inProgress: false,
138
- pythonEnv: this.getPythonCommand(options) // Store the python env
177
+ pythonEnv: this.getPythonCommand(options)
139
178
  };
140
179
  }
141
180
  }