imcp 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +1 -0
  2. package/dist/core/ConfigurationProvider.js +15 -0
  3. package/dist/core/InstallationService.js +2 -7
  4. package/dist/core/MCPManager.d.ts +11 -2
  5. package/dist/core/MCPManager.js +24 -1
  6. package/dist/core/RequirementService.js +2 -8
  7. package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
  8. package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
  9. package/dist/core/installers/clients/ClientInstaller.d.ts +16 -9
  10. package/dist/core/installers/clients/ClientInstaller.js +80 -527
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +37 -0
  13. package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
  14. package/dist/core/installers/clients/ClineInstaller.js +124 -0
  15. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
  16. package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
  17. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
  18. package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
  19. package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
  20. package/dist/core/installers/requirements/BaseInstaller.js +5 -116
  21. package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
  22. package/dist/core/installers/requirements/CommandInstaller.js +7 -0
  23. package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
  24. package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
  25. package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
  26. package/dist/core/installers/requirements/NpmInstaller.js +150 -58
  27. package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
  28. package/dist/core/installers/requirements/PipInstaller.js +66 -28
  29. package/dist/core/onboard/FeedOnboardService.d.ts +50 -13
  30. package/dist/core/onboard/FeedOnboardService.js +263 -88
  31. package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
  32. package/dist/core/onboard/OnboardProcessor.js +290 -0
  33. package/dist/core/onboard/OnboardStatus.d.ts +49 -0
  34. package/dist/core/onboard/OnboardStatus.js +10 -0
  35. package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
  36. package/dist/core/onboard/OnboardStatusManager.js +176 -0
  37. package/dist/core/types.d.ts +4 -5
  38. package/dist/core/validators/FeedValidator.d.ts +8 -1
  39. package/dist/core/validators/FeedValidator.js +60 -7
  40. package/dist/core/validators/IServerValidator.d.ts +19 -0
  41. package/dist/core/validators/IServerValidator.js +2 -0
  42. package/dist/core/validators/SSEServerValidator.d.ts +15 -0
  43. package/dist/core/validators/SSEServerValidator.js +39 -0
  44. package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
  45. package/dist/core/validators/ServerValidatorFactory.js +45 -0
  46. package/dist/core/validators/StdioServerValidator.d.ts +46 -0
  47. package/dist/core/validators/StdioServerValidator.js +229 -0
  48. package/dist/services/InstallRequestValidator.d.ts +1 -1
  49. package/dist/services/ServerService.d.ts +9 -6
  50. package/dist/services/ServerService.js +18 -7
  51. package/dist/utils/adoUtils.d.ts +29 -0
  52. package/dist/utils/adoUtils.js +252 -0
  53. package/dist/utils/clientUtils.d.ts +0 -7
  54. package/dist/utils/clientUtils.js +0 -42
  55. package/dist/utils/githubUtils.d.ts +10 -0
  56. package/dist/utils/githubUtils.js +22 -0
  57. package/dist/utils/macroExpressionUtils.d.ts +38 -0
  58. package/dist/utils/macroExpressionUtils.js +116 -0
  59. package/dist/utils/osUtils.d.ts +4 -20
  60. package/dist/utils/osUtils.js +78 -23
  61. package/dist/web/contract/serverContract.d.ts +3 -1
  62. package/dist/web/public/css/notifications.css +48 -17
  63. package/dist/web/public/css/onboard.css +66 -3
  64. package/dist/web/public/index.html +84 -16
  65. package/dist/web/public/js/api.js +3 -6
  66. package/dist/web/public/js/flights/flights.js +127 -0
  67. package/dist/web/public/js/modal/installation.js +5 -5
  68. package/dist/web/public/js/modal/modalSetup.js +3 -2
  69. package/dist/web/public/js/notifications.js +66 -27
  70. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  71. package/dist/web/public/js/onboard/formProcessor.js +810 -255
  72. package/dist/web/public/js/onboard/index.js +328 -85
  73. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  74. package/dist/web/public/js/onboard/state.js +61 -17
  75. package/dist/web/public/js/onboard/templates.js +217 -249
  76. package/dist/web/public/js/onboard/uiHandlers.js +679 -117
  77. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  78. package/dist/web/public/js/serverCategoryList.js +15 -2
  79. package/dist/web/public/onboard.html +191 -45
  80. package/dist/web/public/styles.css +91 -1
  81. package/dist/web/server.d.ts +0 -10
  82. package/dist/web/server.js +131 -22
  83. package/package.json +2 -2
  84. package/src/core/ConfigurationProvider.ts +15 -0
  85. package/src/core/InstallationService.ts +2 -7
  86. package/src/core/MCPManager.ts +26 -1
  87. package/src/core/RequirementService.ts +2 -9
  88. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  89. package/src/core/installers/clients/ClientInstaller.ts +97 -608
  90. package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
  91. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  92. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  93. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  94. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  95. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  96. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  97. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  98. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  99. package/src/core/onboard/FeedOnboardService.ts +346 -0
  100. package/src/core/onboard/OnboardProcessor.ts +305 -0
  101. package/src/core/onboard/OnboardStatus.ts +55 -0
  102. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  103. package/src/core/types.ts +4 -5
  104. package/src/core/validators/FeedValidator.ts +79 -0
  105. package/src/core/validators/IServerValidator.ts +21 -0
  106. package/src/core/validators/SSEServerValidator.ts +43 -0
  107. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  108. package/src/core/validators/StdioServerValidator.ts +259 -0
  109. package/src/services/InstallRequestValidator.ts +1 -1
  110. package/src/services/ServerService.ts +22 -7
  111. package/src/utils/adoUtils.ts +291 -0
  112. package/src/utils/clientUtils.ts +0 -44
  113. package/src/utils/githubUtils.ts +24 -0
  114. package/src/utils/macroExpressionUtils.ts +121 -0
  115. package/src/utils/osUtils.ts +89 -24
  116. package/src/web/contract/serverContract.ts +74 -0
  117. package/src/web/public/css/notifications.css +48 -17
  118. package/src/web/public/css/onboard.css +107 -0
  119. package/src/web/public/index.html +84 -16
  120. package/src/web/public/js/api.js +3 -6
  121. package/src/web/public/js/flights/flights.js +127 -0
  122. package/src/web/public/js/modal/installation.js +5 -5
  123. package/src/web/public/js/modal/modalSetup.js +3 -2
  124. package/src/web/public/js/notifications.js +66 -27
  125. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  126. package/src/web/public/js/onboard/formProcessor.js +864 -0
  127. package/src/web/public/js/onboard/index.js +374 -0
  128. package/src/web/public/js/onboard/publishHandler.js +132 -0
  129. package/src/web/public/js/onboard/state.js +76 -0
  130. package/src/web/public/js/onboard/templates.js +343 -0
  131. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  132. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  133. package/src/web/public/js/serverCategoryList.js +15 -2
  134. package/src/web/public/onboard.html +296 -0
  135. package/src/web/public/styles.css +91 -1
  136. package/src/web/server.ts +167 -58
