imcp 0.0.19 → 0.1.2

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 (183) hide show
  1. package/.roo/rules-code/rules.md +88 -0
  2. package/dist/cli/index.js +1 -45
  3. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  4. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  5. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  6. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  7. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  9. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  10. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  11. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  12. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  13. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  14. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  15. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  16. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  17. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  18. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  19. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  20. package/dist/core/loaders/InstallOperationManager.js +311 -0
  21. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  22. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  23. package/dist/core/metadatas/constants.d.ts +7 -0
  24. package/dist/core/metadatas/constants.js +7 -0
  25. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  26. package/dist/core/metadatas/recordingConstants.js +45 -0
  27. package/dist/core/metadatas/types.d.ts +21 -0
  28. package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
  29. package/dist/core/onboard/FeedOnboardService.js +52 -5
  30. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  31. package/dist/core/onboard/InstallOperationManager.js +144 -0
  32. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  33. package/dist/core/validators/StdioServerValidator.js +4 -3
  34. package/dist/services/InstallationService.d.ts +2 -37
  35. package/dist/services/InstallationService.js +45 -313
  36. package/dist/services/MCPManager.d.ts +1 -1
  37. package/dist/services/MCPManager.js +53 -47
  38. package/dist/services/RequirementService.d.ts +85 -12
  39. package/dist/services/RequirementService.js +488 -49
  40. package/dist/services/ServerService.d.ts +0 -6
  41. package/dist/services/ServerService.js +0 -74
  42. package/dist/services/TelemetryService.d.ts +15 -0
  43. package/dist/services/TelemetryService.js +54 -0
  44. package/dist/utils/adoUtils.js +6 -3
  45. package/dist/utils/githubAuth.js +65 -0
  46. package/dist/utils/logger.d.ts +16 -0
  47. package/dist/utils/logger.js +78 -1
  48. package/dist/utils/macroExpressionUtils.js +3 -25
  49. package/dist/utils/osUtils.d.ts +22 -1
  50. package/dist/utils/osUtils.js +92 -1
  51. package/dist/utils/versionUtils.d.ts +20 -0
  52. package/dist/utils/versionUtils.js +76 -0
  53. package/dist/web/public/css/modal.css +292 -1
  54. package/dist/web/public/css/serverCategoryList.css +120 -0
  55. package/dist/web/public/css/serverDetails.css +14 -1
  56. package/dist/web/public/index.html +126 -21
  57. package/dist/web/public/js/flights/flights.js +1 -1
  58. package/dist/web/public/js/modal/index.js +8 -14
  59. package/dist/web/public/js/modal/installModal.js +3 -4
  60. package/dist/web/public/js/modal/installation.js +122 -137
  61. package/dist/web/public/js/modal/loadingModal.js +155 -25
  62. package/dist/web/public/js/modal/messageQueue.js +45 -101
  63. package/dist/web/public/js/modal/modalSetup.js +125 -43
  64. package/dist/web/public/js/modal/modalUtils.js +0 -12
  65. package/dist/web/public/js/modal.js +23 -10
  66. package/dist/web/public/js/onboard/formProcessor.js +18 -11
  67. package/dist/web/public/js/onboard/publishHandler.js +35 -3
  68. package/dist/web/public/js/onboard/templates.js +5 -1
  69. package/dist/web/public/js/onboard/uiHandlers.js +266 -39
  70. package/dist/web/public/js/onboard/validationHandlers.js +71 -39
  71. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  72. package/dist/web/public/js/serverCategoryList.js +93 -9
  73. package/dist/web/public/js/settings.js +314 -0
  74. package/dist/web/public/onboard.html +2 -2
  75. package/dist/web/public/settings.html +135 -0
  76. package/dist/web/public/styles.css +32 -0
  77. package/dist/web/server.js +93 -1
  78. package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
  79. package/docs/Telemetry.md +136 -0
  80. package/memory-bank/activeContext.md +26 -0
  81. package/memory-bank/decisionLog.md +91 -0
  82. package/memory-bank/productContext.md +41 -0
  83. package/memory-bank/progress.md +35 -0
  84. package/memory-bank/systemPatterns.md +10 -0
  85. package/package.json +2 -1
  86. package/src/cli/index.ts +1 -48
  87. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  88. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  89. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  90. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  91. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  92. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  93. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  94. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  95. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  96. package/src/core/loaders/InstallOperationManager.ts +367 -0
  97. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  98. package/src/core/metadatas/constants.ts +9 -0
  99. package/src/core/metadatas/recordingConstants.ts +62 -0
  100. package/src/core/metadatas/types.ts +23 -0
  101. package/src/core/onboard/FeedOnboardService.ts +59 -5
  102. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  103. package/src/core/validators/StdioServerValidator.ts +4 -3
  104. package/src/services/InstallationService.ts +54 -399
  105. package/src/services/MCPManager.ts +61 -64
  106. package/src/services/RequirementService.ts +564 -67
  107. package/src/services/ServerService.ts +0 -90
  108. package/src/services/TelemetryService.ts +59 -0
  109. package/src/utils/adoUtils.ts +6 -4
  110. package/src/utils/githubAuth.ts +84 -1
  111. package/src/utils/logger.ts +83 -1
  112. package/src/utils/macroExpressionUtils.ts +4 -21
  113. package/src/utils/osUtils.ts +92 -1
  114. package/src/utils/versionUtils.ts +98 -13
  115. package/src/web/public/css/modal.css +292 -1
  116. package/src/web/public/css/serverCategoryList.css +120 -0
  117. package/src/web/public/css/serverDetails.css +14 -1
  118. package/src/web/public/index.html +126 -21
  119. package/src/web/public/js/flights/flights.js +1 -1
  120. package/src/web/public/js/modal/index.js +8 -14
  121. package/src/web/public/js/modal/installModal.js +3 -4
  122. package/src/web/public/js/modal/installation.js +122 -137
  123. package/src/web/public/js/modal/loadingModal.js +155 -25
  124. package/src/web/public/js/modal/modalSetup.js +125 -43
  125. package/src/web/public/js/modal/modalUtils.js +0 -12
  126. package/src/web/public/js/modal.js +23 -10
  127. package/src/web/public/js/onboard/formProcessor.js +18 -11
  128. package/src/web/public/js/onboard/publishHandler.js +35 -3
  129. package/src/web/public/js/onboard/templates.js +5 -1
  130. package/src/web/public/js/onboard/uiHandlers.js +266 -39
  131. package/src/web/public/js/onboard/validationHandlers.js +71 -39
  132. package/src/web/public/js/serverCategoryDetails.js +60 -11
  133. package/src/web/public/js/serverCategoryList.js +93 -9
  134. package/src/web/public/js/settings.js +314 -0
  135. package/src/web/public/onboard.html +2 -2
  136. package/src/web/public/settings.html +135 -0
  137. package/src/web/public/styles.css +32 -0
  138. package/src/web/server.ts +96 -1
  139. package/dist/cli/commands/start.d.ts +0 -2
  140. package/dist/cli/commands/start.js +0 -32
  141. package/dist/cli/commands/sync.d.ts +0 -2
  142. package/dist/cli/commands/sync.js +0 -17
  143. package/dist/core/ConfigurationLoader.d.ts +0 -32
  144. package/dist/core/ConfigurationLoader.js +0 -236
  145. package/dist/core/ConfigurationProvider.d.ts +0 -35
  146. package/dist/core/ConfigurationProvider.js +0 -375
  147. package/dist/core/InstallationService.d.ts +0 -50
  148. package/dist/core/InstallationService.js +0 -350
  149. package/dist/core/MCPManager.d.ts +0 -28
  150. package/dist/core/MCPManager.js +0 -188
  151. package/dist/core/RequirementService.d.ts +0 -40
  152. package/dist/core/RequirementService.js +0 -110
  153. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  154. package/dist/core/ServerSchemaLoader.js +0 -43
  155. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  156. package/dist/core/ServerSchemaProvider.js +0 -120
  157. package/dist/core/constants.d.ts +0 -47
  158. package/dist/core/constants.js +0 -94
  159. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  160. package/dist/core/installers/BaseInstaller.js +0 -253
  161. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  162. package/dist/core/installers/ClientInstaller.js +0 -564
  163. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  164. package/dist/core/installers/CommandInstaller.js +0 -173
  165. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  166. package/dist/core/installers/GeneralInstaller.js +0 -85
  167. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  168. package/dist/core/installers/InstallerFactory.js +0 -97
  169. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  170. package/dist/core/installers/NpmInstaller.js +0 -127
  171. package/dist/core/installers/PipInstaller.d.ts +0 -28
  172. package/dist/core/installers/PipInstaller.js +0 -127
  173. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  174. package/dist/core/installers/RequirementInstaller.js +0 -3
  175. package/dist/core/types.d.ts +0 -166
  176. package/dist/core/types.js +0 -16
  177. package/dist/services/InstallRequestValidator.d.ts +0 -21
  178. package/dist/services/InstallRequestValidator.js +0 -99
  179. package/dist/web/public/js/modal/installHandler.js +0 -227
  180. package/dist/web/public/js/modal/loadingUI.js +0 -74
  181. package/dist/web/public/js/modal/modalUI.js +0 -214
  182. package/dist/web/public/js/modal/version.js +0 -20
  183. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -2,6 +2,8 @@ import { RequirementConfig, RequirementStatus, OSType, ServerInstallOptions } fr
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { getOSType, refreshPathEnv } from '../../../utils/osUtils.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
6
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
5
7
 
