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.
- package/dist/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +15 -0
- package/dist/core/InstallationService.js +2 -7
- package/dist/core/MCPManager.d.ts +11 -2
- package/dist/core/MCPManager.js +24 -1
- package/dist/core/RequirementService.js +2 -8
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
- package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
- package/dist/core/installers/clients/ClientInstaller.d.ts +16 -8
- package/dist/core/installers/clients/ClientInstaller.js +77 -504
- package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
- package/dist/core/installers/clients/ClientInstallerFactory.js +41 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
- package/dist/core/installers/clients/ClineInstaller.js +124 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
- package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
- package/dist/core/installers/requirements/BaseInstaller.js +5 -116
- package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/CommandInstaller.js +7 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
- package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
- package/dist/core/installers/requirements/NpmInstaller.js +150 -58
- package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
- package/dist/core/installers/requirements/PipInstaller.js +66 -28
- package/dist/core/onboard/FeedOnboardService.d.ts +72 -0
- package/dist/core/onboard/FeedOnboardService.js +312 -0
- package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
- package/dist/core/onboard/OnboardProcessor.js +290 -0
- package/dist/core/onboard/OnboardStatus.d.ts +49 -0
- package/dist/core/onboard/OnboardStatus.js +10 -0
- package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
- package/dist/core/onboard/OnboardStatusManager.js +176 -0
- package/dist/core/types.d.ts +6 -6
- package/dist/core/validators/FeedValidator.d.ts +20 -0
- package/dist/core/validators/FeedValidator.js +80 -0
- package/dist/core/validators/IServerValidator.d.ts +19 -0
- package/dist/core/validators/IServerValidator.js +2 -0
- package/dist/core/validators/SSEServerValidator.d.ts +15 -0
- package/dist/core/validators/SSEServerValidator.js +39 -0
- package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
- package/dist/core/validators/ServerValidatorFactory.js +45 -0
- package/dist/core/validators/StdioServerValidator.d.ts +46 -0
- package/dist/core/validators/StdioServerValidator.js +229 -0
- package/dist/services/InstallRequestValidator.d.ts +1 -1
- package/dist/services/ServerService.d.ts +9 -6
- package/dist/services/ServerService.js +18 -7
- package/dist/utils/adoUtils.d.ts +29 -0
- package/dist/utils/adoUtils.js +252 -0
- package/dist/utils/clientUtils.d.ts +0 -7
- package/dist/utils/clientUtils.js +0 -42
- package/dist/utils/githubUtils.d.ts +10 -0
- package/dist/utils/githubUtils.js +22 -0
- package/dist/utils/macroExpressionUtils.d.ts +38 -0
- package/dist/utils/macroExpressionUtils.js +116 -0
- package/dist/utils/osUtils.d.ts +4 -20
- package/dist/utils/osUtils.js +78 -23
- package/dist/web/contract/serverContract.d.ts +66 -0
- package/dist/web/contract/serverContract.js +2 -0
- package/dist/web/public/css/notifications.css +48 -17
- package/dist/web/public/css/onboard.css +107 -0
- package/dist/web/public/index.html +90 -18
- package/dist/web/public/js/api.js +3 -6
- package/dist/web/public/js/flights/flights.js +127 -0
- package/dist/web/public/js/modal/index.js +58 -0
- package/dist/web/public/js/modal/installHandler.js +227 -0
- package/dist/web/public/js/modal/installModal.js +163 -0
- package/dist/web/public/js/modal/installation.js +281 -0
- package/dist/web/public/js/modal/loadingModal.js +52 -0
- package/dist/web/public/js/modal/loadingUI.js +74 -0
- package/dist/web/public/js/modal/messageQueue.js +112 -0
- package/dist/web/public/js/modal/modalSetup.js +513 -0
- package/dist/web/public/js/modal/modalUI.js +214 -0
- package/dist/web/public/js/modal/modalUtils.js +49 -0
- package/dist/web/public/js/modal/version.js +20 -0
- package/dist/web/public/js/modal/versionUtils.js +20 -0
- package/dist/web/public/js/modal.js +25 -1041
- package/dist/web/public/js/notifications.js +66 -27
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/dist/web/public/js/onboard/formProcessor.js +864 -0
- package/dist/web/public/js/onboard/index.js +374 -0
- package/dist/web/public/js/onboard/publishHandler.js +132 -0
- package/dist/web/public/js/onboard/state.js +76 -0
- package/dist/web/public/js/onboard/templates.js +343 -0
- package/dist/web/public/js/onboard/uiHandlers.js +758 -0
- package/dist/web/public/js/onboard/validationHandlers.js +378 -0
- package/dist/web/public/js/serverCategoryDetails.js +43 -17
- package/dist/web/public/js/serverCategoryList.js +15 -2
- package/dist/web/public/onboard.html +296 -0
- package/dist/web/public/styles.css +91 -1
- package/dist/web/server.d.ts +0 -10
- package/dist/web/server.js +131 -22
- package/package.json +2 -2
- package/src/core/ConfigurationProvider.ts +15 -0
- package/src/core/InstallationService.ts +2 -7
- package/src/core/MCPManager.ts +26 -1
- package/src/core/RequirementService.ts +2 -9
- package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
- package/src/core/installers/clients/ClientInstaller.ts +97 -589
- package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
- package/src/core/installers/clients/ClineInstaller.ts +135 -0
- package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
- package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
- package/src/core/installers/requirements/BaseInstaller.ts +13 -136
- package/src/core/installers/requirements/CommandInstaller.ts +9 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
- package/src/core/installers/requirements/NpmInstaller.ts +178 -61
- package/src/core/installers/requirements/PipInstaller.ts +68 -29
- package/src/core/onboard/FeedOnboardService.ts +346 -0
- package/src/core/onboard/OnboardProcessor.ts +305 -0
- package/src/core/onboard/OnboardStatus.ts +55 -0
- package/src/core/onboard/OnboardStatusManager.ts +188 -0
- package/src/core/types.ts +6 -6
- package/src/core/validators/FeedValidator.ts +79 -0
- package/src/core/validators/IServerValidator.ts +21 -0
- package/src/core/validators/SSEServerValidator.ts +43 -0
- package/src/core/validators/ServerValidatorFactory.ts +51 -0
- package/src/core/validators/StdioServerValidator.ts +259 -0
- package/src/services/InstallRequestValidator.ts +1 -1
- package/src/services/ServerService.ts +22 -7
- package/src/utils/adoUtils.ts +291 -0
- package/src/utils/clientUtils.ts +0 -44
- package/src/utils/githubUtils.ts +24 -0
- package/src/utils/macroExpressionUtils.ts +121 -0
- package/src/utils/osUtils.ts +89 -24
- package/src/web/contract/serverContract.ts +74 -0
- package/src/web/public/css/notifications.css +48 -17
- package/src/web/public/css/onboard.css +107 -0
- package/src/web/public/index.html +90 -18
- package/src/web/public/js/api.js +3 -6
- package/src/web/public/js/flights/flights.js +127 -0
- package/src/web/public/js/modal/index.js +58 -0
- package/src/web/public/js/modal/installModal.js +163 -0
- package/src/web/public/js/modal/installation.js +281 -0
- package/src/web/public/js/modal/loadingModal.js +52 -0
- package/src/web/public/js/modal/messageQueue.js +112 -0
- package/src/web/public/js/modal/modalSetup.js +513 -0
- package/src/web/public/js/modal/modalUtils.js +49 -0
- package/src/web/public/js/modal/versionUtils.js +20 -0
- package/src/web/public/js/modal.js +25 -1041
- package/src/web/public/js/notifications.js +66 -27
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/src/web/public/js/onboard/formProcessor.js +864 -0
- package/src/web/public/js/onboard/index.js +374 -0
- package/src/web/public/js/onboard/publishHandler.js +132 -0
- package/src/web/public/js/onboard/state.js +76 -0
- package/src/web/public/js/onboard/templates.js +343 -0
- package/src/web/public/js/onboard/uiHandlers.js +758 -0
- package/src/web/public/js/onboard/validationHandlers.js +378 -0
- package/src/web/public/js/serverCategoryDetails.js +43 -17
- package/src/web/public/js/serverCategoryList.js +15 -2
- package/src/web/public/onboard.html +296 -0
- package/src/web/public/styles.css +91 -1
- 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/
|
|
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 = {}
|
|
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
|
+
}
|
package/src/utils/clientUtils.ts
CHANGED
|
@@ -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
|