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
@@ -1,5 +1,9 @@
1
1
  import { BaseInstaller } from './BaseInstaller.js';
2
- import { handleGitHubRelease } from '../../../utils/githubUtils.js';
2
+ import { handleGitHubRelease, getGitHubLatestVersion } from '../../../utils/githubUtils.js';
3
+ // Assuming getArtifactLatestVersion will be available in adoUtils.ts
4
+ import { handleArtifact as handleAdoArtifact, getArtifactLatestVersion } from '../../../utils/adoUtils.js';
5
+ import { compareVersions } from '../../../utils/versionUtils.js';
6
+ import { Logger } from '../../../utils/logger.js';
3
7
  /**
4
8
  * Installer implementation for Python packages using pip
5
9
  */
@@ -20,29 +24,71 @@ export class PipInstaller extends BaseInstaller {
20
24
  return requirement.type === 'pip';
21
25
  }
22
26
  supportCheckUpdates() {
23
- /// temporarily disabling update check for pip as not able to get which pip of python is being used
24
27
  return true;
25
28
  }
29
+ /**
30
+ * Get the latest version available for the pip package.
31
+ * @param requirement The requirement to check.
32
+ * @param options Optional server install options.
33
+ * @returns The latest version string, or undefined if not found or not applicable.
34
+ */
35
+ async getLatestVersion(requirement, options) {
36
+ if (requirement.registry) {
37
+ if (requirement.registry.githubRelease) {
38
+ return getGitHubLatestVersion(this.execPromise, requirement.registry.githubRelease.repository);
39
+ }
40
+ else if (requirement.registry.artifacts) {
41
+ // Assuming getArtifactLatestVersion exists and has a compatible signature
42
+ return getArtifactLatestVersion(requirement, requirement.registry.artifacts, options);
43
+ }
44
+ }
45
+ // Default: get common latest version from pip index
46
+ const pipCmd = this.getPipCommand(options);
47
+ const { stdout } = await this.execPromise(`${pipCmd} index versions ${requirement.name} --pre=0`);
48
+ // Parse output to find the latest version. Example output:
49
+ // mypackage (1.0.0)
50
+ // Available versions: 1.0.0, 0.9.0
51
+ // LATEST: 1.0.0
52
+ // Or for some packages:
53
+ // mypackage
54
+ // VERSIONS: 1.0.0, 0.9.0
55
+ // Latest: 1.0.0
56
+ const latestMatch = stdout.match(/(?:LATEST|Latest):\s*([^\s]+)/);
57
+ if (latestMatch && latestMatch[1]) {
58
+ return latestMatch[1];
59
+ }
60
+ // Fallback if LATEST line is not found, try to get the first version from "Available versions" or "VERSIONS"
61
+ const versionsMatch = stdout.match(/(?:Available versions|VERSIONS):\s*([^\n]+)/);
62
+ if (versionsMatch && versionsMatch[1]) {
63
+ const versions = versionsMatch[1].split(',').map(v => v.trim());
64
+ if (versions.length > 0) {
65
+ // Assuming versions are listed in a somewhat reasonable order,
66
+ // or we might need more sophisticated version sorting here.
67
+ return versions[0];
68
+ }
69
+ }
70
+ return undefined; // Or throw an error if version cannot be determined
71
+ }
26
72
  /**
27
73
  * Check if the Python package is already installed
28
74
  * @param requirement The requirement to check
75
+ * @param options Optional server install options
29
76
  * @returns The status of the requirement
30
77
  */