6
8
  /**
7
9
  * Mapping of command names to their package IDs on different platforms
@@ -138,8 +140,15 @@ export class CommandInstaller extends BaseInstaller {
138
140
  * @param requirement The requirement to install
139
141
  * @returns The status of the installation
140
142
  */
141
- async install(requirement: RequirementConfig): Promise<RequirementStatus> {
142
- try {
143
+ /**
144
+ * Install the command
145
+ * @param requirement The requirement to install
146
+ * @param options Optional install options
147
+ * @param recorder Optional InstallOperationManager for recording steps
148
+ * @returns The status of the installation
149
+ */
150
+ async install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus> {
151
+ const doInstall = async (): Promise<RequirementStatus> => {
143
152
  const status = await this.checkInstallation(requirement);
144
153
  if (status.installed) {
145
154
  return status;
@@ -150,18 +159,17 @@ export class CommandInstaller extends BaseInstaller {
150
159
  let installCommand: string;
151
160
 
152
161
  if (osType === OSType.Windows) {
153
- // Windows installation using winget
154
162
  installCommand = `winget install --id ${packageId}`;
155
163
  if (requirement.version && requirement.version !== 'latest') {
156
164
  installCommand += ` --version ${requirement.version}`;
157
165
  }
158
166
  } else if (osType === OSType.MacOS) {
159
- // macOS installation using Homebrew
160
167
  installCommand = `brew install ${packageId}`;
161
168
  if (requirement.version && requirement.version !== 'latest') {
162
169
  installCommand += `@${requirement.version}`;
163
170
  }
164
171
  } else {
172
+ if (recorder) await recorder.recordStep('CommandInstaller:UnsupportedOS', 'failed', `Unsupported OS for ${requirement.name}`);
165
173
  throw new Error(`Unsupported operating system for installing ${requirement.name}`);
166
174
  }
167
175
 
@@ -176,7 +184,6 @@ export class CommandInstaller extends BaseInstaller {
176
184
  if (!updatedStatus.installed) {
177
185
  throw new Error(`Failed to install ${requirement.name}`);
178
186
  }
179
-
180
187
  return {
181
188
  name: requirement.name,
182
189
  type: 'command',
@@ -184,14 +191,41 @@ export class CommandInstaller extends BaseInstaller {
184
191
  version: updatedStatus.version || requirement.version,
185
192
  inProgress: false
186
193
  };
187
- } catch (error) {
188
- return {
189
- name: requirement.name,
190
- type: 'command',
191
- installed: false,
192
- error: error instanceof Error ? error.message : String(error),
193
- inProgress: false
194
- };
194
+ };
195
+
196
+ if (recorder) {
197
+ return recorder.recording(doInstall, {
198
+ stepName: RecordingConstants.STEP_COMMAND_INSTALLER_INSTALL,
199
+ inProgressMessage: `Installing command: ${requirement.name}`,
200
+ endMessage: (result) =>
201
+ result.installed
202
+ ? `Install completed for ${requirement.name}`
203
+ : `Install failed for ${requirement.name}`,
204
+ onError: (error) => {
205
+ return {
206
+ result: {
207
+ name: requirement.name,
208
+ type: 'command',
209
+ installed: false,
210
+ error: error instanceof Error ? error.message : String(error),
211
+ inProgress: false,
212
+ },
213
+ message: error instanceof Error ? error.message : String(error),
214
+ };
215
+ },
216
+ });
217
+ } else {
218
+ try {
219
+ return await doInstall();
220
+ } catch (error) {
221
+ return {
222
+ name: requirement.name,
223
+ type: 'command',
224
+ installed: false,
225
+ error: error instanceof Error ? error.message : String(error),
226
+ inProgress: false,
227
+ };
228
+ }
195
229
  }
196
230
  }
197
231
  }
@@ -2,6 +2,8 @@ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../.
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { handleGitHubRelease } from '../../../utils/githubUtils.js';
4
4
  import { handleArtifact } from '../../../utils/adoUtils.js';
5
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
6
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
5
7
 
6
8
  /**
7
9
  * Installer implementation for general requirements (type 'other')
@@ -55,8 +57,17 @@ export class GeneralInstaller extends BaseInstaller {
55
57
  * @param requirement The requirement to install
56
58
  * @returns The status of the installation, including the install path in updateInfo
57
59
  */
58
- async install(requirement: RequirementConfig): Promise<RequirementStatus> {
59
- try {
60
+ /**
61
+ * Install the general requirement
62
+ * For type 'other', this doesn't actually install anything, but downloads
63
+ * or locates the asset and returns the path for the caller to use
64
+ * @param requirement The requirement to install
65
+ * @param options Optional install options
66
+ * @param recorder Optional InstallOperationManager for recording steps
67
+ * @returns The status of the installation, including the install path in updateInfo
68
+ */
69
+ async install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus> {
70
+ const doInstall = async (): Promise<RequirementStatus> => {
60
71
  // For type 'other', a registry must be specified
61
72
  if (!requirement.registry) {
62
73
  throw new Error('Registry must be specified for requirement type "other"');
@@ -82,14 +93,41 @@ export class GeneralInstaller extends BaseInstaller {
82
93
  version: requirement.version,
83
94
  inProgress: false,
84
95
  };
85
- } catch (error) {
86
- return {
87
- name: requirement.name,
88
- type: 'other',
89
- installed: false,
90
- error: error instanceof Error ? error.message : String(error),
91
- inProgress: false
92
- };
96
+ };
97
+
98
+ if (recorder) {
99
+ return recorder.recording(doInstall, {
100
+ stepName: RecordingConstants.STEP_GENERAL_INSTALLER_INSTALL,
101
+ inProgressMessage: `Installing general requirement: ${requirement.name}`,
102
+ endMessage: (result) =>
103
+ result.installed
104
+ ? `Install completed for ${requirement.name}`
105
+ : `Install failed for ${requirement.name}`,
106
+ onError: (error) => {
107
+ return {
108
+ result: {
109
+ name: requirement.name,
110
+ type: 'other',
111
+ installed: false,
112
+ error: error instanceof Error ? error.message : String(error),
113
+ inProgress: false,
114
+ },
115
+ message: error instanceof Error ? error.message : String(error),
116
+ };
117
+ },
118
+ });
119
+ } else {
120
+ try {
121
+ return await doInstall();
122
+ } catch (error) {
123
+ return {
124
+ name: requirement.name,
125
+ type: 'other',
126
+ installed: false,
127
+ error: error instanceof Error ? error.message : String(error),
128
+ inProgress: false,
129
+ };
130
+ }
93
131
  }
94
132
  }
95
133
  }
@@ -6,7 +6,7 @@ import { CommandInstaller } from './CommandInstaller.js';
6
6
  import { GeneralInstaller } from './GeneralInstaller.js';
7
7
  import { exec } from 'child_process';
8
8
  import util from 'util';
9
-
9
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
10
10
  /**
11
11
  * Factory for creating the appropriate installer for a requirement
12
12
  */
@@ -59,10 +59,11 @@ export class InstallerFactory {
59
59
  /**
60
60
  * Install a requirement using the appropriate installer
61
61
  * @param requirement The requirement to install
62
+ * @param recorder Optional InstallOperationManager for recording steps
62
63
  * @param options Installation options including python environment
63
64
  * @returns The installation status
64
65
  */
65
- public async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
66
+ public async install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus> {
66
67
  const installer = this.getInstaller(requirement);
67
68
  if (!installer) {
68
69
  return {
@@ -74,7 +75,7 @@ export class InstallerFactory {
74
75
  };
75
76
  }
76
77
 
77
- return await installer.install(requirement, options);
78
+ return await installer.install(requirement, recorder, options);
78
79
  }
79
80
 
80
81
  /**
@@ -8,6 +8,9 @@ import path from 'path';
8
8
  import fs from 'fs/promises';
9
9
  import { SETTINGS_DIR } from '../../metadatas/constants.js'; // Corrected path
10
10
  import { Logger } from '../../../utils/logger.js';
11
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
12
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
13
+
11
14
  /**
12
15
  * Installer implementation for NPM packages
13
16
  */
@@ -152,7 +155,8 @@ export class NpmInstaller extends BaseInstaller {
152
155
  private async _installPackage(
153
156
  requirement: RequirementConfig,
154
157
  packageSource: string,
155
- targetDir: string
158
+ targetDir: string,
159
+ recorder: InstallOperationManager
156
160
  ): Promise<{ version: string }> {
157
161
  Logger.debug(`Installing NPM package from "${packageSource}" into "${targetDir}"`);
158
162
  await fs.mkdir(targetDir, { recursive: true });
@@ -161,89 +165,107 @@ export class NpmInstaller extends BaseInstaller {
161
165
  Logger.debug(`Executing install command: ${installCommand}`);
162
166
  const requirementName = this._getRequirementName(requirement);
163
167
 
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
- }
168
+ return await recorder.recording(
169
+ async () => {
170
+ const { stdout: installStdout, stderr: installStderr } = await this.execPromise(installCommand);
171
+ Logger.debug(`NPM install stdout for ${packageSource} in ${targetDir}: ${installStdout}`);
172
+ if (installStderr && !installStderr.toLowerCase().includes('added') && !installStderr.toLowerCase().includes('updated') && !installStderr.toLowerCase().includes('found 0 vulnerabilities')) {
173
+ // Log stderr if it's not just typical success noise
174
+ Logger.log(`NPM install stderr for ${packageSource} in ${targetDir}: ${installStderr}`);
175
+ }
171
176
 
172
177
 
173
- const installedVersion = await this._getInstalledVersion(requirementName, targetDir);
178
+ const installedVersion = await this._getInstalledVersion(requirementName, targetDir);
174
179
 
175
- if (installedVersion) {
176
- Logger.log(`Successfully installed and verified ${requirementName}@${installedVersion} into ${targetDir}`);
177
- return { version: installedVersion };
178
- } else {
179
- throw new Error(`Successfully ran npm install for ${packageSource}, but ${requirement.name} version could not be determined via npm list in ${targetDir}.`);
180
+ if (installedVersion) {
181
+ Logger.log(`Successfully installed and verified ${requirementName}@${installedVersion} into ${targetDir}`);
182
+ return { version: installedVersion };
183
+ } else {
184
+ throw new Error(`Successfully ran npm install for ${packageSource}, but ${requirement.name} version could not be determined via npm list in ${targetDir}, stderr: ${installStderr}`);
185
+ }
186
+ },
187
+ {
188
+ stepName: RecordingConstants.STEP_INSTALLATION_COMMAND_EXECUTION,
189
+ inProgressMessage: `Running: ${installCommand}`,
190
+ onError: (error) => {
191
+ Logger.error(`Error during NPM installation: ${error instanceof Error ? error.message : String(error)}`);
192
+ throw error;
193
+ }
180
194
  }
181
-
182
- } catch (error) {
183
- Logger.error(`Failed to install or verify NPM package ${packageSource} into ${targetDir}`, error);
184
- throw error;
185
- }
195
+ )
186
196
  }
187
197
 
188
-
189
198
  /**
190
199
  * Install the NPM package.
191
200
  * @param requirement The requirement to install.
192
- * @param options Installation options, including pythonCommand for ADO (though not used by npm) and folderName.
201
+ * @param recorder Optional InstallOperationManager for recording steps.
202
+ * @param options Installation options.
193
203
  * @returns The status of the installation.
194
204
  */
195
- async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
205
+ async install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus> {
196
206
  const targetDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
197
207
  await fs.mkdir(targetDir, { recursive: true });
208
+ return recorder.recording(
209
+ async (): Promise<RequirementStatus> => {
210
+ const status = await this.checkInstallation(requirement, { settings: { folderName: targetDir } });
211
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
212
+ Logger.log(`${requirement.name}@${status.version} already installed in ${targetDir}.`);
213
+ return status;
214
+ }
198
215
 
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
- }
216
+ let resolvedVersion = requirement.version;
217
+ let packageToInstall: string = `${requirement.name}@${requirement.version}`;
205
218
 
206
- let resolvedVersion = requirement.version;
207
- let packageToInstall: string = `${requirement.name}@${requirement.version}`;
208
-
209
- if (requirement.registry) {
210
- if (requirement.registry.githubRelease) {
211
- const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
212
- packageToInstall = result.resolvedPath;
213
- resolvedVersion = result.resolvedVersion;
214
- } else if (requirement.registry.artifacts) {
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;
223
- } else {
224
- throw new Error('Invalid registry configuration for npm.');
219
+ if (requirement.registry) {
220
+ if (requirement.registry.githubRelease) {
221
+ const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
222
+ packageToInstall = result.resolvedPath;
223
+ resolvedVersion = result.resolvedVersion;
224
+ } else if (requirement.registry.artifacts) {
225
+ const adoResult: AdoArtifactResult = await handleAdoArtifact(
226
+ requirement,
227
+ requirement.registry.artifacts,
228
+ options?.settings?.pythonCommand,
229
+ targetDir
230
+ );
231
+ packageToInstall = `${adoResult.package}@${adoResult.version}`;
232
+ resolvedVersion = adoResult.version;
233
+ } else {
234
+ if (recorder) await recorder.recordStep('NpmInstaller:RegistryConfig', 'failed', 'Invalid registry configuration for npm.');
235
+ throw new Error('Invalid registry configuration for npm.');
236
+ }
225
237
  }
226
- }
227
- const finalInstallResult = await this._installPackage(requirement, packageToInstall, targetDir);
228
- resolvedVersion = finalInstallResult.version;
238
+ const finalInstallResult = await this._installPackage(requirement, packageToInstall, targetDir, recorder);
239
+ resolvedVersion = finalInstallResult.version;
229
240
 
230
-
231
- return {
232
- name: requirement.name,
233
- type: 'npm',
234
- installed: true,
235
- version: resolvedVersion,
236
- inProgress: false,
237
- npmPath: targetDir
238
- };
239
- } catch (error) {
240
- return {
241
- name: requirement.name,
242
- type: 'npm',
243
- installed: false,
244
- error: error instanceof Error ? error.message : String(error),
245
- inProgress: false
246
- };
247
- }
241
+ return {
242
+ name: requirement.name,
243
+ type: 'npm',
244
+ installed: true,
245
+ version: resolvedVersion,
246
+ inProgress: false,
247
+ npmPath: targetDir
248
+ };
249
+ },
250
+ {
251
+ stepName: RecordingConstants.STEP_NPM_INSTALLER_INSTALL,
252
+ inProgressMessage: `Installing npm package: ${requirement.name}`,
253
+ endMessage: (result) =>
254
+ result.installed
255
+ ? `Install completed for ${requirement.name}`
256
+ : `Install failed for ${requirement.name}`,
257
+ onError: (error) => {
258
+ return {
259
+ result: {
260
+ name: requirement.name,
261
+ type: 'npm',
262
+ installed: false,
263
+ error: error instanceof Error ? error.message : String(error),
264
+ inProgress: false
265
+ },
266
+ message: error instanceof Error ? error.message : String(error),
267
+ };
268
+ },
269
+ });
248
270
  }
249
271
  }
@@ -1,11 +1,11 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../metadatas/types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { handleGitHubRelease, getGitHubLatestVersion } from '../../../utils/githubUtils.js';
4
- // Assuming getArtifactLatestVersion will be available in adoUtils.ts
5
4
  import { handleArtifact as handleAdoArtifact, getArtifactLatestVersion } from '../../../utils/adoUtils.js';
6
5
  import { compareVersions } from '../../../utils/versionUtils.js';
7
6
  import { Logger } from '../../../utils/logger.js';
8
-
7
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
8
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
9
9
  /**
10
10
  * Installer implementation for Python packages using pip
11
11
  */
@@ -113,69 +113,95 @@ export class PipInstaller extends BaseInstaller {
113
113
  /**
114
114
  * Install the Python package
115
115
  * @param requirement The requirement to install
116
+ * @param recorder Optional InstallOperationManager for recording steps
116
117
  * @param options Optional server install options
117
118
  * @returns The status of the installation
118
119
  */
119
- async install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus> {
120
- try {
121
- const status = await this.checkInstallation(requirement, options);
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}.`);
124
- return status;
125
- }
126
-
127
- const pipCmd = this.getPipCommand(options);
120
+ async install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus> {
128
121
 
129
- if (!requirement.registry) {
130
- const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
131
- if (stderr && stderr.toLowerCase().includes('error')) {
132
- throw new Error(stderr);
122
+ return await recorder.recording(
123
+ async (): Promise<RequirementStatus> => {
124
+ const status = await this.checkInstallation(requirement, options);
125
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
126
+ Logger.log(`${requirement.name}==${status.version} already installed for ${this.getPythonCommand}.`);
127
+ return status;
133
128
  }
134
- } else {
135
- let packageSource: string;
136
129
 
137
- if (requirement.registry.githubRelease) {
138
- const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
139
- packageSource = result.resolvedPath;
140
- const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
141
- if (stderr && stderr.toLowerCase().includes('error')) {
142
- throw new Error(stderr);
143
- }
144
- } else if (requirement.registry.artifacts) {
145
- const pythonCmd = this.getPythonCommand(options);
146
- const adoArtifactResult = await handleAdoArtifact(requirement, requirement.registry.artifacts, pythonCmd);
130
+ const pipCmd = this.getPipCommand(options);
131
+ let command: string
147
132
 
148
- const { stderr } = await this.execPromise(
149
- `${pipCmd} install ${adoArtifactResult.package} --extra-index-url ${adoArtifactResult.registryUrl}`
150
- );
151
- if (stderr && stderr.toLowerCase().includes('error')) {
152
- const checkStatus = await this.checkInstallation(requirement, options);
153
- if (!checkStatus.installed) {
154
- throw new Error(`Pip installation failed with: ${stderr}`);
155
- }
133
+ if (!requirement.registry) {
134
+ if (requirement.version && !requirement.version.includes('latest')) {
135
+ command = `${pipCmd} install ${requirement.name}==${requirement.version}`;
136
+ } else {
137
+ command = `${pipCmd} install --upgrade ${requirement.name}`;
156
138
  }
157
139
  } else {
158
- throw new Error('Invalid registry configuration');
140
+ let packageSource: string;
141
+ if (requirement.registry.githubRelease) {
142
+ const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
143
+ packageSource = result.resolvedPath;
144
+ command = `${pipCmd} install "${packageSource}"`
145
+ } else if (requirement.registry.artifacts) {
146
+ const pythonCmd = this.getPythonCommand(options);
147
+ const adoArtifactResult = await handleAdoArtifact(requirement, requirement.registry.artifacts, pythonCmd);
148
+
149
+ command = `${pipCmd} install ${adoArtifactResult.package} --extra-index-url ${adoArtifactResult.registryUrl}`;
150
+ } else {
151
+ await recorder.recordStep('PipInstaller:RegistryConfig', 'failed', 'Invalid registry configuration');
152
+ throw new Error('Invalid registry configuration');
153
+ }
159
154
  }
160
- }
155
+ return await recorder.recording(
156
+ async () => {
157
+ const { stderr } = await this.execPromise(command);
158
+ if (stderr && stderr.toLowerCase().includes('error')) {
159
+ Logger.debug(`Pip installation error: ${stderr}`);
161
160
 
162
- return {
163
- name: requirement.name,
164
- type: 'pip',
165
- installed: true,
166
- version: requirement.version, // This might need to be updated to actual installed version
167
- inProgress: false,
168
- pythonEnv: this.getPythonCommand(options)
169
- };
170
- } catch (error) {
171
- return {
172
- name: requirement.name,
173
- type: 'pip',
174
- installed: false,
175
- error: error instanceof Error ? error.message : String(error),
176
- inProgress: false,
177
- pythonEnv: this.getPythonCommand(options)
178
- };
179
- }
161
+ // wait for 5 seconds as python pip would be little delayed
162
+ await new Promise(resolve => setTimeout(resolve, 5000));
163
+ const checkStatus = await this.checkInstallation(requirement, options);
164
+ if (!checkStatus.installed) {
165
+ Logger.error(`Package not found after the command, ${stderr}`);
166
+ throw new Error(`Pip installation failed with: ${stderr}`);
167
+ }
168
+ }
169
+
170
+ return {
171
+ name: requirement.name,
172
+ type: 'pip',
173
+ installed: true,
174
+ version: requirement.version, // This might need to be updated to actual installed version
175
+ inProgress: false,
176
+ pythonEnv: this.getPythonCommand(options)
177
+ };
178
+ },
179
+ {
180
+ stepName: `${RecordingConstants.STEP_INSTALL_COMMAND_PREFIX}: ${requirement.name} : ${requirement.version}`,
181
+ inProgressMessage: `Running: ${command}`,
182
+ endMessage: (result) => result.installed ? `Succeeded: ${command}` : `Failed: ${command}`,
183
+ }
184
+ );
185
+ },
186
+ {
187
+ stepName: RecordingConstants.STEP_PIP_INSTALLER_INSTALL,
188
+ inProgressMessage: `Installing pip package: ${requirement.name}`,
189
+ endMessage: (result) => result.installed
190
+ ? `Install completed for ${requirement.name} with version ${result.version}`
191
+ : `Install failed for ${requirement.name}`,
192
+ onError: (error) => {
193
+ return {
194
+ result: {
195
+ name: requirement.name,
196
+ type: 'pip',
197
+ installed: false,
198
+ error: error instanceof Error ? error.message : String(error),
199
+ inProgress: false,
200
+ pythonEnv: this.getPythonCommand(options)
201
+ },
202
+ message: error instanceof Error ? error.message : String(error),
203
+ };
204
+ },
205
+ });
180
206
  }
181
207
  }
@@ -1,4 +1,5 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../metadatas/types.js';
2
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
2
3
 
3
4
  /**
4
5
  * Interface for requirement installers.
@@ -12,15 +13,15 @@ export interface RequirementInstaller {
12
13
  */
13
14
  canHandle(requirement: RequirementConfig): boolean;
14
15
 
15
-
16
16
  supportCheckUpdates(): boolean;
17
-
18
17
  /**
19
18
  * Install the requirement
20
19
  * @param requirement The requirement to install
20
+ * @param options Optional install options
21
+ * @param recorder Optional InstallOperationManager for recording steps
21
22
  * @returns The status of the installation
22
23
  */
23
- install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
+ install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
24
25
 
25
26
  /**
26
27
  * Check if the requirement is already installed