imcp 0.0.13 → 0.0.15

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 (157) 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 -8
  10. package/dist/core/installers/clients/ClientInstaller.js +77 -504
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +41 -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 +72 -0
  30. package/dist/core/onboard/FeedOnboardService.js +312 -0
  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 +6 -6
  38. package/dist/core/validators/FeedValidator.d.ts +20 -0
  39. package/dist/core/validators/FeedValidator.js +80 -0
  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 +66 -0
  62. package/dist/web/contract/serverContract.js +2 -0
  63. package/dist/web/public/css/notifications.css +48 -17
  64. package/dist/web/public/css/onboard.css +107 -0
  65. package/dist/web/public/index.html +90 -18
  66. package/dist/web/public/js/api.js +3 -6
  67. package/dist/web/public/js/flights/flights.js +127 -0
  68. package/dist/web/public/js/modal/index.js +58 -0
  69. package/dist/web/public/js/modal/installHandler.js +227 -0
  70. package/dist/web/public/js/modal/installModal.js +163 -0
  71. package/dist/web/public/js/modal/installation.js +281 -0
  72. package/dist/web/public/js/modal/loadingModal.js +52 -0
  73. package/dist/web/public/js/modal/loadingUI.js +74 -0
  74. package/dist/web/public/js/modal/messageQueue.js +112 -0
  75. package/dist/web/public/js/modal/modalSetup.js +513 -0
  76. package/dist/web/public/js/modal/modalUI.js +214 -0
  77. package/dist/web/public/js/modal/modalUtils.js +49 -0
  78. package/dist/web/public/js/modal/version.js +20 -0
  79. package/dist/web/public/js/modal/versionUtils.js +20 -0
  80. package/dist/web/public/js/modal.js +25 -1041
  81. package/dist/web/public/js/notifications.js +66 -27
  82. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  83. package/dist/web/public/js/onboard/formProcessor.js +864 -0
  84. package/dist/web/public/js/onboard/index.js +374 -0
  85. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  86. package/dist/web/public/js/onboard/state.js +76 -0
  87. package/dist/web/public/js/onboard/templates.js +343 -0
  88. package/dist/web/public/js/onboard/uiHandlers.js +758 -0
  89. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  90. package/dist/web/public/js/serverCategoryDetails.js +43 -17
  91. package/dist/web/public/js/serverCategoryList.js +15 -2
  92. package/dist/web/public/onboard.html +296 -0
  93. package/dist/web/public/styles.css +91 -1
  94. package/dist/web/server.d.ts +0 -10
  95. package/dist/web/server.js +131 -22
  96. package/package.json +2 -2
  97. package/src/core/ConfigurationProvider.ts +15 -0
  98. package/src/core/InstallationService.ts +2 -7
  99. package/src/core/MCPManager.ts +26 -1
  100. package/src/core/RequirementService.ts +2 -9
  101. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  102. package/src/core/installers/clients/ClientInstaller.ts +97 -589
  103. package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
  104. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  105. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  106. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  107. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  108. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  109. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  110. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  111. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  112. package/src/core/onboard/FeedOnboardService.ts +346 -0
  113. package/src/core/onboard/OnboardProcessor.ts +305 -0
  114. package/src/core/onboard/OnboardStatus.ts +55 -0
  115. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  116. package/src/core/types.ts +6 -6
  117. package/src/core/validators/FeedValidator.ts +79 -0
  118. package/src/core/validators/IServerValidator.ts +21 -0
  119. package/src/core/validators/SSEServerValidator.ts +43 -0
  120. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  121. package/src/core/validators/StdioServerValidator.ts +259 -0
  122. package/src/services/InstallRequestValidator.ts +1 -1
  123. package/src/services/ServerService.ts +22 -7
  124. package/src/utils/adoUtils.ts +291 -0
  125. package/src/utils/clientUtils.ts +0 -44
  126. package/src/utils/githubUtils.ts +24 -0
  127. package/src/utils/macroExpressionUtils.ts +121 -0
  128. package/src/utils/osUtils.ts +89 -24
  129. package/src/web/contract/serverContract.ts +74 -0
  130. package/src/web/public/css/notifications.css +48 -17
  131. package/src/web/public/css/onboard.css +107 -0
  132. package/src/web/public/index.html +90 -18
  133. package/src/web/public/js/api.js +3 -6
  134. package/src/web/public/js/flights/flights.js +127 -0
  135. package/src/web/public/js/modal/index.js +58 -0
  136. package/src/web/public/js/modal/installModal.js +163 -0
  137. package/src/web/public/js/modal/installation.js +281 -0
  138. package/src/web/public/js/modal/loadingModal.js +52 -0
  139. package/src/web/public/js/modal/messageQueue.js +112 -0
  140. package/src/web/public/js/modal/modalSetup.js +513 -0
  141. package/src/web/public/js/modal/modalUtils.js +49 -0
  142. package/src/web/public/js/modal/versionUtils.js +20 -0
  143. package/src/web/public/js/modal.js +25 -1041
  144. package/src/web/public/js/notifications.js +66 -27
  145. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  146. package/src/web/public/js/onboard/formProcessor.js +864 -0
  147. package/src/web/public/js/onboard/index.js +374 -0
  148. package/src/web/public/js/onboard/publishHandler.js +132 -0
  149. package/src/web/public/js/onboard/state.js +76 -0
  150. package/src/web/public/js/onboard/templates.js +343 -0
  151. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  152. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  153. package/src/web/public/js/serverCategoryDetails.js +43 -17
  154. package/src/web/public/js/serverCategoryList.js +15 -2
  155. package/src/web/public/onboard.html +296 -0
  156. package/src/web/public/styles.css +91 -1
  157. package/src/web/server.ts +167 -58