31
78
  async checkInstallation(requirement, options) {
32
79
  try {
33
80
  const pipCmd = this.getPipCommand(options);
34
81
  const { stdout, stderr } = await this.execPromise(`${pipCmd} show ${requirement.name}`);
35
- // If we get an output and no error, the package is installed
36
- const installed = stdout.includes(requirement.name);
82
+ const installed = stdout.includes(requirement.name.toLowerCase());
37
83
  const versionMatch = stdout.match(/Version: (.+)/);
38
84
  const installedVersion = versionMatch ? versionMatch[1] : undefined;
39
85
  return {
40
86
  name: requirement.name,
41
87
  type: 'pip',
42
- installed,
88
+ installed: installed,
43
89
  version: installedVersion,
44
90
  inProgress: false,
45
- pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
91
+ pythonEnv: this.getPythonCommand(options)
46
92
  };
47
93
  }
48
94
  catch (error) {
@@ -52,70 +98,62 @@ export class PipInstaller extends BaseInstaller {
52
98
  installed: false,
53
99
  error: error instanceof Error ? error.message : String(error),
54
100
  inProgress: false,
55
- pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
101
+ pythonEnv: this.getPythonCommand(options)
56
102
  };
57
103
  }
58
104
  }
59
105
  /**
60
106
  * Install the Python package
61
107
  * @param requirement The requirement to install
108
+ * @param options Optional server install options
62
109
  * @returns The status of the installation
63
110
  */
