imcp 0.1.1 → 0.1.3

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 (111) hide show
  1. package/dist/cli/index.js +1 -45
  2. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  3. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  4. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  5. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  6. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  7. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  9. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  10. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  11. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  12. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  13. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  14. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  15. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  16. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  17. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  18. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  19. package/dist/core/loaders/InstallOperationManager.js +311 -0
  20. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  21. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  22. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  23. package/dist/core/metadatas/recordingConstants.js +45 -0
  24. package/dist/core/metadatas/types.d.ts +21 -0
  25. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  26. package/dist/core/onboard/InstallOperationManager.js +144 -0
  27. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  28. package/dist/core/validators/StdioServerValidator.js +4 -3
  29. package/dist/services/InstallationService.d.ts +2 -37
  30. package/dist/services/InstallationService.js +45 -313
  31. package/dist/services/MCPManager.d.ts +1 -1
  32. package/dist/services/MCPManager.js +4 -58
  33. package/dist/services/RequirementService.d.ts +85 -12
  34. package/dist/services/RequirementService.js +488 -49
  35. package/dist/services/ServerService.d.ts +0 -6
  36. package/dist/services/ServerService.js +0 -74
  37. package/dist/utils/adoUtils.js +6 -3
  38. package/dist/utils/logger.js +1 -1
  39. package/dist/utils/macroExpressionUtils.js +3 -25
  40. package/dist/utils/osUtils.d.ts +22 -1
  41. package/dist/utils/osUtils.js +92 -1
  42. package/dist/utils/versionUtils.d.ts +20 -1
  43. package/dist/utils/versionUtils.js +51 -4
  44. package/dist/web/public/css/modal.css +292 -1
  45. package/dist/web/public/css/serverDetails.css +14 -1
  46. package/dist/web/public/index.html +122 -20
  47. package/dist/web/public/js/flights/flights.js +1 -0
  48. package/dist/web/public/js/modal/index.js +8 -14
  49. package/dist/web/public/js/modal/installModal.js +3 -4
  50. package/dist/web/public/js/modal/installation.js +122 -137
  51. package/dist/web/public/js/modal/loadingModal.js +155 -25
  52. package/dist/web/public/js/modal/messageQueue.js +45 -101
  53. package/dist/web/public/js/modal/modalSetup.js +125 -43
  54. package/dist/web/public/js/modal/modalUtils.js +0 -12
  55. package/dist/web/public/js/modal.js +23 -10
  56. package/dist/web/public/js/onboard/publishHandler.js +22 -20
  57. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  58. package/dist/web/public/js/serverCategoryList.js +2 -2
  59. package/dist/web/public/js/settings.js +314 -0
  60. package/dist/web/public/settings.html +135 -0
  61. package/dist/web/public/styles.css +32 -0
  62. package/dist/web/server.js +82 -0
  63. package/memory-bank/activeContext.md +13 -1
  64. package/memory-bank/decisionLog.md +63 -0
  65. package/memory-bank/progress.md +30 -0
  66. package/memory-bank/systemPatterns.md +7 -0
  67. package/package.json +1 -1
  68. package/src/cli/index.ts +1 -48
  69. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  70. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  71. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  72. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  73. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  74. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  75. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  76. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  77. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  78. package/src/core/loaders/InstallOperationManager.ts +367 -0
  79. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  80. package/src/core/metadatas/recordingConstants.ts +62 -0
  81. package/src/core/metadatas/types.ts +23 -0
  82. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  83. package/src/core/validators/StdioServerValidator.ts +4 -3
  84. package/src/services/InstallationService.ts +54 -399
  85. package/src/services/MCPManager.ts +4 -77
  86. package/src/services/RequirementService.ts +564 -67
  87. package/src/services/ServerService.ts +0 -90
  88. package/src/utils/adoUtils.ts +6 -4
  89. package/src/utils/logger.ts +1 -1
  90. package/src/utils/macroExpressionUtils.ts +4 -21
  91. package/src/utils/osUtils.ts +92 -1
  92. package/src/utils/versionUtils.ts +71 -19
  93. package/src/web/public/css/modal.css +292 -1
  94. package/src/web/public/css/serverDetails.css +14 -1
  95. package/src/web/public/index.html +122 -20
  96. package/src/web/public/js/flights/flights.js +1 -1
  97. package/src/web/public/js/modal/index.js +8 -14
  98. package/src/web/public/js/modal/installModal.js +3 -4
  99. package/src/web/public/js/modal/installation.js +122 -137
  100. package/src/web/public/js/modal/loadingModal.js +155 -25
  101. package/src/web/public/js/modal/modalSetup.js +125 -43
  102. package/src/web/public/js/modal/modalUtils.js +0 -12
  103. package/src/web/public/js/modal.js +23 -10
  104. package/src/web/public/js/onboard/publishHandler.js +22 -20
  105. package/src/web/public/js/serverCategoryDetails.js +60 -11
  106. package/src/web/public/js/serverCategoryList.js +5 -5
  107. package/src/web/public/js/settings.js +314 -0
  108. package/src/web/public/settings.html +135 -0
  109. package/src/web/public/styles.css +32 -0
  110. package/src/web/server.ts +85 -0
  111. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -9,6 +9,8 @@ import {
9
9
  } from '../../metadatas/types.js';