@@ -0,0 +1,259 @@
1
+ import { McpConfig, RequirementConfig, FeedConfiguration } from "../types.js";
2
+ import { IServerValidator } from "./IServerValidator.js";
3
+ import { Logger } from "../../utils/logger.js";
4
+ import { createInstallerFactory } from "../installers/index.js";
5
+ import { exec, spawn } from 'child_process';
6
+ import util from 'util';
7
+ import { MACRO_EXPRESSIONS, resolveNpmModulePath } from "../../utils/macroExpressionUtils.js";
8
+ import { getSystemPythonPackageDirectory } from "../../utils/osUtils.js";
9
+ import { SETTINGS_DIR } from "../constants.js";
10
+ import path from "path";
11
+
12
+ const execPromise = util.promisify(exec);
13
+
14
+ /**
15
+ * Validates MCP server configurations for stdio mode
16
+ */
17
+ export class StdioServerValidator implements IServerValidator {
18
+ private installerFactory = createInstallerFactory();
19
+
20
+ /**
21
+ * Tests if a command exists and is executable
22
+ * @param command The command to test
23
+ * @returns true if command exists and is executable
24
+ */
25
+ private async isCommandExecutable(command: string): Promise<boolean> {
26
+ try {
27
+ Logger.debug(`Testing if command is executable: ${command}`);
28
+ const testCmd = process.platform === 'win32' ?
29
+ `where ${command}` :
30
+ `command -v ${command}`;
31
+
32
+ await execPromise(testCmd);
33
+ return true;
34
+ } catch (error) {
35
+ Logger.debug(`Command not found: ${command}`);
36
+ return false;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Generates a dedicated folder path for a requirement.
42
+ * @param requirement The requirement configuration.
43
+ * @returns The path to the requirement's dedicated folder.
44
+ * @private
45
+ */
46
+ private _getRequirementFolderPath(requirement: RequirementConfig): string {
47
+ return path.join(
48
+ SETTINGS_DIR,
49
+ 'onboard',
50
+ 'npm_requirements',
51
+ requirement.name,
52
+ requirement.version.includes('latest') ? 'latest' : requirement.version);
53
+ }
54
+
55
+ /**
56
+ * Validates and installs a requirement if needed
57
+ * @param requirement The requirement config to validate and install
58
+ * @returns true if requirement is successfully installed/validated
59
+ */
60
+ private async validateRequirement(requirement: RequirementConfig): Promise<boolean> {
61
+ try {
62
+ Logger.debug(`Validating/installing requirement: ${requirement.name}`);
63
+ const installer = this.installerFactory.getInstaller(requirement);
64
+
65
+ if (!installer) {
66
+ const msg = `No installer found for requirement type: ${requirement.type}`;
67
+ Logger.error(msg);
68
+ throw new Error(msg);
69
+ }
70
+
71
+ const targetDir = this._getRequirementFolderPath(requirement);
72
+ const status = await installer.install(requirement, { settings: { folderName: targetDir } });
73
+ if (!status.installed) {
74
+ const msg = `Failed to install requirement ${requirement.name}: ${status.error || 'Unknown error'}`;
75
+ Logger.error(msg);
76
+ throw new Error(msg);
77
+ }
78
+
79
+ Logger.debug(`Requirement ${requirement.name} is valid and installed`);
80
+ return true;
81
+ } catch (error) {
82
+ const errorMsg = `Error validating/installing requirement ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`;
83
+ Logger.error(errorMsg);
84
+ throw error instanceof Error ? error : new Error(errorMsg);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Tests if a server can be started successfully with proper path resolution
90
+ * @param command The command to start the server
91
+ * @param args The command line arguments
92
+ * @returns Promise<boolean> true if server starts successfully, false otherwise
93
+ *
94
+ * Handles special cases:
95
+ * - For node commands: resolves ${NPMPATH} in arguments using resolveNpmModulePath
96
+ * - For python/python3 commands: resolves ${PYTHON_PACKAGE} in arguments using system Python packages
97
+ */
98
+ private async testServerStartup(command: string, args: string[], requirement?: RequirementConfig): Promise<boolean> {
99
+ try {
100
+ // Log initial command and args
101
+ Logger.debug(`Testing server startup with command: ${command}`);
102
+ Logger.debug(`Original arguments: ${args.join(' ')}`);
103
+
104
+ // Handle path resolution based on command type
105
+ let finalArgs = [...args];
106
+
107
+ if (command === 'node') {
108
+ // Resolve npm module paths in arguments
109
+ Logger.debug('Resolving npm module paths in arguments');
110
+ const npmPath = requirement ? this._getRequirementFolderPath(requirement) : undefined;
111
+ finalArgs = args.map(arg => arg.replace(MACRO_EXPRESSIONS.NPMPATH, resolveNpmModulePath(npmPath)));
112
+ Logger.debug(`Resolved npm arguments: ${finalArgs.join(' ')}`);
113
+ } else if (command === 'python' || command === 'python3') {
114
+ // Resolve Python package paths in arguments
115
+ Logger.debug('Resolving Python package paths in arguments');
116
+ const pythonDir = await getSystemPythonPackageDirectory();
117
+ if (pythonDir) {
118
+ finalArgs = args.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
119
+ Logger.debug(`Resolved Python arguments: ${finalArgs.join(' ')}`);
120
+ } else {
121
+ const msg = 'Could not resolve system Python package directory';
122
+ Logger.error(msg);
123
+ throw new Error(msg);
124
+ }
125
+ }
126
+
127
+ return await new Promise<boolean>((resolve, reject) => {
128
+ Logger.debug(`Starting process with command: ${command} ${finalArgs.join(' ')}`);
129
+
130
+ // Set timeout for server startup test
131
+ const timeout = setTimeout(() => {
132
+ const msg = 'Server startup test timed out after 10 seconds';
133
+ Logger.error(msg);
134
+ serverProcess.kill();
135
+ reject(new Error(msg));
136
+ }, 20000);
137
+
138
+ // Start the server process
139
+ const serverProcess = spawn(command, finalArgs, {
140
+ stdio: ['ignore', 'pipe', 'pipe'],
141
+ shell: true
142
+ });
143
+
144
+ let output = '';
145
+ let errorOutput = '';
146
+
147
+ // Collect stdout
148
+ serverProcess.stdout.on('data', (data: Buffer) => {
149
+ output += data.toString();
150
+ // If we see any output, consider it a success
151
+ clearTimeout(timeout);
152
+ serverProcess.kill();
153
+ resolve(true);
154
+ });
155
+
156
+ // Collect stderr
157
+ serverProcess.stderr.on('data', (data: Buffer) => {
158
+ errorOutput += data.toString();
159
+ });
160
+
161
+ // Handle process exit
162
+ serverProcess.on('exit', (code: number | null) => {
163
+ clearTimeout(timeout);
164
+ if (code !== null && code !== 0) {
165
+ const msg = `Server startup failed with code ${code}: ${errorOutput}`;
166
+ Logger.error(msg);
167
+ reject(new Error(msg));
168
+ }
169
+ });
170
+
171
+ // Handle process error
172
+ serverProcess.on('error', (error: Error) => {
173
+ clearTimeout(timeout);
174
+ const msg = `Server startup error: ${error.message}`;
175
+ Logger.error(msg);
176
+ reject(error);
177
+ });
178
+ });
179
+ } catch (error) {
180
+ const msg = `Failed to test server startup: ${error instanceof Error ? error.message : String(error)}`;
181
+ Logger.error(msg);
182
+ throw error instanceof Error ? error : new Error(msg);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Validates stdio-specific MCP server configuration
188
+ * Checks command, arguments, dependencies and required environment variables
189
+ * @param server The MCP server configuration to validate
190
+ * @param config The feed configuration containing shared requirements
191
+ * @returns true if valid, throws error if invalid
192
+ */
193
+ public async validateServer(server: McpConfig, config: FeedConfiguration): Promise<boolean> {
194
+ try {
195
+ Logger.debug(`Validating stdio server configuration: ${server.name}`);
196
+
197
+ // Check required installation command
198
+ if (!server.installation?.command) {
199
+ throw new Error('Server command is required in installation configuration');
200
+ }
201
+
202
+ // Validate server mode
203
+ if (server.mode !== 'stdio') {
204
+ throw new Error(`Invalid server mode for stdio validator: ${server.mode}`);
205
+ }
206
+
207
+ // Parse command and arguments
208
+ const fullCommand = server.installation.command;
209
+ const [baseCommand, ...defaultArgs] = fullCommand.split(' ');
210
+ const args = [...defaultArgs, ...(server.installation.args || [])];
211
+
212
+ // Validate command exists and is executable
213
+ const isExecutable = await this.isCommandExecutable(baseCommand);
214
+ if (!isExecutable) {
215
+ throw new Error(`Command not found or not executable: ${baseCommand}`);
216
+ }
217
+
218
+ // Validate required environment variables if specified
219
+ const envVars = server.installation.env;
220
+ if (envVars) {
221
+ for (const [name, varConfig] of Object.entries(envVars)) {
222
+ if (varConfig.Required && !varConfig.Default && !process.env[name]) {
223
+ throw new Error(`Required environment variable not set: ${name}`);
224
+ }
225
+ }
226
+ }
227
+
228
+ // Validate dependencies if specified
229
+ if (server.dependencies?.requirements) {
230
+ Logger.debug(`Validating ${server.dependencies.requirements.length} requirements`);
231
+
232
+ for (const req of server.dependencies.requirements) {
233
+ const reqConfig: RequirementConfig = config.requirements?.find(r => r.name === req.name) || {
234
+ name: req.name,
235
+ version: req.version,
236
+ type: 'npm' // Default to npm if not specified
237
+ };
238
+
239
+ const isValid = await this.validateRequirement(reqConfig);
240
+ if (!isValid) {
241
+ throw new Error(`Dependency validation failed for: ${req.name}`);
242
+ }
243
+ }
244
+ }
245
+ // Test server startup
246
+ const serverStarted = await this.testServerStartup(baseCommand, args, config.requirements?.find(r => r.type === 'npm'));
247
+ if (!serverStarted) {
248
+ throw new Error(`Failed to start server with command: ${fullCommand} ${args.join(' ')}`);
249
+ }
250
+
251
+ Logger.debug(`Stdio server validation successful: ${server.name}`);
252
+ return true;
253
+ } catch (error) {
254
+ const errorMsg = `Server validation failed: ${error instanceof Error ? error.message : String(error)}`;
255
+ Logger.error(errorMsg);
256
+ throw new Error(errorMsg);
257
+ }
258
+ }
259
+ }
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { FeedConfiguration, EnvVariableConfig, ServerInstallOptions } from '../core/types.js';
4
- import { InstallServersRequestBody } from '../web/server.js'; // Assuming InstallRequestBody is defined here
4
+ import { InstallServersRequestBody } from '../web/contract/serverContract.js'; // Assuming InstallRequestBody is defined here
5
5
  import { SUPPORTED_CLIENT_NAMES } from '../core/constants.js';
6
6
 
7
7
  export class InstallRequestValidator {
@@ -7,9 +7,12 @@ import {
7
7
  ServerInstallOptions,
8
8
  ServerCategoryListOptions,
9
9
  ServerOperationResult,
10
- ServerUninstallOptions
10
+ ServerUninstallOptions,
11
+ FeedConfiguration,
12
+ OperationStatus as CoreOperationStatus
11
13
  } from '../core/types.js';
12
14
  import { mcpManager } from '../core/MCPManager.js';
15
+ import { OperationStatus } from '../core/onboard/OnboardStatus.js';
13
16
  import { UPDATE_CHECK_INTERVAL_MS } from '../core/constants.js';
14
17
  import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
15
18
 
@@ -140,12 +143,11 @@ export class ServerService {
140
143
 
141
144
  /**
142
145
  * Installs a specific mcp tool for a server.
143
- * TODO: This might require enhancing MCPManager to handle category-specific installs.
144
146
  */
145
147
  async installMcpServer(
146
148
  category: string,
147
149
  serverName: string,
148
- options: ServerInstallOptions = {} // Reuse ServerInstallOptions for env etc.
150
+ options: ServerInstallOptions = {}
149
151
  ): Promise<ServerOperationResult> {
150
152
  Logger.debug(`Installing MCP server: ${JSON.stringify({ category, serverName, options })}`);
151
153
  try {
@@ -158,10 +160,6 @@ export class ServerService {
158
160
  }
159
161
  }
160
162
 
161
- /**
162
- * Installs a specific mcp tool for a server.
163
- * TODO: This might require enhancing MCPManager to handle category-specific installs.
164
- */
165
163
  /**
166
164
  * Uninstall MCP server from specified client targets
167
165
  * @param category The server category
@@ -230,6 +228,23 @@ export class ServerService {
230
228
  async syncFeeds(): Promise<void> {
231
229
  return mcpManager.syncFeeds();
232
230
  }
231
+
232
+ /**
233
+ * Onboards a new feed configuration
234
+ * @param config Feed configuration to onboard
235
+ */
236
+ async onboardFeed(config: FeedConfiguration): Promise<OperationStatus & { feedConfiguration?: FeedConfiguration }> {
237
+ try {
238
+ const result = await mcpManager.onboardFeed(config);
239
+ // Log based on the operation status, e.g., if it's pending or succeeded.
240
+ // The actual "success" of onboarding is a multi-step process (PR creation, merge).
241
+ Logger.log(`Feed onboarding process for '${config.name}' initiated with status: ${result.status}, ID: ${result.onboardingId}`);
242
+ return result;
243
+ } catch (error) {
244
+ Logger.error(`Failed to initiate feed onboarding for '${config.name}':`, error);
245
+ throw error; // Or return a specific error status object
246
+ }
247
+ }
233
248
  }
234
249
 
235
250
  // Export a singleton instance
@@ -0,0 +1,291 @@
1
+ import { RequirementConfig, RegistryConfig } from '../core/types.js';
2
+ import { SETTINGS_DIR } from '../core/constants.js';
3
+ import { Logger } from './logger.js';
4
+ import { exec, execSync } from 'child_process';
5
+ import util from 'util';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { isToolInstalled } from './osUtils.js'; // Assuming this can be adapted or a similar check is fine
9
+
10
+ const execAsync = util.promisify(exec);
11
+
12
+ export interface AdoArtifactResult {
13
+ type: 'npm' | 'pip';
14
+ package: string;
15
+ registryUrl: string;
16
+ folderPath?: string; // Only for npm
17
+ version: string;
18
+ }
19
+
20
+ /**
21
+ * Sets up the NPM credential provider.
22
+ * Installs @microsoft/artifacts-npm-credprovider if not already installed.
23
+ * @private
24
+ */
25
+ async function _setupNpmCredentialProvider(): Promise<void> {
26
+ Logger.debug('Setting up NPM credential provider...');
27
+ const credProviderCommand = 'artifacts-npm-credprovider';
28
+
29
+ // Check if artifacts-npm-credprovider is installed by trying to get its version or a known command.
30
+ // A more robust check might involve checking a specific version or path.
31
+ let installed = false;
32
+ try {
33
+ // Attempt to get help output as a basic check for presence
34
+ const { stdout, stderr } = await execAsync(`${credProviderCommand} --help`);
35
+ Logger.debug(`${credProviderCommand} --help stdout: ${stdout}`);
36
+ if (stderr) Logger.debug(`${credProviderCommand} --help stderr: ${stderr}`);
37
+ installed = true;
38
+ Logger.debug(`${credProviderCommand} appears to be installed.`);
39
+ } catch (e) {
40
+ Logger.debug(`${credProviderCommand} not found or --help failed, attempting installation. Error: ${e}`);
41
+ }
42
+
43
+ if (!installed) {
44
+ Logger.log(`Installing @microsoft/artifacts-npm-credprovider...`);
45
+ try {
46
+ const installCommand = `npm install -g @microsoft/artifacts-npm-credprovider --registry https://pkgs.dev.azure.com/mseng/226da8e7-b1c0-4167-9678-53f461e07706/_packaging/AzureArtifacts/npm/registry/`;
47
+ const { stdout: installStdout, stderr: installStderr } = await execAsync(installCommand);
48
+ Logger.debug(`@microsoft/artifacts-npm-credprovider install stdout: ${installStdout}`);
49
+ if (installStderr) Logger.debug(`@microsoft/artifacts-npm-credprovider install stderr: ${installStderr}`);
50
+ Logger.log('@microsoft/artifacts-npm-credprovider installed successfully.');
51
+ } catch (error) {
52
+ Logger.error('Failed to install @microsoft/artifacts-npm-credprovider', error);
53
+ throw new Error(`Failed to install @microsoft/artifacts-npm-credprovider: ${error instanceof Error ? error.message : String(error)}`);
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Installs pip requirements: keyring and artifacts-keyring.
60
+ * @param pythonCommand The python command to use (e.g., 'python' or 'python3').
61
+ * @private
62
+ */
63
+ async function _installPipRequirements(pythonCommand: string): Promise<void> {
64
+ Logger.debug(`Installing pip requirements: keyring, artifacts-keyring using ${pythonCommand}`);
65
+ try {
66
+ const command = `${pythonCommand} -m pip install keyring artifacts-keyring`;
67
+ const { stdout, stderr } = await execAsync(command);
68
+ Logger.debug(`pip install keyring artifacts-keyring stdout: ${stdout}`);
69
+ if (stderr) Logger.debug(`pip install keyring artifacts-keyring stderr: ${stderr}`);
70
+ Logger.log('Successfully installed keyring and artifacts-keyring.');
71
+ } catch (error) {
72
+ Logger.error('Failed to install pip requirements (keyring, artifacts-keyring)', error);
73
+ throw new Error(`Failed to install pip requirements: ${error instanceof Error ? error.message : String(error)}`);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Configures NPM authentication for Azure DevOps Artifacts in a specified directory.
79
+ * This includes setting up the credential provider, creating an .npmrc file,
80
+ * and running the credential provider.
81
+ * @param execPromise The promise-based exec function.
82
+ * @param registry The Azure DevOps artifacts registry configuration.
83
+ * @param directoryPath The directory where .npmrc will be created and commands will be executed.
84
+ * @private
85
+ */
86
+ async function _configureNpmAdoAuthInDirectory(
87
+ execPromise: (command: string, options?: { cwd?: string; env?: NodeJS.ProcessEnv }) => Promise<{ stdout: string; stderr: string }>,
88
+ registry: RegistryConfig['artifacts'],
89
+ directoryPath: string
90
+ ): Promise<void> {
91
+ if (!registry || !registry.registryUrl || !registry.registryName) {
92
+ throw new Error('Registry name and URL are required for NPM ADO auth configuration.');
93
+ }
94
+
95
+ await _setupNpmCredentialProvider();
96
+
97
+ const npmrcContent = `@${registry.registryName}:registry=${registry.registryUrl}\nalways-auth=true`;
98
+ const npmrcPath = path.join(directoryPath, '.npmrc');
99
+ await fs.writeFile(npmrcPath, npmrcContent.trim());
100
+ Logger.debug(`Created/Updated .npmrc file at: ${npmrcPath}`);
101
+
102
+ const env = { ...process.env, NUGET_CREDENTIALPROVIDER_VSTS_TOKENTYPE: 'SelfDescribing' };
103
+ Logger.debug(`Running artifacts-npm-credprovider in ${directoryPath}`);
104
+ const { stdout: credProviderStdout, stderr: credProviderStderr } = await execPromise('artifacts-npm-credprovider', { cwd: directoryPath, env });
105
+ Logger.debug(`artifacts-npm-credprovider stdout: ${credProviderStdout}`);
106
+ if (credProviderStderr) Logger.debug(`artifacts-npm-credprovider stderr: ${credProviderStderr}`);
107
+ Logger.log(`Successfully ran artifacts-npm-credprovider in ${directoryPath}`);
108
+ }
109
+
110
+ /**
111
+ * Handles fetching and setting up an Azure DevOps artifact.
112
+ * @param requirement The requirement configuration.
113
+ * @param registry The Azure DevOps artifacts registry configuration.
114
+ * @param pythonCommand Optional Python command (e.g., 'python', 'python3') for pip operations.
115
+ * @param targetDir Optional target directory for npm artifact setup. If not provided, a default will be used.
116
+ * @returns A promise that resolves with the artifact details.
117
+ */
118
+ export async function handleArtifact(
119
+ requirement: RequirementConfig,
120
+ registry: RegistryConfig['artifacts'],
121
+ pythonCommand: string = 'python', // Default to 'python' if not provided
122
+ targetDir?: string // Optional target directory for npm
123
+ ): Promise<AdoArtifactResult> {
124
+ if (!registry) {
125
+ throw new Error('Azure DevOps artifacts registry configuration is required.');
126
+ }
127
+ if (!registry.registryName || !registry.registryUrl) {
128
+ throw new Error('Registry name and URL are required for Azure DevOps artifacts.');
129
+ }
130
+
131
+ Logger.debug(`Handling ADO artifact for requirement: ${requirement.name}, type: ${requirement.type}`);
132
+
133
+ if (requirement.type === 'npm') {
134
+ const requirementDir = targetDir || path.join(SETTINGS_DIR, 'requirements', requirement.name, requirement.version);
135
+ await fs.mkdir(requirementDir, { recursive: true });
136
+ Logger.debug(`Ensured directory for npm requirement: ${requirementDir}`);
137
+
138
+ try {
139
+ await _configureNpmAdoAuthInDirectory(execAsync, registry, requirementDir);
140
+ Logger.log(`Successfully configured ADO NPM auth for ${requirement.name} in ${requirementDir}`);
141
+
142
+ const version = requirement.version.includes('latest') ? 'latest' : requirement.version;
143
+ return {
144
+ folderPath: requirementDir,
145
+ type: 'npm',
146
+ package: `@${registry.registryName}/${requirement.name}`,
147
+ registryUrl: registry.registryUrl,
148
+ version: version
149
+ };
150
+ } catch (error) {
151
+ Logger.error(`Failed to configure ADO NPM auth for ${requirement.name} in ${requirementDir}`, error);
152
+ throw new Error(`ADO NPM auth configuration failed: ${error instanceof Error ? error.message : String(error)}`);
153
+ }
154
+
155
+ } else if (requirement.type === 'pip') {
156
+ await _installPipRequirements(pythonCommand);
157
+
158
+ const packageName = requirement.version.toLowerCase().includes('latest')
159
+ ? `${requirement.name} --upgrade`
160
+ : `${requirement.name}==${requirement.version}`;
161
+
162
+ Logger.log(`Pip requirement ${packageName} configured with registry ${registry.registryUrl}`);
163
+ return {
164
+ type: 'pip',
165
+ package: packageName,
166
+ registryUrl: registry.registryUrl,
167
+ version: requirement.version
168
+ };
169
+ } else {
170
+ throw new Error(`Unsupported requirement type for ADO artifact: ${requirement.type}`);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get the latest version of an artifact from Azure DevOps.
176
+ * This is a simplified implementation and might require further enhancements for robust authentication and error handling.
177
+ * @param execPromise The promise-based exec function.
178
+ * @param requirement The requirement configuration.
179
+ * @param registry The Azure DevOps artifacts registry configuration.
180
+ * @param options Optional server install options (e.g., for pythonCommand).
181
+ * @returns The latest version string, or undefined if not found or an error occurs.
182
+ */
183
+ export async function getArtifactLatestVersion(
184
+ requirement: RequirementConfig,
185
+ registry: RegistryConfig['artifacts'],
186
+ options?: import('../core/types.js').ServerInstallOptions, // Added import for ServerInstallOptions
187
+ targetDir?: string // Optional target directory for npm
188
+ ): Promise<string | undefined> {
189
+ if (!registry || !registry.registryUrl) {
190
+ Logger.error('ADO Artifact registry URL is missing for fetching latest version.');
191
+ return undefined;
192
+ }
193
+
194
+ Logger.debug(`Attempting to fetch latest version for ADO artifact: ${requirement.name} of type ${requirement.type}`);
195
+
196
+ if (requirement.type === 'npm') {
197
+ if (!registry.registryName) { // Ensure registryName is present
198
+ Logger.error('NPM ADO Artifact registry name is missing for fetching latest version.');
199
+ return undefined;
200
+ }
201
+
202
+ const requirementDir = targetDir || path.join(SETTINGS_DIR, 'requirements', requirement.name, requirement.version);
203
+
204
+ try {
205
+ await fs.mkdir(requirementDir, { recursive: true });
206
+ Logger.debug(`Ensured directory for NPM ADO version check: ${requirementDir}`);
207
+
208
+ // Configure auth within the requirementDir
209
+ await _configureNpmAdoAuthInDirectory(execAsync, registry, requirementDir);
210
+ Logger.log(`Successfully configured ADO NPM auth for version check of ${requirement.name} in ${requirementDir}`);
211
+
212
+ const fullPackageName = `@${registry.registryName}/${requirement.name}`;
213
+ const command = `npm view ${fullPackageName} version --registry ${registry.registryUrl}`;
214
+ Logger.debug(`Executing for NPM ADO latest version: ${command} in ${requirementDir}`);
215
+
216
+ const { stdout, stderr } = await execAsync(command, { cwd: requirementDir });
217
+
218
+ if (stderr && !stdout.trim()) {
219
+ Logger.error(`Error fetching NPM ADO version for ${fullPackageName} via npm view: ${stderr}`);
220
+ return undefined;
221
+ }
222
+
223
+ const version = stdout.trim();
224
+ if (version) {
225
+ Logger.debug(`Found latest NPM ADO version for ${fullPackageName}: ${version}`);
226
+ return version;
227
+ } else {
228
+ Logger.log(`Could not parse latest NPM version from ADO for ${fullPackageName} from output: '${stdout}'`);
229
+ return undefined;
230
+ }
231
+ } catch (error) {
232
+ Logger.error(`Exception fetching latest NPM version from ADO for ${requirement.name} in ${requirementDir}: ${error instanceof Error ? error.message : String(error)}`);
233
+ return undefined;
234
+ }
235
+ } else if (requirement.type === 'pip') {
236
+ try {
237
+ const pythonCmd = options?.settings?.pythonEnv as string || 'python';
238
+ const pipCmd = `${pythonCmd} -m pip`;
239
+
240
+ // Ensure keyring and artifacts-keyring are installed.
241
+ // This is usually handled by `handleArtifact` during install.
242
+ // For a standalone version check, we might need to ensure they exist.
243
+ // Consider calling _installPipRequirements(pythonCmd) if necessary, but it has side effects.
244
+ // For now, assume they are present if ADO pip artifacts are being used.
245
+
246
+ const command = `${pipCmd} index versions ${requirement.name} --extra-index-url ${registry.registryUrl}`;
247
+ Logger.debug(`Executing for Pip ADO latest version: ${command}`);
248
+ const { stdout, stderr } = await execAsync(command);
249
+
250
+ if (stderr && !stdout.trim()) { // If there's stderr and no significant stdout, it's likely an error
251
+ Logger.error(`Error fetching Pip ADO version for ${requirement.name}: ${stderr}`);
252
+ return undefined;
253
+ }
254
+
255
+ // Parse output like:
256
+ // mypackage (1.0.0)
257
+ // Available versions: 1.0.0, 0.9.0
258
+ // LATEST: 1.0.0
259
+ // Or:
260
+ // mypackage
261
+ // VERSIONS: 1.0.0, 0.9.0
262
+ // Latest: 1.0.0
263
+ const latestMatch = stdout.match(/(?:LATEST|Latest):\s*([^\s]+)/);
264
+ if (latestMatch && latestMatch[1]) {
265
+ Logger.debug(`Found latest Pip ADO version via LATEST line: ${latestMatch[1]}`);
266
+ return latestMatch[1];
267
+ }
268
+
269
+ // Fallback: try to parse from "Available versions" or "VERSIONS"
270
+ const versionsMatch = stdout.match(/(?:Available versions|VERSIONS):\s*([^\n]+)/);
271
+ if (versionsMatch && versionsMatch[1]) {
272
+ const versions = versionsMatch[1].split(',').map(v => v.trim()).filter(v => v);
273
+ if (versions.length > 0) {
274
+ // This is a naive approach, assumes the first listed is relevant or latest.
275
+ // Proper sorting (e.g. semver) would be better.
276
+ Logger.debug(`Found Pip ADO versions: ${versions.join(', ')}. Taking the first one: ${versions[0]}`);
277
+ return versions[0];
278
+ }
279
+ }
280
+
281
+ Logger.log(`Could not parse latest Pip version from ADO for ${requirement.name} from output: ${stdout}`); // Changed Logger.warn to Logger.log
282
+ return undefined;
283
+ } catch (error) {
284
+ Logger.error(`Exception fetching latest Pip version from ADO for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
285
+ return undefined;
286
+ }
287
+ }
288
+
289
+ Logger.log(`getArtifactLatestVersion not implemented for type '${requirement.type}' or encountered an issue.`); // Changed Logger.warn to Logger.log
290
+ return undefined;
291
+ }
@@ -1,9 +1,6 @@
1
1
  import fs from 'fs/promises';
2
- import * as fsSync from 'fs';
3
2
  import * as path from 'path';
4
- import { execSync } from 'child_process';
5
3
  import extractZipModule from 'extract-zip';
6
- import { Logger } from './logger.js';
7
4
 
8
5
  /**
9
6
  * Extract a zip file to a directory
@@ -37,47 +34,6 @@ export async function extractZipFile(zipPath: string, options: { dir: string }):
37
34
  }
38
35
 
39
36
 
40
- /**
41
- * Resolves the path to an NPM module, replacing template variables
42
- * First checks if the module path exists under NVM-controlled Node, then falls back to global npm
43
- * @param pathString The path string potentially containing template variables
44
- * @returns The resolved path
45
- */
46
- export function resolveNpmModulePath(pathString: string): string {
47
- // If the path doesn't contain the ${NPMPATH} template, return it as is
48
- if (!pathString.includes('${NPMPATH}')) {
49
- return pathString;
50
- }
51
-
52
- // First try to get NVM-controlled npm path
53
- const nvmHome = process.env.NVM_HOME;
54
- if (nvmHome) {
55
- try {
56
- // Get current node version
57
- const nodeVersion = execSync('node -v').toString().trim();
58
-
59
- // Construct the path to npm in the NVM directory
60
- const nvmNodePath = path.join(nvmHome, nodeVersion);
61
- const resolvedPath = pathString.replace('${NPMPATH}', nvmNodePath);
62
- // Check if this path exists
63
- try {
64
- fsSync.accessSync(resolvedPath);
65
- return resolvedPath;
66
- } catch (error) {
67
- Logger.debug(`NVM controlled path doesn't exist: ${resolvedPath}, will try global npm`);
68
- // Path doesn't exist, will fall back to global npm
69
- }
70
- } catch (error) {
71
- Logger.debug(`Error determining Node version: ${error}, will use global npm`);
72
- // Error getting node version, will fall back to global npm
73
- }
74
- }
75
-
76
- // Fall back to global npm path
77
- const globalNpmPath = execSync('npm root -g').toString().trim();
78
- return pathString.replace('${NPMPATH}', globalNpmPath);
79
- }
80
-
81
37
  /**
82
38
  * Reads a JSON file and parses its content
83
39
  * @param filePath Path to the JSON file