64
111
  async install(requirement, options) {
65
112
  try {
66
113
  const status = await this.checkInstallation(requirement, options);
67
- if (status.installed) {
114
+ if (status.installed && status.version && compareVersions(status.version, requirement.version) === 0 && !requirement.version.toLowerCase().includes('latest')) {
115
+ Logger.log(`${requirement.name}==${status.version} already installed for ${this.getPythonCommand}.`);
68
116
  return status;
69
117
  }
70
118
  const pipCmd = this.getPipCommand(options);
71
- // If no registry is specified, use standard pip installation
72
119
  if (!requirement.registry) {
73
- // Standard pip installation
74
120
  const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
75
121
  if (stderr && stderr.toLowerCase().includes('error')) {
76
122
  throw new Error(stderr);
77
123
  }
78
124
  }
79
125
  else {
80
- // Handle different registry types
81
126
  let packageSource;
82
127
  if (requirement.registry.githubRelease) {
83
128
  const result = await handleGitHubRelease(requirement, requirement.registry.githubRelease);
84
129
  packageSource = result.resolvedPath;
85
- // Install from the downloaded wheel or tar.gz file
86
130
  const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
87
131
  if (stderr && stderr.toLowerCase().includes('error')) {
88
132
  throw new Error(stderr);
89
133
  }
90
134
  }
91
135
  else if (requirement.registry.artifacts) {
92
- const registryUrl = requirement.registry.artifacts.registryUrl;
93
- // Install using the custom index URL
94
- const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`);
136
+ const pythonCmd = this.getPythonCommand(options);
137
+ const adoArtifactResult = await handleAdoArtifact(requirement, requirement.registry.artifacts, pythonCmd);
138
+ const { stderr } = await this.execPromise(`${pipCmd} install ${adoArtifactResult.package} --extra-index-url ${adoArtifactResult.registryUrl}`);
95
139
  if (stderr && stderr.toLowerCase().includes('error')) {
96
- throw new Error(stderr);
97
- }
98
- }
99
- else if (requirement.registry.local) {
100
- packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
101
- // Install from the local path
102
- const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
103
- if (stderr && stderr.toLowerCase().includes('error')) {
104
- throw new Error(stderr);
140
+ const checkStatus = await this.checkInstallation(requirement, options);
141
+ if (!checkStatus.installed) {
142
+ throw new Error(`Pip installation failed with: ${stderr}`);
143
+ }
105
144
  }
106
145
  }
107
146
  else {
108
147
  throw new Error('Invalid registry configuration');
109
148
  }
110
149
  }
111
- // Store the pythonEnv in the status for future use
112
150
  return {
113
151
  name: requirement.name,
114
152
  type: 'pip',
115
153
  installed: true,
116
- version: requirement.version,
154
+ version: requirement.version, // This might need to be updated to actual installed version
117
155
  inProgress: false,
118
- pythonEnv: this.getPythonCommand(options) // Store the python env
156
+ pythonEnv: this.getPythonCommand(options)
119
157
  };
120
158
  }
121
159
  catch (error) {
@@ -125,7 +163,7 @@ export class PipInstaller extends BaseInstaller {
125
163
  installed: false,
126
164
  error: error instanceof Error ? error.message : String(error),
127
165
  inProgress: false,
128
- pythonEnv: this.getPythonCommand(options) // Store the python env
166
+ pythonEnv: this.getPythonCommand(options)
129
167
  };
130
168
  }
131
169
  }
@@ -0,0 +1,72 @@
1
+ import { FeedConfiguration } from '../types.js';
2
+ import { OperationStatus } from './OnboardStatus.js';
3
+ /**
4
+ * Service for handling feed onboarding operations
5
+ */
6
+ export declare class FeedOnboardService {
7
+ constructor();
8
+ /**
9
+ * Onboard a new feed configuration
10
+ * @param config Feed configuration to onboard
11
+ */
12
+ onboardFeed(config: FeedConfiguration, forExistingCategory?: boolean): Promise<OperationStatus & {
13
+ feedConfiguration?: FeedConfiguration;
14
+ }>;
15
+ /**
16
+ * Validate a feed configuration without performing full onboarding
17
+ * @param config Feed configuration to validate
18
+ * @returns Operation status indicating the result of the validation initiation
19
+ */
20
+ validateFeed(config: FeedConfiguration, forExistingCategory?: boolean): Promise<OperationStatus & {
21
+ feedConfiguration?: FeedConfiguration;
22
+ }>;
23
+ /**
24
+ * Initiates either a full onboarding or a validation-only operation.
25
+ * It checks for existing non-completed operations for the same feed and operation type.
26
+ * If an existing operation is found, its status is returned. Otherwise, a new operation is created and started.
27
+ * @param config The feed configuration.
28
+ * @param operationType The type of operation to initiate (FULL_ONBOARDING or VALIDATION_ONLY).
29
+ * @returns A promise that resolves to the operation status.
30
+ */
31
+ private _initiateOperation;
32
+ /**
33
+ * Finds an existing non-completed operation for a given feed name, server name, and operation type.
34
+ * @param feedName The name of the feed.
35
+ * @param operationType The type of operation.
36
+ * @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
37
+ */
38
+ private _findExistingNonCompletedOperation;
39
+ /**
40
+ * Performs static validation of the feed configuration.
41
+ * @param config The feed configuration to validate.
42
+ * @param forExistingCategory Whether this is for an existing category.
43
+ * @throws Error if validation fails.
44
+ */
45
+ private validateStaticConfig;
46
+ /**
47
+ * Validates the feed configuration.
48
+ * Updates the onboarding status to VALIDATING, then to VALIDATED or FAILED based on the validation result.
49
+ * @param onboardingId The ID of the onboarding process.
50
+ * @param config The feed configuration to validate.
51
+ * @throws Error if validation fails, to be caught by the calling process.
52
+ */
53
+ private _validateFeedConfiguration;
54
+ /**
55
+ * Processes a validation-only operation for a feed configuration.
56
+ * It validates the configuration and updates the status to SUCCEEDED if validation is successful.
57
+ * If validation fails, the error is handled by _validateFeedConfiguration and the calling _initiateOperation method.
58
+ * @param onboardingId The ID of the onboarding process.
59
+ * @param config The feed configuration.
60
+ */
61
+ private processValidationOnly;
62
+ /**
63
+ * Processes the full onboarding for a feed configuration.
64
+ * This includes validation, forking/cloning the repository, saving the config, and creating a pull request.
65
+ * Manages status updates throughout the process and handles cleanup of temporary directories.
66
+ * @param onboardingId The ID of the onboarding process.
67
+ * @param config The feed configuration.
68
+ * @throws Error if any step of the full onboarding process fails.
69
+ */
70
+ private processFullOnboarding;
71
+ }
72
+ export declare const feedOnboardService: FeedOnboardService;
@@ -0,0 +1,312 @@
1
+ import { configProvider } from '../ConfigurationProvider.js';
2
+ import { feedValidator } from '../validators/FeedValidator.js';
3
+ import { Logger } from '../../utils/logger.js';
4
+ import { OnboardingProcessStatus } from './OnboardStatus.js';
5
+ import { onboardStatusManager } from './OnboardStatusManager.js';
6
+ import { onboardProcessor } from './OnboardProcessor.js';
7
+ const NON_COMPLETED_ONBOARDING_STATUSES = [
8
+ OnboardingProcessStatus.PENDING,
9
+ OnboardingProcessStatus.VALIDATING,
10
+ OnboardingProcessStatus.VALIDATED,
11
+ OnboardingProcessStatus.PR_CREATING,
12
+ ];
13
+ /**
14
+ * Service for handling feed onboarding operations
15
+ */
16
+ export class FeedOnboardService {
17
+ // tempDir and repoDir will be generated per onboarding process
18
+ constructor() {
19
+ // No shared tempDir or repoDir at instance level
20
+ }
21
+ /**
22
+ * Onboard a new feed configuration
23
+ * @param config Feed configuration to onboard
24
+ */
25
+ async onboardFeed(config, forExistingCategory) {
26
+ // Perform static validation first
27
+ await this.validateStaticConfig(config, forExistingCategory);
28
+ const operationStatus = await this._initiateOperation(config, 'FULL_ONBOARDING', forExistingCategory);
29
+ return { ...operationStatus, feedConfiguration: config };
30
+ }
31
+ /**
32
+ * Validate a feed configuration without performing full onboarding
33
+ * @param config Feed configuration to validate
34
+ * @returns Operation status indicating the result of the validation initiation
35
+ */
36
+ async validateFeed(config, forExistingCategory) {
37
+ // Perform static validation first
38
+ await this.validateStaticConfig(config, forExistingCategory);
39
+ const operationStatus = await this._initiateOperation(config, 'VALIDATION_ONLY', forExistingCategory);
40
+ return { ...operationStatus, feedConfiguration: config };
41
+ }
42
+ /**
43
+ * Initiates either a full onboarding or a validation-only operation.
44
+ * It checks for existing non-completed operations for the same feed and operation type.
45
+ * If an existing operation is found, its status is returned. Otherwise, a new operation is created and started.
46
+ * @param config The feed configuration.
47
+ * @param operationType The type of operation to initiate (FULL_ONBOARDING or VALIDATION_ONLY).
48
+ * @returns A promise that resolves to the operation status.
49
+ */
50
+ async _initiateOperation(config, operationType, forExistingCategory) {
51
+ let existingOperation = await this._findExistingNonCompletedOperation(config.name, operationType);
52
+ if (existingOperation) {
53
+ // OnboardStatus has a lastUpdated field (ISO string)
54
+ // This field is updated by OnboardStatusManager.updateStatus
55
+ const fiveMinutesInMs = 5 * 60 * 1000;
56
+ const lastUpdateTimestamp = existingOperation.lastUpdated ? new Date(existingOperation.lastUpdated).getTime() : 0;
57
+ const currentTime = new Date().getTime();
58
+ if (lastUpdateTimestamp > 0 && (currentTime - lastUpdateTimestamp) > fiveMinutesInMs) {
59
+ Logger.log(`WARNING: [${existingOperation.onboardingId}] Found stale ${operationType} operation for feed: ${config.name} (last updated at: ${existingOperation.lastUpdated}). Proceeding to create a new operation.`);
60
+ existingOperation = undefined; // Treat as no existing operation for starting a new one
61
+ }
62
+ else {
63
+ Logger.log(`[${existingOperation.onboardingId}] Found existing non-completed ${operationType} operation for feed: ${config.name}. Returning its status.`);
64
+ return {
65
+ onboardingId: existingOperation.onboardingId,
66
+ status: existingOperation.status,
67
+ message: `An ${operationType} process for this feed (${existingOperation.onboardingId}) is already in status: ${existingOperation.status}. Current step: ${existingOperation.currentStep || 'N/A'}`,
68
+ lastQueried: new Date().toISOString(),
69
+ };
70
+ }
71
+ }
72
+ // serverName is removed
73
+ const initialStatus = await onboardStatusManager.createInitialStatus(config.name, operationType);
74
+ const onboardingId = initialStatus.onboardingId;
75
+ await onboardStatusManager.saveFeedConfiguration(onboardingId, config);
76
+ if (operationType === 'FULL_ONBOARDING') {
77
+ this.processFullOnboarding(onboardingId, config).catch(async (error) => {
78
+ Logger.error(`[${onboardingId}] Full feed onboarding process failed:`, error);
79
+ await onboardStatusManager.updateStatus(onboardingId, {
80
+ status: OnboardingProcessStatus.FAILED,
81
+ errorMessage: error instanceof Error ? error.message : String(error),
82
+ currentStep: 'Failed during full onboarding process',
83
+ prInfo: undefined,
84
+ });
85
+ });
86
+ }
87
+ else if (operationType === 'VALIDATION_ONLY') {
88
+ this.processValidationOnly(onboardingId, config).catch(async (error) => {
89
+ Logger.error(`[${onboardingId}] Feed validation process failed:`, error);
90
+ await onboardStatusManager.updateStatus(onboardingId, {
91
+ status: OnboardingProcessStatus.FAILED,
92
+ errorMessage: error instanceof Error ? error.message : String(error),
93
+ currentStep: 'Failed during validation process',
94
+ validationStatus: error.validationStatus || { isValid: false, message: error instanceof Error ? error.message : String(error) },
95
+ prInfo: undefined,
96
+ });
97
+ });
98
+ }
99
+ return {
100
+ onboardingId,
101
+ status: OnboardingProcessStatus.PENDING,
102
+ message: `New ${operationType} process started with ID: ${onboardingId}.`,
103
+ lastQueried: new Date().toISOString(),
104
+ };
105
+ }
106
+ /**
107
+ * Finds an existing non-completed operation for a given feed name, server name, and operation type.
108
+ * @param feedName The name of the feed.
109
+ * @param operationType The type of operation.
110
+ * @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
111
+ */
112
+ async _findExistingNonCompletedOperation(feedName, operationType) {
113
+ const allStatuses = await onboardStatusManager.getAllStatuses();
114
+ return Object.values(allStatuses).find((status) => status.feedName === feedName &&
115
+ // status.serverName === serverName && // serverName is removed
116
+ status.operationType === operationType &&
117
+ NON_COMPLETED_ONBOARDING_STATUSES.includes(status.status));
118
+ }
119
+ /**
120
+ * Performs static validation of the feed configuration.
121
+ * @param config The feed configuration to validate.
122
+ * @param forExistingCategory Whether this is for an existing category.
123
+ * @throws Error if validation fails.
124
+ */
125
+ async validateStaticConfig(config, forExistingCategory) {
126
+ // --- Start of moved and enhanced static validation ---
127
+ if (!config || typeof config !== 'object') { // Check if config itself is a valid object
128
+ throw new Error('Invalid configuration: Input must be a valid object.');
129
+ }
130
+ if (typeof config.name !== 'string' || !config.name.trim()) {
131
+ throw new Error('Invalid configuration: "name" must be a non-empty string.');
132
+ }
133
+ if (!Array.isArray(config.requirements)) {
134
+ throw new Error('Invalid configuration: "requirements" must be an array.');
135
+ }
136
+ if (!Array.isArray(config.mcpServers)) {
137
+ throw new Error('Invalid configuration: "mcpServers" must be an array.');
138
+ }
139
+ // --- End of moved static validation ---
140
+ const existingConfig = await configProvider.getFeedConfiguration(config.name);
141
+ if (forExistingCategory) {
142
+ if (!existingConfig) {
143
+ throw new Error(`Cannot update non-existent category: ${config.name}. Please ensure the category name is correct or create it as a new category.`);
144
+ }
145
+ // Original logic from existing validateStaticConfig:
146
+ // This implies that for an existing category, the payload must define mcpServers,
147
+ // and at least one of them must be new.
148
+ // If config.mcpServers is empty, newServersInPayload.length will be 0, leading to an error.
149
+ const existingServerNames = new Set(existingConfig.mcpServers.map((s) => s.name));
150
+ const newServersInPayload = config.mcpServers.filter((s) => !existingServerNames.has(s.name));
151
+ if (newServersInPayload.length === 0) {
152
+ // This error triggers if config.mcpServers is empty, or if all servers in config.mcpServers already exist.
153
+ throw new Error('No new servers provided for the existing category. Ensure the "mcpServers" list in your payload contains at least one server not already in this category, or that the list is not empty if you intend to add servers.');
154
+ }
155
+ }
156
+ else { // For new categories
157
+ if (existingConfig) {
158
+ throw new Error(`Category "${config.name}" already exists. To modify an existing category, please indicate that it is for an existing category.`);
159
+ }
160
+ if (!config.mcpServers || config.mcpServers.length === 0) {
161
+ throw new Error('Server configuration ("mcpServers") must be provided and non-empty for new categories.');
162
+ }
163
+ }
164
+ }
165
+ /**
166
+ * Validates the feed configuration.
167
+ * Updates the onboarding status to VALIDATING, then to VALIDATED or FAILED based on the validation result.
168
+ * @param onboardingId The ID of the onboarding process.
169
+ * @param config The feed configuration to validate.
170
+ * @throws Error if validation fails, to be caught by the calling process.
171
+ */
172
+ async _validateFeedConfiguration(onboardingId, config) {
173
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.VALIDATING, currentStep: 'Validating feed configuration' });
174
+ let overallValidationStatus = { isValid: true, serverResults: [] };
175
+ try {
176
+ feedValidator.validate(config);
177
+ // Then, validate each MCP server
178
+ for (const server of config.mcpServers) {
179
+ try {
180
+ await feedValidator.validateServer(server, config);
181
+ overallValidationStatus.serverResults?.push({ serverName: server.name, isValid: true });
182
+ await onboardStatusManager.updateStatus(onboardingId, {
183
+ currentStep: `Validated server: ${server.name}`,
184
+ validationStatus: overallValidationStatus // Update with intermediate results
185
+ });
186
+ }
187
+ catch (serverValidationError) {
188
+ overallValidationStatus.isValid = false;
189
+ overallValidationStatus.serverResults?.push({
190
+ serverName: server.name,
191
+ isValid: false,
192
+ message: serverValidationError.message || String(serverValidationError)
193
+ });
194
+ // Update status immediately upon first server validation failure
195
+ await onboardStatusManager.updateStatus(onboardingId, {
196
+ status: OnboardingProcessStatus.FAILED,
197
+ currentStep: `Validation failed for server: ${server.name}`,
198
+ errorMessage: `Server ${server.name}: ${serverValidationError.message || String(serverValidationError)}`,
199
+ validationStatus: overallValidationStatus
200
+ });
201
+ throw serverValidationError; // Propagate the error to fail the whole validation
202
+ }
203
+ }
204
+ // If all servers validated successfully
205
+ await onboardStatusManager.updateStatus(onboardingId, {
206
+ status: OnboardingProcessStatus.VALIDATED,
207
+ currentStep: 'All servers in feed configuration validated successfully',
208
+ validationStatus: overallValidationStatus
209
+ });
210
+ }
211
+ catch (error) {
212
+ const validationError = error; // Assuming error might have validation details
213
+ // This block will now primarily catch errors from the main feedValidator.validate(config)
214
+ // or if a server validation error was re-thrown and not caught by the loop's catch.
215
+ await onboardStatusManager.updateStatus(onboardingId, {
216
+ status: OnboardingProcessStatus.FAILED,
217
+ currentStep: 'Feed configuration validation failed',
218
+ errorMessage: validationError.message || String(error),
219
+ validationStatus: {
220
+ isValid: false,
221
+ message: validationError.message || String(error),
222
+ serverResults: overallValidationStatus.serverResults // Include any server results gathered so far
223
+ }
224
+ });
225
+ throw error; // Re-throw to be caught by the calling process
226
+ }
227
+ }
228
+ /**
229
+ * Processes a validation-only operation for a feed configuration.
230
+ * It validates the configuration and updates the status to SUCCEEDED if validation is successful.
231
+ * If validation fails, the error is handled by _validateFeedConfiguration and the calling _initiateOperation method.
232
+ * @param onboardingId The ID of the onboarding process.
233
+ * @param config The feed configuration.
234
+ */
235
+ async processValidationOnly(onboardingId, config, forExistingCategory) {
236
+ try {
237
+ await this._validateFeedConfiguration(onboardingId, config);
238
+ // _validateFeedConfiguration now handles setting VALIDATED or FAILED.
239
+ // If it completes without throwing, it means validation was successful.
240
+ const finalStatus = await onboardStatusManager.getStatus(onboardingId);
241
+ const result = {
242
+ validationStatus: finalStatus?.validationStatus || { isValid: true, serverResults: [] }, // Get the detailed status
243
+ feedConfiguration: config
244
+ };
245
+ await onboardStatusManager.updateStatus(onboardingId, {
246
+ status: OnboardingProcessStatus.SUCCEEDED, // Mark as SUCCEEDED for VALIDATION_ONLY
247
+ currentStep: 'Feed configuration validated successfully',
248
+ errorMessage: undefined,
249
+ prInfo: undefined,
250
+ result: result
251
+ });
252
+ Logger.log(`[${onboardingId}] Successfully validated feed: ${config.name}`);
253
+ }
254
+ catch (error) {
255
+ // Error handling is done within _validateFeedConfiguration and by the caller of processValidationOnly
256
+ Logger.error(`[${onboardingId}] Feed validation process failed for ${config.name}:`, error);
257
+ // No need to re-throw here as the caller's catch block will handle it.
258
+ }
259
+ }
260
+ /**
261
+ * Processes the full onboarding for a feed configuration.
262
+ * This includes validation, forking/cloning the repository, saving the config, and creating a pull request.
263
+ * Manages status updates throughout the process and handles cleanup of temporary directories.
264
+ * @param onboardingId The ID of the onboarding process.
265
+ * @param config The feed configuration.
266
+ * @throws Error if any step of the full onboarding process fails.
267
+ */
268
+ async processFullOnboarding(onboardingId, config, forExistingCategory) {
269
+ const { tempDir, repoDir } = onboardProcessor.createDirectories(onboardingId);
270
+ try {
271
+ await this._validateFeedConfiguration(onboardingId, config);
272
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Forking repository' });
273
+ await onboardProcessor.forkRepo(onboardingId, repoDir);
274
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Cloning repository' });
275
+ await onboardProcessor.cloneRepo(onboardingId, tempDir, repoDir);
276
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Setting up branch' });
277
+ const branchName = await onboardProcessor.setupBranch(onboardingId, config, repoDir);
278
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Saving feed configuration to repository' });
279
+ await onboardProcessor.saveFeedConfigToRepo(onboardingId, config, repoDir);
280
+ await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Creating pull request' });
281
+ const prInfo = await onboardProcessor.createPullRequest(onboardingId, config, repoDir, branchName);
282
+ await onboardStatusManager.updateStatus(onboardingId, {
283
+ status: OnboardingProcessStatus.SUCCEEDED,
284
+ currentStep: 'Successfully onboarded',
285
+ errorMessage: undefined,
286
+ prInfo: prInfo,
287
+ result: prInfo.url
288
+ });
289
+ Logger.log(`[${onboardingId}] Successfully onboarded feed: ${config.name}`);
290
+ }
291
+ catch (error) {
292
+ Logger.error(`[${onboardingId}] Full feed onboarding failed:`, error);
293
+ const errorMessage = error instanceof Error ? error.message : String(error);
294
+ let currentStep = 'Failed during full onboarding process';
295
+ if (error instanceof Error && 'step' in error) {
296
+ currentStep = `Failed at step: ${error.step}`;
297
+ }
298
+ else if ((await onboardStatusManager.getStatus(onboardingId))?.currentStep?.includes('validation failed')) {
299
+ throw error;
300
+ }
301
+ await onboardStatusManager.updateStatus(onboardingId, {
302
+ status: OnboardingProcessStatus.FAILED,
303
+ errorMessage: errorMessage,
304
+ currentStep: currentStep,
305
+ });
306
+ throw error;
307
+ }
308
+ }
309
+ }
310
+ // Export singleton instance
311
+ export const feedOnboardService = new FeedOnboardService();
312
+ //# sourceMappingURL=FeedOnboardService.js.map
@@ -0,0 +1,79 @@
1
+ import { FeedConfiguration } from '../types.js';
2
+ /**
3
+ * Class responsible for handling GitHub operations during the onboarding process
4
+ */
5
+ export declare class OnboardProcessor {
6
+ /**
7
+ * Forks the GitHub repository specified in GITHUB_REPO constants.
8
+ * Uses the `gh repo fork` command.
9
+ * @param onboardingId The ID of the onboarding process (for logging).
10
+ * @param repoDir The directory of the repository (currently unused but kept for consistency).
11
+ * @throws An error if the forking process fails, with a 'step' property set to 'forkRepo'.
12
+ */
13
+ forkRepo(onboardingId: string, repoDir: string): Promise<void>;
14
+ /**
15
+ * Clones the user's fork of the repository into a temporary directory.
16
+ * It first removes any existing repository directory for the current onboarding process.
17
+ * Then, it retrieves the GitHub username and constructs the repository URL to clone.
18
+ * @param onboardingId The ID of the onboarding process (for logging).
19
+ * @param tempDir The base temporary directory for the onboarding process.
20
+ * @param repoDir The specific directory within tempDir where the repository will be cloned.
21
+ * @throws An error if cloning fails, with a 'step' property set to 'cloneRepo'.
22
+ */
23
+ cloneRepo(onboardingId: string, tempDir: string, repoDir: string): Promise<void>;
24
+ /**
25
+ * Retrieves the GitHub username of the authenticated user using the `gh api user` command.
26
+ * @returns A promise that resolves to the GitHub username as a string.
27
+ * @throws An error if fetching the username fails.
28
+ */
29
+ private getGitHubUsername;
30
+ /**
31
+ * Saves the feed configuration as a JSON file into the specified path within the cloned repository.
32
+ * The file will be named `config.name.json`.
33
+ * @param onboardingId The ID of the onboarding process.
34
+ * @param config The feed configuration to save.
35
+ * @param repoDir The root directory of the cloned repository.
36
+ * @throws An error if saving the configuration fails, with a 'step' property set to 'saveFeedConfigToRepo'.
37
+ */
38
+ saveFeedConfigToRepo(onboardingId: string, config: FeedConfiguration, repoDir: string): Promise<void>;
39
+ /**
40
+ * Creates a pull request on GitHub for the newly added feed configuration.
41
+ * @param onboardingId The ID of the onboarding process.
42
+ * @param config The feed configuration, used for branch name and PR details.
43
+ * @param repoDir The root directory of the cloned repository.
44
+ * @returns A promise that resolves to an object containing the URL of the created PR and the branch name.
45
+ * @throws An error if any step of the PR creation process fails.
46
+ */
47
+ /**
48
+ * Sets up a clean branch for the feed configuration changes.
49
+ * @param onboardingId The ID of the onboarding process.
50
+ * @param config The feed configuration.
51
+ * @param repoDir The root directory of the cloned repository.
52
+ * @returns The name of the created branch.
53
+ * @throws An error if branch setup fails.
54
+ */
55
+ setupBranch(onboardingId: string, config: FeedConfiguration, repoDir: string): Promise<string>;
56
+ /**
57
+ * Creates a pull request for the feed configuration changes.
58
+ * @param onboardingId The ID of the onboarding process.
59
+ * @param config The feed configuration.
60
+ * @param repoDir The root directory of the cloned repository.
61
+ * @param branchName The name of the branch to create the PR from.
62
+ * @returns Object containing the PR URL and branch name.
63
+ * @throws An error if PR creation fails.
64
+ */
65
+ createPullRequest(onboardingId: string, config: FeedConfiguration, repoDir: string, branchName: string): Promise<{
66
+ url: string;
67
+ branchName: string;
68
+ }>;
69
+ /**
70
+ * Creates necessary directories for the onboarding process
71
+ * @param onboardingId The ID of the onboarding process
72
+ * @returns Object containing tempDir and repoDir paths
73
+ */
74
+ createDirectories(onboardingId: string): {
75
+ tempDir: string;
76
+ repoDir: string;
77
+ };
78
+ }
79
+ export declare const onboardProcessor: OnboardProcessor;