10
10
  import { Logger } from '../../../utils/logger.js';
11
11
  import { ClientInstallerFactory } from './ClientInstallerFactory.js';
12
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
13
+ import { stat } from 'fs';
12
14
 
13
15
  /**
14
16
  * Main client installer class that orchestrates client installation process
@@ -26,56 +28,27 @@ export class ClientInstaller {
26
28
  }
27
29
 
28
30
  /**
29
- * Generate a unique operation ID for tracking installations
30
- */
31
- private generateOperationId(): string {
32
- return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
33
- }
34
-
35
- /**
36
- * Check if server requirements are ready
37
- * Waits for requirements to be ready with timeout
31
+ * Install all specified clients
38
32
  */
39
- private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
40
- let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
41
-
42
- if (!requirementsReady) {
43
- const pendingStatus: OperationStatus = {
44
- status: 'pending',
45
- type: 'install',
46
- target: 'server',
47
- message: `Waiting for requirements to be ready for client: ${clientName}`,
48
- operationId
49
- };
50
-
51
- await this.configProvider.updateServerOperationStatus(
52
- this.categoryName,
53
- this.serverName,
54
- clientName,
55
- pendingStatus
56
- );
57
-
58
- // Set up periodic checking with timeout
59
- const startTime = Date.now();
60
- const timeoutMs = 5 * 60 * 1000; // 5 minutes
61
- const intervalMs = 5 * 1000; // 5 seconds
62
-
63
- while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
64
- await new Promise(resolve => setTimeout(resolve, intervalMs));
65
- requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
66
- }
67
- }
33
+ async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
34
+ const initialStatuses: OperationStatus[] = [];
68
35
 
69
- var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
36
+ // Start installation for each client asynchronously
37
+ const installPromises = this.clients.map(async (clientName) => {
38
+ const status = await this.installClient(clientName, options);
39
+ initialStatuses.push(status);
40
+ return status;
41
+ });
70
42
 
71
- // Find first non-empty npmPath from requirements status
72
- const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
73
- if (npmPathRequirement && npmPathRequirement.npmPath) {
74
- options.settings = options.settings || {};
75
- options.settings.npmPath = npmPathRequirement.npmPath;
76
- }
43
+ // Wait for all installations to complete
44
+ await Promise.all(installPromises);
77
45
 
78
- return requirementsReady;
46
+ // Return result
47
+ return {
48
+ success: true,
49
+ message: 'Client installations completed',
50
+ status: initialStatuses
51
+ };
79
52
  }
80
53
 