@@ -1,7 +1,6 @@
1
1
  import path from 'path';
2
- import fs from 'fs/promises';
3
2
  import { SETTINGS_DIR } from '../../constants.js';
4
- import { extractZipFile } from '../../../utils/clientUtils.js';
3
+ import { Logger } from '../../../utils/logger.js';
5
4
  /**
6
5
  * Abstract base class with common functionality for all requirement installers
7
6
  */
@@ -16,9 +15,10 @@ export class BaseInstaller {
16
15
  * Check if updates are available for the requirement
17
16
  * @param requirement The requirement to check
18
17
  * @param currentStatus The current status of the requirement
18
+ * @param options Optional server install options.
19
19
  * @returns The status of the requirement with update information
20
20
  */
21
- async checkForUpdates(requirement, currentStatus) {
21
+ async checkForUpdates(requirement, currentStatus, options) {
22
22
  try {
23
23
  // If requirement is not installed, no need to check for updates
24
24
  if (!currentStatus.installed) {
@@ -28,23 +28,7 @@ export class BaseInstaller {
28
28
  if (!requirement.version.includes('latest')) {
29
29
  return currentStatus;
30
30
  }
31
- let latestVersion;
32
- // Check based on registry type
33
- if (requirement.registry?.githubRelease) {
34
- latestVersion = await this.getGitHubLatestVersion(requirement.registry.githubRelease.repository);
35
- }
36
- else {
37
- // Use common practice based on requirement type
38
- switch (requirement.type) {
39
- case 'npm':
40
- latestVersion = await this.getNpmLatestVersion(requirement.name);
41
- break;
42
- case 'pip':
43
- latestVersion = await this.getPipLatestVersion(requirement.name);
44
- break;
45
- // Add other types as needed
46
- }
47
- }
31
+ const latestVersion = await this.getLatestVersion(requirement, options);
48
32
  // If we found a latest version and it's different from current
49
33
  if (latestVersion && latestVersion !== currentStatus.version) {
50
34
  return {
@@ -65,104 +49,9 @@ export class BaseInstaller {
65
49
  }
66
50
  catch (error) {
67
51
  // Don't update status on error, just log it
68
- console.warn(`Error checking for updates for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
52
+ Logger.log(`Error checking for updates for ${requirement.name}: ${error instanceof Error ? error.message : String(error)}`);
69
53
  return currentStatus;
70
54
  }
71
55
  }
72
- /**
73
- * Helper to handle artifact registry downloads
74
- * @param requirement The requirement configuration
75
- * @param registry The artifacts registry configuration
76
- * @returns The registry URL
77
- */
78
- async handleArtifactsRegistry(requirement, registry) {
79
- if (!registry) {
80
- throw new Error('Artifacts registry configuration is required');
81
- }
82
- const { registryUrl, assetName } = registry;
83
- if (!registryUrl) {
84
- throw new Error('Registry URL is required for artifacts downloads');
85
- }
86
- return registryUrl;
87
- }
88
- /**
89
- * Helper to handle local path registry
90
- * @param requirement The requirement configuration
91
- * @param registry The local registry configuration
92
- * @returns The local path or extracted asset path
93
- */
94
- async handleLocalRegistry(requirement, registry) {
95
- if (!registry) {
96
- throw new Error('Local registry configuration is required');
97
- }
98
- const { localPath, assetName } = registry;
99
- if (!localPath) {
100
- throw new Error('Local path is required for local registry');
101
- }
102
- // Verify the local path exists
103
- try {
104
- await fs.access(localPath);
105
- }
106
- catch (error) {
107
- throw new Error(`Local path ${localPath} does not exist or is not accessible`);
108
- }
109
- // If the path is a zip file and assetName is specified, extract it
110
- if (localPath.endsWith('.zip') && assetName) {
111
- const extractDir = path.join(this.downloadsDir, path.basename(localPath, '.zip'));
112
- await fs.mkdir(extractDir, { recursive: true });
113
- // Extract the zip file
114
- await extractZipFile(localPath, { dir: extractDir });
115
- // Find the asset in the extracted directory
116
- const assetPath = path.join(extractDir, assetName);
117
- try {
118
- await fs.access(assetPath);
119
- return assetPath;
120
- }
121
- catch (error) {
122
- throw new Error(`Asset ${assetName} not found in extracted directory ${extractDir}`);
123
- }
124
- }
125
- return localPath;
126
- }
127
- /**
128
- * Get the latest version available for a GitHub repository
129
- * @param repository The GitHub repository in format 'owner/repo'
130
- * @returns The latest version or tag
131
- */
132
- async getGitHubLatestVersion(repository) {
133
- try {
134
- // Use GitHub CLI to get the latest release
135
- const { stdout } = await this.execPromise(`gh release view --repo ${repository} --json tagName --jq .tagName`);
136
- const latestTag = stdout.trim();
137
- // Remove 'v' prefix if present
138
- return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
139
- }
140
- catch (error) {
141
- // If gh command fails, try to get the latest tag
142
- const { stdout } = await this.execPromise(`git ls-remote --tags --refs https://github.com/${repository}.git | sort -t '/' -k 3 -V | tail -n 1 | awk -F/ '{print $3}'`);
143
- let latestTag = stdout.trim();
144
- // Remove 'v' prefix if present
145
- return latestTag.startsWith('v') ? latestTag.substring(1) : latestTag;
146
- }
147
- }
148
- /**
149
- * Get the latest version available for an NPM package
150
- * @param packageName The name of the NPM package
151
- * @returns The latest version
152
- */
153
- async getNpmLatestVersion(packageName) {
154
- const { stdout } = await this.execPromise(`npm view ${packageName} version`);
155
- return stdout.trim();
156
- }
157
- /**
158
- * Get the latest version available for a pip package
159
- * @param packageName The name of the pip package
160
- * @returns The latest version
161
- */
162
- async getPipLatestVersion(packageName, options) {
163
- const pythonCmd = options?.settings?.pythonEnv || 'python';
164
- const { stdout } = await this.execPromise(`${pythonCmd} -m pip index versions ${packageName} --pre=0 | grep -oP "(?<=Latest:\\s)[^\\s]+" | head -1`);
165
- return stdout.trim();
166
- }
167
56
  }
168
57
  //# sourceMappingURL=BaseInstaller.js.map
@@ -1,9 +1,14 @@
1
- import { RequirementConfig, RequirementStatus } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  /**
4
4
  * Installer implementation for command-line tools
5
5
  */
6
6
  export declare class CommandInstaller extends BaseInstaller {
7
+ /**
8
+ * Constructor for the CommandInstaller
9
+ * @param execPromise Function to execute commands
10
+ */
11
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined>;
7
12
  /**
8
13
  * Mapping of command names to their package IDs
9
14
  * This handles special cases where the command name differs from the package ID
@@ -6,6 +6,13 @@ import { Logger } from '../../../utils/logger.js';
6
6
  * Installer implementation for command-line tools
7
7
  */
8
8
  export class CommandInstaller extends BaseInstaller {
9
+ /**
10
+ * Constructor for the CommandInstaller
11
+ * @param execPromise Function to execute commands
12
+ */
13
+ getLatestVersion(requirement, options) {
14
+ throw new Error('Method not implemented.');
15
+ }
9
16
  /**
10
17
  * Mapping of command names to their package IDs
11
18
  * This handles special cases where the command name differs from the package ID
@@ -1,10 +1,15 @@
1
- import { RequirementConfig, RequirementStatus } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  /**
4
4
  * Installer implementation for general requirements (type 'other')
5
5
  * This installer handles requirements that don't fit into specific package manager categories
6
6
  */
7
7
  export declare class GeneralInstaller extends BaseInstaller {
8
+ /**
9
+ * Constructor for the GeneralInstaller
10
+ * @param execPromise Function to execute commands
11
+ */
12
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined>;
8
13
  /**
9
14
  * Check if this installer can handle the given requirement type
10
15
  * @param requirement The requirement to check
@@ -1,10 +1,18 @@
1
1
  import { BaseInstaller } from './BaseInstaller.js';
2
2
  import { handleGitHubRelease } from '../../../utils/githubUtils.js';
3
+ import { handleArtifact } from '../../../utils/adoUtils.js';
3
4
  /**
4
5
  * Installer implementation for general requirements (type 'other')
5
6
  * This installer handles requirements that don't fit into specific package manager categories
6
7
  */
7
8
  export class GeneralInstaller extends BaseInstaller {
9
+ /**
10
+ * Constructor for the GeneralInstaller
11
+ * @param execPromise Function to execute commands
12
+ */
13
+ getLatestVersion(requirement, options) {
14
+ throw new Error('Method not implemented.');
15
+ }
8
16
  /**
9
17
  * Check if this installer can handle the given requirement type
10
18
  * @param requirement The requirement to check
@@ -54,10 +62,7 @@ export class GeneralInstaller extends BaseInstaller {
54
62
  installPath = result.resolvedPath;
55
63
  }
56
64
  else if (requirement.registry.artifacts) {
57
- installPath = await this.handleArtifactsRegistry(requirement, requirement.registry.artifacts);
58
- }
59
- else if (requirement.registry.local) {
60
- installPath = await this.handleLocalRegistry(requirement, requirement.registry.local);
65
+ await handleArtifact(requirement, requirement.registry.artifacts);
61
66
  }
62
67
  else {
63
68
  throw new Error('Invalid registry configuration');
@@ -1,4 +1,4 @@
1
- import { RequirementConfig, RequirementStatus } from '../../types.js';
1
+ import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  /**
4
4
  * Installer implementation for NPM packages
@@ -12,15 +12,54 @@ export declare class NpmInstaller extends BaseInstaller {
12
12
  canHandle(requirement: RequirementConfig): boolean;
13
13
  supportCheckUpdates(): boolean;
14
14
  /**
15
- * Check if the NPM package is already installed
15
+ * Get the latest version available for the NPM package.
16
+ * @param requirement The requirement to check.
17
+ * @param options Optional server install options.
18
+ * @returns The latest version string, or undefined if not found or not applicable.
19
+ */
20
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined>;
21
+ /**
22
+ * Generates a dedicated folder path for a requirement.
23
+ * @param requirement The requirement configuration.
24
+ * @returns The path to the requirement's dedicated folder.
25
+ * @private
26
+ */
27
+ private _getRequirementFolderPath;
28
+ /**
29
+ * Check if the NPM package is already installed in its dedicated folder.
16
30
  * @param requirement The requirement to check
31
+ * @param options Installation options, may contain folderName.
17
32
  * @returns The status of the requirement
18
33
  */
19
- checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
34
+ checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
35
+ /**
36
+ * Retrieves the installed version of an NPM package from a given directory.
37
+ * @param requirementName The name of the NPM package.
38
+ * @param directory The directory to check for the package.
39
+ * @returns The installed version string, or undefined if not found or an error occurs.
40
+ * @private
41
+ */
42
+ private _getInstalledVersion;
43
+ /**
44
+ * Get the name of the requirement, including registry information if applicable.
45
+ * @param requirement The requirement configuration.
46
+ * @returns The formatted requirement name.
47
+ * @private
48
+ */
49
+ private _getRequirementName;
50
+ /**
51
+ * Installs an NPM package into a dedicated local folder.
52
+ * @param requirement The requirement to install.
53
+ * @param packageSource This can be name@version, path to tgz, or path to folder.
54
+ * @param targetDir Target directory for installation.
55
+ * @returns The installed version of the package.
56
+ */
57
+ private _installPackage;
20
58
  /**
21
- * Install the NPM package
22
- * @param requirement The requirement to install
23
- * @returns The status of the installation
59
+ * Install the NPM package.
60
+ * @param requirement The requirement to install.
61
+ * @param options Installation options, including pythonCommand for ADO (though not used by npm) and folderName.
62
+ * @returns The status of the installation.
24
63
  */
25
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
64
+ install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
26
65
  }
@@ -1,6 +1,12 @@
1
1
  import { BaseInstaller } from './BaseInstaller.js';
2
2
  import { compareVersions } from '../../../utils/versionUtils.js';
3
- import { handleGitHubRelease } from '../../../utils/githubUtils.js';
3
+ import { handleGitHubRelease, getGitHubLatestVersion } from '../../../utils/githubUtils.js';
4
+ // Assuming getArtifactLatestVersion will be available in adoUtils.ts
5
+ import { handleArtifact as handleAdoArtifact, getArtifactLatestVersion } from '../../../utils/adoUtils.js';
6
+ import path from 'path';
7
+ import fs from 'fs/promises';
8
+ import { SETTINGS_DIR } from '../../constants.js'; // Corrected path
9
+ import { Logger } from '../../../utils/logger.js';
4
10
  /**
5
11
  * Installer implementation for NPM packages
6
12
  */
@@ -17,101 +23,187 @@ export class NpmInstaller extends BaseInstaller {
17
23
  return true;
18
24
  }
19
25
  /**
20
- * Check if the NPM package is already installed
26
+ * Get the latest version available for the NPM package.
27
+ * @param requirement The requirement to check.
28
+ * @param options Optional server install options.
29
+ * @returns The latest version string, or undefined if not found or not applicable.
30
+ */
31
+ async getLatestVersion(requirement, options) {
32
+ if (requirement.registry) {
33
+ if (requirement.registry.githubRelease) {
34
+ return getGitHubLatestVersion(this.execPromise, requirement.registry.githubRelease.repository);
35
+ }
36
+ else if (requirement.registry.artifacts) {
37
+ // Assuming getArtifactLatestVersion exists and has a compatible signature
38
+ // This might need adjustment based on the actual implementation of getArtifactLatestVersion
39
+ const targetDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
40
+ return getArtifactLatestVersion(requirement, requirement.registry.artifacts, options, targetDir);
41
+ }
42
+ }
43
+ // Default: get common latest version from npm registry
44
+ const { stdout } = await this.execPromise(`npm view ${requirement.name} version`);
45
+ return stdout.trim();
46
+ }
47
+ /**
48
+ * Generates a dedicated folder path for a requirement.
49
+ * @param requirement The requirement configuration.
50
+ * @returns The path to the requirement's dedicated folder.
51
+ * @private
52
+ */
53
+ _getRequirementFolderPath(requirement) {
54
+ return path.join(SETTINGS_DIR, 'npm_requirements', requirement.name, requirement.version.includes('latest') ? 'latest' : requirement.version);
55
+ }
56
+ /**
57
+ * Check if the NPM package is already installed in its dedicated folder.
21
58
  * @param requirement The requirement to check
59
+ * @param options Installation options, may contain folderName.
22
60
  * @returns The status of the requirement
23
61
  */
24
- async checkInstallation(requirement) {
62
+ async checkInstallation(requirement, options) {
63
+ const requirementDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
64
+ const requirementName = this._getRequirementName(requirement);
65
+ Logger.debug(`Checking installation for ${requirementName} in ${requirementDir}`);
25
66
  try {
26
- const { stdout } = await this.execPromise(`npm list -g ${requirement.name} --json`);
27
- const pkgInfo = JSON.parse(stdout);
28
- const installedVersion = pkgInfo?.dependencies?.[requirement.name]?.version;
67
+ const installedVersion = await this._getInstalledVersion(requirementName, requirementDir);
68
+ if (installedVersion) {
69
+ return {
70
+ name: requirement.name,
71
+ type: 'npm',
72
+ installed: true,
73
+ version: installedVersion,
74
+ npmPath: requirementDir,
75
+ inProgress: false,
76
+ };
77
+ }
78
+ // If package not found in dependencies or version is missing
29
79
  return {
30
80
  name: requirement.name,
31
81
  type: 'npm',
32
- installed: !!installedVersion,
33
- version: installedVersion,
34
- inProgress: false
82
+ installed: false,
83
+ inProgress: false,
84
+ error: `Package ${requirement.name} not found or version missing in npm list output in ${requirementDir}.`,
35
85
  };
36
86
  }
37
87
  catch (error) {
88
+ // npm list command likely failed (e.g., package not installed, or requirementDir not an npm project)
89
+ Logger.debug(`Error checking installation for ${requirement.name} in ${requirementDir}: ${error instanceof Error ? error.message : String(error)}`);
38
90
  return {
39
91
  name: requirement.name,
40
92
  type: 'npm',
41
93
  installed: false,
94
+ inProgress: false,
42
95
  error: error instanceof Error ? error.message : String(error),
43
- inProgress: false
44
96
  };
45
97
  }
46
98
  }
47
99
  /**
48
- * Install the NPM package
49
- * @param requirement The requirement to install
50
- * @returns The status of the installation
100
+ * Retrieves the installed version of an NPM package from a given directory.
101
+ * @param requirementName The name of the NPM package.
102
+ * @param directory The directory to check for the package.
103
+ * @returns The installed version string, or undefined if not found or an error occurs.
104
+ * @private
51
105
  */
52
- async install(requirement) {
106
+ async _getInstalledVersion(requirementName, directory) {
53
107
  try {
54
- const status = await this.checkInstallation(requirement);
55
- if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0) {
56
- return status;
108
+ const command = `npm list ${requirementName} --depth=0 --json --prefix "${directory}"`;
109
+ Logger.debug(`Getting installed version for ${requirementName} in ${directory} with command: ${command}`);
110
+ const { stdout } = await this.execPromise(command);
111
+ const listOutput = JSON.parse(stdout);
112
+ if (listOutput.dependencies && listOutput.dependencies[requirementName] && listOutput.dependencies[requirementName].version) {
113
+ return listOutput.dependencies[requirementName].version;
57
114
  }
58
- let resolvedVersion = requirement.version;
59
- // If no registry is specified, use standard npm installation
60
- if (!requirement.registry) {
61
- const { stderr } = await this.execPromise(`npm install -g ${requirement.name}@${requirement.version}`);
62
- if (stderr && !stderr.includes('added')) {
63
- throw new Error(stderr);
64
- }
115
+ Logger.debug(`Package ${requirementName} not found in npm list output in ${directory}.`);
116
+ return undefined;
117
+ }
118
+ catch (error) {
119
+ Logger.debug(`Error getting installed version for ${requirementName} in ${directory}: ${error instanceof Error ? error.message : String(error)}`);
120
+ return undefined; // Return undefined on error to indicate version couldn't be retrieved
121
+ }
122
+ }
123
+ /**
124
+ * Get the name of the requirement, including registry information if applicable.
125
+ * @param requirement The requirement configuration.
126
+ * @returns The formatted requirement name.
127
+ * @private
128
+ */
129
+ _getRequirementName(requirement) {
130
+ return requirement.registry?.artifacts?.registryName ? `@${requirement.registry.artifacts.registryName}/${requirement.name}` : requirement.name;
131
+ }
132
+ /**
133
+ * Installs an NPM package into a dedicated local folder.
134
+ * @param requirement The requirement to install.
135
+ * @param packageSource This can be name@version, path to tgz, or path to folder.
136
+ * @param targetDir Target directory for installation.
137
+ * @returns The installed version of the package.
138
+ */
139
+ async _installPackage(requirement, packageSource, targetDir) {
140
+ Logger.debug(`Installing NPM package from "${packageSource}" into "${targetDir}"`);
141
+ await fs.mkdir(targetDir, { recursive: true });
142
+ const installCommand = `npm install ${packageSource} --prefix "${targetDir}"`;
143
+ Logger.debug(`Executing install command: ${installCommand}`);
144
+ const requirementName = this._getRequirementName(requirement);
145
+ try {
146
+ const { stdout: installStdout, stderr: installStderr } = await this.execPromise(installCommand);
147
+ Logger.debug(`NPM install stdout for ${packageSource} in ${targetDir}: ${installStdout}`);
148
+ if (installStderr && !installStderr.toLowerCase().includes('added') && !installStderr.toLowerCase().includes('updated') && !installStderr.toLowerCase().includes('found 0 vulnerabilities')) {
149
+ // Log stderr if it's not just typical success noise
150
+ Logger.log(`NPM install stderr for ${packageSource} in ${targetDir}: ${installStderr}`);
151
+ }
152
+ const installedVersion = await this._getInstalledVersion(requirementName, targetDir);
153
+ if (installedVersion) {
154
+ Logger.log(`Successfully installed and verified ${requirementName}@${installedVersion} into ${targetDir}`);
155
+ return { version: installedVersion };
65
156
  }
66
157
  else {
67
- // Handle different registry types
68
- let packageSource;
158
+ throw new Error(`Successfully ran npm install for ${packageSource}, but ${requirement.name} version could not be determined via npm list in ${targetDir}.`);
159
+ }
160
+ }
161
+ catch (error) {
162
+ Logger.error(`Failed to install or verify NPM package ${packageSource} into ${targetDir}`, error);
163
+ throw error;
164
+ }
165
+ }
166
+ /**
167
+ * Install the NPM package.
168
+ * @param requirement The requirement to install.
169
+ * @param options Installation options, including pythonCommand for ADO (though not used by npm) and folderName.
170
+ * @returns The status of the installation.
171
+ */
172
+ async install(requirement, options) {
173
+ const targetDir = options?.settings?.folderName || this._getRequirementFolderPath(requirement);
174
+ await fs.mkdir(targetDir, { recursive: true });
175
+ try {
176
+ const status = await this.checkInstallation(requirement, { settings: { folderName: targetDir } });
177
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
178
+ Logger.log(`${requirement.name}@${status.version} already installed in ${targetDir}.`);
179
+ return status;
180
+ }
181
+ let resolvedVersion = requirement.version;
182
+ let packageToInstall = `${requirement.name}@${requirement.version}`;
183
+ if (requirement.registry) {
69
184
  if (requirement.registry.githubRelease) {
70
185
  const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
71
- packageSource = result.resolvedPath;
186
+ packageToInstall = result.resolvedPath;
72
187
  resolvedVersion = result.resolvedVersion;
73
188
  }
74
189
  else if (requirement.registry.artifacts) {
75
- // For npm with artifacts, configure npm to use the specified registry URL
76
- const registryUrl = requirement.registry.artifacts.registryUrl;
77
- await this.execPromise(`npm config set registry ${registryUrl}`);
78
- // Now install the package with the configured registry
79
- const { stderr } = await this.execPromise(`npm install -g ${requirement.name}@${requirement.version}`);
80
- if (stderr && !stderr.includes('added')) {
81
- // Reset the registry to the default npm registry
82
- await this.execPromise('npm config set registry https://registry.npmjs.org/');
83
- throw new Error(stderr);
84
- }
85
- // Reset the registry to the default npm registry
86
- await this.execPromise('npm config set registry https://registry.npmjs.org/');
87
- return {
88
- name: requirement.name,
89
- type: 'npm',
90
- installed: true,
91
- version: requirement.version,
92
- inProgress: false
93
- };
94
- }
95
- else if (requirement.registry.local) {
96
- packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
190
+ const adoResult = await handleAdoArtifact(requirement, requirement.registry.artifacts, options?.settings?.pythonCommand, targetDir);
191
+ packageToInstall = `${adoResult.package}@${adoResult.version}`;
192
+ resolvedVersion = adoResult.version;
97
193
  }
98
194
  else {
99
- throw new Error('Invalid registry configuration');
100
- }
101
- // Install from the package source
102
- const { stderr } = await this.execPromise(`npm install -g "${packageSource}"`);
103
- if (stderr && !stderr.includes('added')) {
104
- const status = await this.checkInstallation(requirement);
105
- if (!status.installed)
106
- throw new Error(stderr);
195
+ throw new Error('Invalid registry configuration for npm.');
107
196
  }
108
197
  }
198
+ const finalInstallResult = await this._installPackage(requirement, packageToInstall, targetDir);
199
+ resolvedVersion = finalInstallResult.version;
109
200
  return {
110
201
  name: requirement.name,
111
202
  type: 'npm',
112
203
  installed: true,
113
204
  version: resolvedVersion,
114
- inProgress: false
205
+ inProgress: false,
206
+ npmPath: targetDir
115
207
  };
116
208
  }
117
209
  catch (error) {
@@ -13,15 +13,24 @@ export declare class PipInstaller extends BaseInstaller {
13
13
  */
14
14
  canHandle(requirement: RequirementConfig): boolean;
15
15
  supportCheckUpdates(): boolean;
16
+ /**
17
+ * Get the latest version available for the pip package.
18
+ * @param requirement The requirement to check.
19
+ * @param options Optional server install options.
20
+ * @returns The latest version string, or undefined if not found or not applicable.
21
+ */
22
+ getLatestVersion(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<string | undefined>;
16
23
  /**
17
24
  * Check if the Python package is already installed
18
25
  * @param requirement The requirement to check
26
+ * @param options Optional server install options
19
27
  * @returns The status of the requirement
20
28
  */
21
29
  checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
22
30
  /**
23
31
  * Install the Python package
24
32
  * @param requirement The requirement to install
33
+ * @param options Optional server install options
25
34
  * @returns The status of the installation
26
35
  */
27
36
  install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;