81
54
  /**
@@ -104,7 +77,41 @@ export class ClientInstaller {
104
77
  operationId
105
78
  };
106
79
 
107
- this.processInstallation(clientName, operationId, options);
80
+ // Async installation process
81
+ this.processInstallation(clientName, operationId, options)
82
+ .then((status) => {
83
+ if (status.status === 'completed' || status.status === 'failed') {
84
+ InstallOperationManager
85
+ .getInstance(this.categoryName, this.serverName)
86
+ .markOverallStatus(status.status)
87
+ }
88
+
89
+ this.configProvider.updateServerOperationStatus(
90
+ this.categoryName,
91
+ this.serverName,
92
+ clientName,
93
+ status
94
+ );
95
+ })
96
+ .catch((error) => {
97
+ this.configProvider.updateServerOperationStatus(
98
+ this.categoryName,
99
+ this.serverName,
100
+ clientName,
101
+ {
102
+ status: 'failed',
103
+ type: 'install',
104
+ target: 'server',
105
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
106
+ operationId,
107
+ error: error instanceof Error ? error.message : String(error)
108
+ }
109
+ );
110
+ Logger.error(`Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`);
111
+ InstallOperationManager
112
+ .getInstance(this.categoryName, this.serverName)
113
+ .markOverallStatus('failed', error)
114
+ });
108
115
 
109
116
  // Update server status with initial client installation status
110
117
  await this.configProvider.updateServerOperationStatus(
@@ -116,107 +123,100 @@ export class ClientInstaller {
116
123
  return initialStatus;
117
124
  }
118
125
 
119
- private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<void> {
120
- try {
121
- // Check requirements
122
- const requirementsReady = await this.checkRequirements(operationId, clientName, options);
123
- if (!requirementsReady) {
124
- const failedStatus: OperationStatus = {
125
- status: 'failed',
126
- type: 'install',
127
- target: 'server',
128
- message: `Requirements not ready for client: ${clientName} after timeout`,
129
- operationId
130
- };
131
-
132
- await this.configProvider.updateServerOperationStatus(
133
- this.categoryName,
134
- this.serverName,
135
- clientName,
136
- failedStatus
137
- );
138
- return;
139
- }
126
+ private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<OperationStatus> {
127
+ const requirementsReady = await this.checkRequirements(operationId, clientName, options);
128
+ if (!requirementsReady) {
129
+ const failedStatus: OperationStatus = {
130
+ status: 'failed',
131
+ type: 'install',
132
+ target: 'server',
133
+ message: `Requirements not ready for client: ${clientName} after timeout`,
134
+ operationId
135
+ };
136
+ return failedStatus;
137
+ }
140
138
 
141
- // Create client-specific installer
142
- const installer = ClientInstallerFactory.getInstaller(clientName);
139
+ // Create client-specific installer
140
+ const installer = ClientInstallerFactory.getInstaller(clientName);
143
141
 
144
- if (!installer) {
145
- throw new Error(`Failed to create installer for client: ${clientName}`);
146
- }
142
+ if (!installer) {
143
+ throw new Error(`Failed to create installer for client: ${clientName}`);
144
+ }
147
145
 
148
- const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
149
- if (!serverConfig) {
150
- throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
151
- }
146
+ const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
147
+ if (!serverConfig) {
148
+ throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
149
+ }
152
150
 
153
- // If we've reached here, requirements are ready - update status to in-progress
154
- const inProgressStatus: OperationStatus = {
155
- status: 'in-progress',
156
- type: 'install',
157
- target: 'server',
158
- message: `Installing client: ${clientName}`,
159
- operationId: operationId
160
- };
151
+ // If we've reached here, requirements are ready - update status to in-progress
152
+ const inProgressStatus: OperationStatus = {
153
+ status: 'in-progress',
154
+ type: 'install',
155
+ target: 'server',
156
+ message: `Installing client: ${clientName}`,
157
+ operationId: operationId
158
+ };
161
159
 
162
- await this.configProvider.updateServerOperationStatus(
163
- this.categoryName,
164
- this.serverName,
165
- clientName,
166
- inProgressStatus
167
- );
168
- // Install client
169
- const status = await installer.install(serverConfig, options);
170
- await this.configProvider.updateServerOperationStatus(
171
- this.categoryName,
172
- this.serverName,
173
- clientName,
174
- status
175
- );
160
+ await this.configProvider.updateServerOperationStatus(
161
+ this.categoryName,
162
+ this.serverName,
163
+ clientName,
164
+ inProgressStatus
165
+ );
166
+ // Install client
167
+ return await installer.install(serverConfig, options, this.categoryName);
168
+ }
176
169
 
177
- if (status.status === 'completed') {
178
- await this.configProvider.reloadClientMCPSettings();
179
- }
180
- } catch (error) {
181
- const errorStatus: OperationStatus = {
182
- status: 'failed',
170
+ /**
171
+ * Generate a unique operation ID for tracking installations
172
+ */
173
+ private generateOperationId(): string {
174
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
175
+ }
176
+
177
+ /**
178
+ * Check if server requirements are ready
179
+ * Waits for requirements to be ready with timeout
180
+ */
181
+ private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
182
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
183
+
184
+ if (!requirementsReady) {
185
+ const pendingStatus: OperationStatus = {
186
+ status: 'pending',
183
187
  type: 'install',
184
188
  target: 'server',
185
- message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
186
- operationId,
187
- error: error instanceof Error ? error.message : String(error)
189
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
190
+ operationId
188
191
  };
189
192
 
190
193
  await this.configProvider.updateServerOperationStatus(
191
194
  this.categoryName,
192
195
  this.serverName,
193
196
  clientName,
194
- errorStatus
197
+ pendingStatus
195
198
  );
196
- }
197
- }
198
199
 
199
- /**
200
- * Install all specified clients
201
- */
202
- async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
203
- const initialStatuses: OperationStatus[] = [];
200
+ // Set up periodic checking with timeout
201
+ const startTime = Date.now();
202
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
203
+ const intervalMs = 5 * 1000; // 5 seconds
204
204
 
205
- // Start installation for each client asynchronously
206
- const installPromises = this.clients.map(async (clientName) => {
207
- const status = await this.installClient(clientName, options);
208
- initialStatuses.push(status);
209
- return status;
210
- });
205
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
206
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
207
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
208
+ }
209
+ }
211
210
 
212
- // Wait for all installations to complete
213
- await Promise.all(installPromises);
211
+ var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
214
212
 
215
- // Return result
216
- return {
217
- success: true,
218
- message: 'Client installations completed',
219
- status: initialStatuses
220
- };
213
+ // Find first non-empty npmPath from requirements status
214
+ const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
215
+ if (npmPathRequirement && npmPathRequirement.npmPath) {
216
+ options.settings = options.settings || {};
217
+ options.settings.npmPath = npmPathRequirement.npmPath;
218
+ }
219
+
220
+ return requirementsReady;
221
221
  }
222
222
  }
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import { SETTINGS_DIR } from '../../metadatas/constants.js';
4
4
  import { RequirementInstaller } from './RequirementInstaller.js';
5
5
  import { Logger } from '../../../utils/logger.js';
6
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
6
7
 
7
8
  /**
8
9
  * Abstract base class with common functionality for all requirement installers
@@ -18,7 +19,14 @@ export abstract class BaseInstaller implements RequirementInstaller {
18
19
 
19
20
  abstract canHandle(requirement: RequirementConfig): boolean;
20
21
  abstract supportCheckUpdates(): boolean;
21
- abstract install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
22
+ /**
23
+ * Install the requirement
24
+ * @param requirement The requirement to install
25
+ * @param options Optional install options
26
+ * @param recorder Optional InstallOperationManager for recording steps
27
+ * @returns The status of the installation
28
+ */
29
+ abstract install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
22
30
  abstract checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
23
31
  /**
24
32
  * Get the latest version available for the requirement.
@@ -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
  /**