imcp 0.0.16 → 0.0.17
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/cli/commands/install.js +2 -2
- package/dist/cli/commands/list.js +2 -2
- package/dist/cli/commands/serve.js +1 -1
- package/dist/core/RequirementService.d.ts +0 -12
- package/dist/core/RequirementService.js +0 -24
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClientInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClientInstaller.js +1 -1
- package/dist/core/installers/clients/ClientInstallerFactory.js +1 -1
- package/dist/core/installers/clients/ClineInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClineInstaller.js +1 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +1 -1
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -1
- package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -1
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -1
- package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -1
- package/dist/core/installers/requirements/BaseInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/BaseInstaller.js +1 -1
- package/dist/core/installers/requirements/CommandInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/CommandInstaller.js +1 -1
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/InstallerFactory.d.ts +1 -1
- package/dist/core/installers/requirements/NpmInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/NpmInstaller.js +1 -1
- package/dist/core/installers/requirements/PipInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/RequirementInstaller.d.ts +1 -1
- package/dist/core/loaders/ConfigurationLoader.d.ts +32 -0
- package/dist/core/loaders/ConfigurationLoader.js +236 -0
- package/dist/core/loaders/ConfigurationProvider.d.ts +35 -0
- package/dist/core/loaders/ConfigurationProvider.js +375 -0
- package/dist/core/loaders/ServerSchemaLoader.d.ts +11 -0
- package/{src/core/ServerSchemaLoader.ts → dist/core/loaders/ServerSchemaLoader.js} +43 -48
- package/dist/core/loaders/ServerSchemaProvider.d.ts +17 -0
- package/{src/core/ServerSchemaProvider.ts → dist/core/loaders/ServerSchemaProvider.js} +120 -137
- package/dist/core/metadatas/constants.d.ts +47 -0
- package/dist/core/metadatas/constants.js +94 -0
- package/dist/core/metadatas/types.d.ts +166 -0
- package/dist/core/metadatas/types.js +16 -0
- package/dist/core/onboard/FeedOnboardService.d.ts +1 -1
- package/dist/core/onboard/FeedOnboardService.js +1 -1
- package/dist/core/onboard/OnboardProcessor.d.ts +1 -1
- package/dist/core/onboard/OnboardProcessor.js +1 -1
- package/dist/core/onboard/OnboardStatus.d.ts +1 -1
- package/dist/core/onboard/OnboardStatusManager.d.ts +1 -1
- package/dist/core/onboard/OnboardStatusManager.js +1 -1
- package/dist/core/validators/FeedValidator.d.ts +1 -1
- package/dist/core/validators/IServerValidator.d.ts +1 -1
- package/dist/core/validators/SSEServerValidator.d.ts +1 -1
- package/dist/core/validators/ServerValidatorFactory.d.ts +1 -1
- package/dist/core/validators/StdioServerValidator.d.ts +1 -1
- package/dist/core/validators/StdioServerValidator.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/services/InstallationService.d.ts +50 -0
- package/dist/services/InstallationService.js +350 -0
- package/dist/services/MCPManager.d.ts +28 -0
- package/dist/services/MCPManager.js +188 -0
- package/dist/services/RequirementService.d.ts +40 -0
- package/dist/services/RequirementService.js +110 -0
- package/dist/services/ServerService.d.ts +2 -2
- package/dist/services/ServerService.js +5 -5
- package/dist/utils/adoUtils.d.ts +2 -2
- package/dist/utils/adoUtils.js +1 -1
- package/dist/utils/feedUtils.js +1 -1
- package/dist/utils/githubUtils.d.ts +1 -1
- package/dist/utils/githubUtils.js +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/macroExpressionUtils.d.ts +1 -1
- package/dist/utils/osUtils.d.ts +1 -1
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/contract/serverContract.d.ts +1 -1
- package/dist/web/public/index.html +1 -3
- package/dist/web/public/js/api.js +2 -80
- package/dist/web/server.js +2 -2
- package/package.json +1 -1
- package/src/cli/commands/install.ts +3 -3
- package/src/cli/commands/list.ts +2 -2
- package/src/cli/commands/serve.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/core/installers/clients/BaseClientInstaller.ts +134 -3
- package/src/core/installers/clients/ClientInstaller.ts +3 -3
- package/src/core/installers/clients/ClientInstallerFactory.ts +1 -1
- package/src/core/installers/clients/ClineInstaller.ts +1 -101
- package/src/core/installers/clients/ExtensionInstaller.ts +1 -1
- package/src/core/installers/clients/GithubCopilotInstaller.ts +1 -101
- package/src/core/installers/clients/MSRooCodeInstaller.ts +1 -102
- package/src/core/installers/requirements/BaseInstaller.ts +2 -2
- package/src/core/installers/requirements/CommandInstaller.ts +1 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +1 -1
- package/src/core/installers/requirements/InstallerFactory.ts +1 -1
- package/src/core/installers/requirements/NpmInstaller.ts +12 -12
- package/src/core/installers/requirements/PipInstaller.ts +1 -1
- package/src/core/installers/requirements/RequirementInstaller.ts +1 -1
- package/src/core/{ConfigurationLoader.ts → loaders/ConfigurationLoader.ts} +31 -7
- package/src/core/{ConfigurationProvider.ts → loaders/ConfigurationProvider.ts} +18 -10
- package/src/core/loaders/ServerSchemaLoader.ts +117 -0
- package/src/core/loaders/ServerSchemaProvider.ts +99 -0
- package/src/core/{types.ts → metadatas/types.ts} +3 -2
- package/src/core/onboard/FeedOnboardService.ts +270 -146
- package/src/core/onboard/OnboardProcessor.ts +60 -11
- package/src/core/onboard/OnboardStatus.ts +7 -2
- package/src/core/onboard/OnboardStatusManager.ts +270 -43
- package/src/core/validators/FeedValidator.ts +65 -9
- package/src/core/validators/IServerValidator.ts +1 -1
- package/src/core/validators/SSEServerValidator.ts +2 -2
- package/src/core/validators/ServerValidatorFactory.ts +1 -1
- package/src/core/validators/StdioServerValidator.ts +86 -34
- package/src/index.ts +3 -3
- package/src/{core → services}/InstallationService.ts +5 -5
- package/src/{core → services}/MCPManager.ts +10 -5
- package/src/{core → services}/RequirementService.ts +2 -31
- package/src/services/ServerService.ts +7 -7
- package/src/utils/adoUtils.ts +3 -3
- package/src/utils/feedUtils.ts +2 -2
- package/src/utils/githubUtils.ts +2 -2
- package/src/utils/logger.ts +13 -1
- package/src/utils/macroExpressionUtils.ts +1 -1
- package/src/utils/osUtils.ts +4 -4
- package/src/web/contract/serverContract.ts +2 -2
- package/src/web/public/index.html +1 -3
- package/src/web/public/js/api.js +2 -80
- package/src/web/public/js/modal/installation.js +1 -1
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
- package/src/web/public/js/onboard/formProcessor.js +200 -34
- package/src/web/public/js/onboard/index.js +2 -2
- package/src/web/public/js/onboard/publishHandler.js +30 -22
- package/src/web/public/js/onboard/templates.js +34 -40
- package/src/web/public/js/onboard/uiHandlers.js +175 -84
- package/src/web/public/js/onboard/validationHandlers.js +147 -64
- package/src/web/public/js/serverCategoryDetails.js +19 -4
- package/src/web/public/js/serverCategoryList.js +13 -1
- package/src/web/public/onboard.html +1 -1
- package/src/web/server.ts +30 -14
- package/src/services/InstallRequestValidator.ts +0 -112
- /package/src/core/{constants.ts → metadatas/constants.ts} +0 -0
|
@@ -2,8 +2,8 @@ import { exec } from 'child_process';
|
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { GITHUB_REPO, SETTINGS_DIR } from '../constants.js';
|
|
6
|
-
import { FeedConfiguration } from '../types.js';
|
|
5
|
+
import { GITHUB_REPO, SETTINGS_DIR } from '../metadatas/constants.js';
|
|
6
|
+
import { FeedConfiguration } from '../metadatas/types.js';
|
|
7
7
|
import { Logger } from '../../utils/logger.js';
|
|
8
8
|
|
|
9
9
|
const execAsync = promisify(exec);
|
|
@@ -110,16 +110,63 @@ export class OnboardProcessor {
|
|
|
110
110
|
* @param onboardingId The ID of the onboarding process.
|
|
111
111
|
* @param config The feed configuration to save.
|
|
112
112
|
* @param repoDir The root directory of the cloned repository.
|
|
113
|
+
* @param serverList The list of MCP server names to process.
|
|
114
|
+
* @returns A promise that resolves to an object containing the path to the saved feed file and the category-specific schemas directory.
|
|
113
115
|
* @throws An error if saving the configuration fails, with a 'step' property set to 'saveFeedConfigToRepo'.
|
|
114
116
|
*/
|
|
115
|
-
public async saveFeedConfigToRepo(onboardingId: string, config: FeedConfiguration, repoDir: string): Promise<
|
|
117
|
+
public async saveFeedConfigToRepo(onboardingId: string, config: FeedConfiguration, repoDir: string, serverList: string[]): Promise<{ feedFilePath: string; categorySchemasPath: string; }> {
|
|
116
118
|
try {
|
|
117
119
|
const feedsDir = path.join(repoDir, GITHUB_REPO.feedsPath);
|
|
118
120
|
await fs.mkdir(feedsDir, { recursive: true });
|
|
119
121
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const categoryName = config.name; // Feed name is the category name
|
|
123
|
+
const categorySchemasPath = path.join(feedsDir, 'schemas', categoryName);
|
|
124
|
+
await fs.mkdir(categorySchemasPath, { recursive: true }); // Ensure category schema directory exists
|
|
125
|
+
|
|
126
|
+
// Process schemas for each MCP server
|
|
127
|
+
// Create a mutable copy of mcpServers to update schema paths
|
|
128
|
+
const updatedMcpServers = [];
|
|
129
|
+
for (const server of config.mcpServers) {
|
|
130
|
+
if (!serverList.includes(server.name)) continue;
|
|
131
|
+
let updatedServer = { ...server }; // Shallow copy server config
|
|
132
|
+
if (updatedServer.schemas && typeof updatedServer.schemas === 'string' && updatedServer.schemas.trim() !== '') {
|
|
133
|
+
const originalSchemaPath = updatedServer.schemas;
|
|
134
|
+
const serverName = updatedServer.name;
|
|
135
|
+
const newSchemaFileName = `${serverName}.json`;
|
|
136
|
+
|
|
137
|
+
// Schemas are now directly under categorySchemasPath
|
|
138
|
+
const newSchemaPathInRepo = path.join(categorySchemasPath, newSchemaFileName);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Read content from the original schema path
|
|
142
|
+
const schemaContent = await fs.readFile(originalSchemaPath, 'utf-8');
|
|
143
|
+
// Write content to the new schema file in the repo
|
|
144
|
+
await fs.writeFile(newSchemaPathInRepo, schemaContent);
|
|
145
|
+
Logger.debug(`[${onboardingId}] Copied schema for server '${serverName}' from '${originalSchemaPath}' to '${newSchemaPathInRepo}'`);
|
|
146
|
+
|
|
147
|
+
// Update the schemas property to the new filename, as per instruction "rename schemas in McpServer as serverName].json"
|
|
148
|
+
updatedServer.schemas = newSchemaFileName;
|
|
149
|
+
} catch (schemaError) {
|
|
150
|
+
const errorMsg = `Error processing schema for server '${serverName}' (source: ${originalSchemaPath}): ${schemaError instanceof Error ? schemaError.message : String(schemaError)}`;
|
|
151
|
+
Logger.error(`[${onboardingId}] ${errorMsg}`);
|
|
152
|
+
// Propagate the error to fail the onboarding step
|
|
153
|
+
throw new Error(errorMsg);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
updatedMcpServers.push(updatedServer);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create a new config object with the updated mcpServers
|
|
160
|
+
const processedConfig = {
|
|
161
|
+
...config,
|
|
162
|
+
mcpServers: updatedMcpServers
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const feedFilePath = path.join(feedsDir, `${config.name}.json`);
|
|
166
|
+
await fs.writeFile(feedFilePath, JSON.stringify(processedConfig, null, 2));
|
|
167
|
+
Logger.debug(`[${onboardingId}] Saved feed configuration (with processed schemas) to ${feedFilePath}`);
|
|
168
|
+
|
|
169
|
+
return { feedFilePath, categorySchemasPath };
|
|
123
170
|
} catch (error) {
|
|
124
171
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
125
172
|
Logger.error(`[${onboardingId}] Error during saveFeedConfigToRepo: ${errorMessage}`);
|
|
@@ -160,7 +207,7 @@ export class OnboardProcessor {
|
|
|
160
207
|
Logger.debug(`[${onboardingId}] Fetched from upstream.`);
|
|
161
208
|
|
|
162
209
|
try {
|
|
163
|
-
await execAsync('git checkout main');
|
|
210
|
+
await execAsync('git checkout main --force');
|
|
164
211
|
Logger.debug(`[${onboardingId}] Switched to existing local branch 'main'.`);
|
|
165
212
|
} catch (checkoutMainError: any) {
|
|
166
213
|
const errStdErr = checkoutMainError.stderr || '';
|
|
@@ -169,7 +216,7 @@ export class OnboardProcessor {
|
|
|
169
216
|
errStdErr.includes('pathspec \'main\' did not match any file(s) known to git')) {
|
|
170
217
|
Logger.debug(`[${onboardingId}] Local branch 'main' not found or invalid. Attempting to create it from 'upstream/main'.`);
|
|
171
218
|
try {
|
|
172
|
-
await execAsync('git checkout -b main upstream/main');
|
|
219
|
+
await execAsync('git checkout -b main --force upstream/main');
|
|
173
220
|
Logger.debug(`[${onboardingId}] Created and switched to local branch 'main' from 'upstream/main'.`);
|
|
174
221
|
} catch (createMainError: any) {
|
|
175
222
|
const errMsg = createMainError instanceof Error ? createMainError.message : String(createMainError);
|
|
@@ -221,7 +268,7 @@ export class OnboardProcessor {
|
|
|
221
268
|
* @returns Object containing the PR URL and branch name.
|
|
222
269
|
* @throws An error if PR creation fails.
|
|
223
270
|
*/
|
|
224
|
-
public async createPullRequest(onboardingId: string, config: FeedConfiguration, repoDir: string, branchName: string): Promise<{ url: string; branchName: string }> {
|
|
271
|
+
public async createPullRequest(onboardingId: string, config: FeedConfiguration, repoDir: string, branchName: string): Promise<{ url: string; branchName: string; prExists: boolean }> {
|
|
225
272
|
try {
|
|
226
273
|
process.chdir(repoDir);
|
|
227
274
|
|
|
@@ -251,19 +298,21 @@ export class OnboardProcessor {
|
|
|
251
298
|
const username = await this.getGitHubUsername();
|
|
252
299
|
const explicitHeadRef = `${username}:${branchName}`;
|
|
253
300
|
|
|
254
|
-
const prCheckCommand = `gh pr list --repo ${GITHUB_REPO.repoName} --head
|
|
301
|
+
const prCheckCommand = `gh pr list --repo ${GITHUB_REPO.repoName} --head ${branchName} --author ${username} --state open --json url --limit 1`;
|
|
255
302
|
Logger.debug(`[${onboardingId}] Checking for existing PR with command: ${prCheckCommand}`);
|
|
256
303
|
const { stdout: existingPrJson } = await execAsync(prCheckCommand);
|
|
257
304
|
|
|
258
305
|
let prUrlToReturn: string;
|
|
259
306
|
|
|
260
307
|
const createPRCommand = `gh pr create --repo ${GITHUB_REPO.repoName} --base main --head ${explicitHeadRef} --title "${title}" --body "${body}"`
|
|
308
|
+
let prExists = false;
|
|
261
309
|
|
|
262
310
|
if (existingPrJson && existingPrJson.trim() !== '[]' && existingPrJson.trim() !== '') {
|
|
263
311
|
try {
|
|
264
312
|
const prs = JSON.parse(existingPrJson);
|
|
265
313
|
if (prs.length > 0 && prs[0].url) {
|
|
266
314
|
prUrlToReturn = prs[0].url;
|
|
315
|
+
prExists = true;
|
|
267
316
|
Logger.log(`[${onboardingId}] Found existing open PR for ${explicitHeadRef} on ${GITHUB_REPO.repoName}: ${prUrlToReturn}`);
|
|
268
317
|
} else {
|
|
269
318
|
Logger.debug(`[${onboardingId}] No existing PR found or unable to parse PR list JSON. Proceeding to create PR.`);
|
|
@@ -282,7 +331,7 @@ export class OnboardProcessor {
|
|
|
282
331
|
}
|
|
283
332
|
|
|
284
333
|
Logger.debug(`[${onboardingId}] Pull request operation completed. PR URL: ${prUrlToReturn}`);
|
|
285
|
-
return { url: prUrlToReturn, branchName };
|
|
334
|
+
return { url: prUrlToReturn, branchName: branchName, prExists: prExists };
|
|
286
335
|
} catch (error) {
|
|
287
336
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
288
337
|
Logger.error(`[${onboardingId}] Error during createPullRequest: ${errorMessage}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FeedConfiguration } from "../types.js";
|
|
1
|
+
import { FeedConfiguration } from "../metadatas/types.js";
|
|
2
2
|
|
|
3
3
|
export enum OnboardingProcessStatus {
|
|
4
4
|
VALIDATING = 'validating',
|
|
@@ -20,7 +20,12 @@ export interface ServerValidationResult {
|
|
|
20
20
|
export interface OnboardStatus {
|
|
21
21
|
onboardingId: string;
|
|
22
22
|
status: OnboardingProcessStatus;
|
|
23
|
-
|
|
23
|
+
steps?: {
|
|
24
|
+
stepName: string;
|
|
25
|
+
timestamp: string;
|
|
26
|
+
status?: OnboardingProcessStatus; // Status of this specific step, if different from overall
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
}[];
|
|
24
29
|
validationStatus?: {
|
|
25
30
|
isValid: boolean;
|
|
26
31
|
message?: string;
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { SETTINGS_DIR } from '../constants.js';
|
|
4
|
-
import { FeedConfiguration } from '../types.js';
|
|
3
|
+
import { SETTINGS_DIR } from '../metadatas/constants.js';
|
|
4
|
+
import { FeedConfiguration, McpConfig } from '../metadatas/types.js';
|
|
5
5
|
import { OnboardStatus, OnboardingProcessStatus, OperationType } from './OnboardStatus.js';
|
|
6
6
|
import { Logger } from '../../utils/logger.js';
|
|
7
7
|
|
|
8
8
|
const ONBOARD_STATUS_DIR = path.join(SETTINGS_DIR, 'onboard');
|
|
9
|
-
const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, '
|
|
9
|
+
const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, 'OnboardStatus.json');
|
|
10
10
|
const FEED_CONFIG_DIR = path.join(ONBOARD_STATUS_DIR, 'feed_configs'); // Staging for feed configs during operation
|
|
11
11
|
|
|
12
|
+
const NON_COMPLETED_ONBOARDING_STATUSES: OnboardingProcessStatus[] = [
|
|
13
|
+
OnboardingProcessStatus.PENDING,
|
|
14
|
+
OnboardingProcessStatus.VALIDATING,
|
|
15
|
+
OnboardingProcessStatus.VALIDATED,
|
|
16
|
+
OnboardingProcessStatus.PR_CREATING,
|
|
17
|
+
];
|
|
18
|
+
|
|
12
19
|
export class OnboardStatusManager {
|
|
13
20
|
private static instance: OnboardStatusManager;
|
|
14
|
-
private activeCategoryOperations: Record<string, OnboardStatus
|
|
21
|
+
private activeCategoryOperations: Record<string, Partial<Record<OperationType, OnboardStatus>>> = {};
|
|
15
22
|
private statusLock: Promise<void> = Promise.resolve();
|
|
16
23
|
|
|
17
24
|
private constructor() {
|
|
@@ -67,12 +74,13 @@ export class OnboardStatusManager {
|
|
|
67
74
|
* @param categoryName The name of the category (acting as operationId).
|
|
68
75
|
* @param config The FeedConfiguration to save.
|
|
69
76
|
*/
|
|
70
|
-
public async saveFeedConfiguration(categoryName: string, config: FeedConfiguration): Promise<void> {
|
|
77
|
+
public async saveFeedConfiguration(categoryName: string, operationType: OperationType, config: FeedConfiguration): Promise<void> {
|
|
71
78
|
await this.withLock(async () => {
|
|
72
79
|
try {
|
|
73
80
|
await fs.mkdir(FEED_CONFIG_DIR, { recursive: true });
|
|
74
81
|
// Suffix `_feed.json` to distinguish from potential status files if names overlap
|
|
75
|
-
const
|
|
82
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
83
|
+
const configPath = path.join(FEED_CONFIG_DIR, `${operationId}_feed.json`);
|
|
76
84
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
77
85
|
Logger.debug(`Saved feed configuration for category ${categoryName} to ${configPath}`);
|
|
78
86
|
} catch (error) {
|
|
@@ -87,10 +95,11 @@ export class OnboardStatusManager {
|
|
|
87
95
|
* @param categoryName The name of the category (acting as operationId).
|
|
88
96
|
* @returns A promise that resolves to the FeedConfiguration, or undefined if not found.
|
|
89
97
|
*/
|
|
90
|
-
public async getFeedConfiguration(categoryName: string): Promise<FeedConfiguration | undefined> {
|
|
98
|
+
public async getFeedConfiguration(categoryName: string, operationType: OperationType): Promise<FeedConfiguration | undefined> {
|
|
91
99
|
return await this.withLock(async () => {
|
|
92
100
|
try {
|
|
93
|
-
const
|
|
101
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
102
|
+
const configPath = path.join(FEED_CONFIG_DIR, `${operationId}_feed.json`);
|
|
94
103
|
const data = await fs.readFile(configPath, 'utf-8');
|
|
95
104
|
return JSON.parse(data) as FeedConfiguration;
|
|
96
105
|
} catch (error) {
|
|
@@ -106,56 +115,180 @@ export class OnboardStatusManager {
|
|
|
106
115
|
|
|
107
116
|
|
|
108
117
|
/**
|
|
109
|
-
* Retrieves the status of a
|
|
110
|
-
* @param categoryName The name of the category
|
|
111
|
-
* @
|
|
118
|
+
* Retrieves the status of a specific operation for a category.
|
|
119
|
+
* @param categoryName The name of the category.
|
|
120
|
+
* @param operationType The type of operation.
|
|
121
|
+
* @returns A promise that resolves to the OnboardStatus, or undefined if no such operation or category.
|
|
112
122
|
*/
|
|
113
|
-
public async getStatus(categoryName: string): Promise<OnboardStatus | undefined> {
|
|
114
|
-
await this.loadStatuses(); // Ensure latest statuses are loaded
|
|
115
|
-
return this.activeCategoryOperations[categoryName];
|
|
123
|
+
public async getStatus(categoryName: string, operationType: OperationType): Promise<OnboardStatus | undefined> {
|
|
124
|
+
await this.loadStatuses(); // Ensure latest statuses are loaded
|
|
125
|
+
return this.activeCategoryOperations[categoryName]?.[operationType];
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
/**
|
|
119
|
-
* Retrieves all active category operation statuses.
|
|
120
|
-
* @returns A promise that resolves to a record of all active OnboardStatus objects
|
|
129
|
+
* Retrieves all active category operation statuses, structured by category and then by operation type.
|
|
130
|
+
* @returns A promise that resolves to a record of all active OnboardStatus objects.
|
|
121
131
|
*/
|
|
122
|
-
public async getAllStatuses(): Promise<Record<string, OnboardStatus
|
|
132
|
+
public async getAllStatuses(): Promise<Record<string, Partial<Record<OperationType, OnboardStatus>>>> {
|
|
123
133
|
await this.loadStatuses();
|
|
124
134
|
return this.activeCategoryOperations;
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
/**
|
|
128
|
-
* Updates the status of a
|
|
129
|
-
* If
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @param
|
|
133
|
-
* @param
|
|
138
|
+
* Updates the status of a specific operation for a category.
|
|
139
|
+
* If a `stepName` is provided in `updates`, it will also add a new step.
|
|
140
|
+
* @param categoryName The name of the category.
|
|
141
|
+
* @param operationType The type of operation.
|
|
142
|
+
* @param updates Partial OnboardStatus object with fields to update. Can include `stepName` to record a new step.
|
|
143
|
+
* @param stepName Optional. If provided, a new step with this name will be recorded.
|
|
144
|
+
* @param stepStatus Optional. Status for the new step, if `stepName` is provided.
|
|
145
|
+
* @param errorMessage Optional. Error message for the new step, if `stepName` is provided.
|
|
134
146
|
* @returns A promise that resolves to the updated OnboardStatus.
|
|
135
147
|
*/
|
|
136
|
-
public async updateStatus(
|
|
148
|
+
public async updateStatus(
|
|
149
|
+
categoryName: string,
|
|
150
|
+
operationType: OperationType,
|
|
151
|
+
updates: Partial<Omit<OnboardStatus, 'steps'>>, // Exclude steps from direct updates here
|
|
152
|
+
stepName?: string,
|
|
153
|
+
stepStatus?: OnboardingProcessStatus,
|
|
154
|
+
errorMessage?: string
|
|
155
|
+
): Promise<OnboardStatus> {
|
|
137
156
|
return await this.withLock(async () => {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
status: OnboardingProcessStatus.PENDING, // Default status if creating new
|
|
142
|
-
lastUpdated: new Date().toISOString(),
|
|
143
|
-
operationType: updates.operationType || 'FULL_ONBOARDING', // Default operation type
|
|
144
|
-
...updates // Apply initial updates if creating new
|
|
145
|
-
};
|
|
157
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
158
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
159
|
+
let existingStatus = categoryOps[operationType];
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
if (!existingStatus) {
|
|
162
|
+
// If no status exists, create an initial one.
|
|
163
|
+
// This might happen if updateStatus is called before createInitialStatus,
|
|
164
|
+
// though ideally createInitialStatus should be the entry point.
|
|
165
|
+
existingStatus = {
|
|
166
|
+
onboardingId: operationId,
|
|
167
|
+
feedName: categoryName,
|
|
168
|
+
status: updates.status || OnboardingProcessStatus.PENDING,
|
|
169
|
+
lastUpdated: new Date().toISOString(),
|
|
170
|
+
operationType: operationType,
|
|
171
|
+
steps: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Apply general updates
|
|
176
|
+
let updatedStatus: OnboardStatus = {
|
|
148
177
|
...existingStatus,
|
|
149
178
|
...updates,
|
|
179
|
+
onboardingId: operationId,
|
|
180
|
+
feedName: categoryName,
|
|
181
|
+
operationType: operationType,
|
|
150
182
|
lastUpdated: new Date().toISOString(),
|
|
151
183
|
};
|
|
152
|
-
|
|
184
|
+
|
|
185
|
+
// If stepName is provided, add it as a new step
|
|
186
|
+
if (stepName) {
|
|
187
|
+
const newStep = {
|
|
188
|
+
stepName,
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
status: stepStatus ?? updates.status ?? existingStatus.status,
|
|
191
|
+
errorMessage: errorMessage ?? updates.errorMessage,
|
|
192
|
+
};
|
|
193
|
+
updatedStatus.steps = [...(existingStatus.steps || []), newStep];
|
|
194
|
+
|
|
195
|
+
// If the step has a specific status, it might also update the overall status
|
|
196
|
+
if (newStep.status && newStep.status !== updatedStatus.status) {
|
|
197
|
+
updatedStatus.status = newStep.status;
|
|
198
|
+
}
|
|
199
|
+
if (newStep.errorMessage && !updatedStatus.errorMessage) {
|
|
200
|
+
updatedStatus.errorMessage = newStep.errorMessage;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
categoryOps[operationType] = updatedStatus;
|
|
205
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
153
206
|
await this.saveStatuses();
|
|
154
207
|
return updatedStatus;
|
|
155
208
|
});
|
|
156
209
|
}
|
|
157
210
|
|
|
158
211
|
/**
|
|
212
|
+
* A more specific method to record a step, which can also update the overall status.
|
|
213
|
+
* This is intended to replace the local `updateStatus` functions in validators/services.
|
|
214
|
+
* @param categoryName The name of the category (e.g., feed name).
|
|
215
|
+
* @param operationType The type of operation.
|
|
216
|
+
* @param stepName The description of the current step.
|
|
217
|
+
* @param serverName Optional server context for this step.
|
|
218
|
+
* @param newStatus Optional overall status to set for the operation.
|
|
219
|
+
* @param errorMessage Optional error message if this step resulted in a failure.
|
|
220
|
+
* @returns A promise that resolves to the updated OnboardStatus.
|
|
221
|
+
*/
|
|
222
|
+
public async recordStep(
|
|
223
|
+
categoryName: string,
|
|
224
|
+
operationType: OperationType,
|
|
225
|
+
stepName: string,
|
|
226
|
+
serverName?: string, // Added serverName to be part of the step if relevant
|
|
227
|
+
newStatus?: OnboardingProcessStatus,
|
|
228
|
+
errorMessage?: string
|
|
229
|
+
): Promise<OnboardStatus> {
|
|
230
|
+
return await this.withLock(async () => {
|
|
231
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
232
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
233
|
+
let currentStatus = categoryOps[operationType];
|
|
234
|
+
|
|
235
|
+
if (!currentStatus) {
|
|
236
|
+
currentStatus = await this.createInitialStatus(categoryName, operationType, serverName);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const stepToAdd = {
|
|
240
|
+
stepName,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
status: newStatus ?? (errorMessage ? OnboardingProcessStatus.FAILED : currentStatus.status), // Step inherits current or becomes FAILED
|
|
243
|
+
errorMessage: errorMessage,
|
|
244
|
+
...(serverName && { serverName }), // Include serverName in the step if provided
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const updatedSteps = [...(currentStatus.steps || []), stepToAdd];
|
|
248
|
+
|
|
249
|
+
const statusUpdatePayload: Partial<OnboardStatus> = {
|
|
250
|
+
steps: updatedSteps,
|
|
251
|
+
lastUpdated: new Date().toISOString(),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (newStatus) {
|
|
255
|
+
statusUpdatePayload.status = newStatus;
|
|
256
|
+
} else if (errorMessage && currentStatus.status !== OnboardingProcessStatus.FAILED) {
|
|
257
|
+
// If an error occurs and we're not already FAILED, mark as FAILED.
|
|
258
|
+
statusUpdatePayload.status = OnboardingProcessStatus.FAILED;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (errorMessage && !currentStatus.errorMessage) { // Store the first error message
|
|
262
|
+
statusUpdatePayload.errorMessage = errorMessage;
|
|
263
|
+
}
|
|
264
|
+
if (serverName && !currentStatus.serverName) { // Store serverName if not already set at top level
|
|
265
|
+
statusUpdatePayload.serverName = serverName;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
const updatedStatus: OnboardStatus = {
|
|
270
|
+
...currentStatus,
|
|
271
|
+
...statusUpdatePayload,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
categoryOps[operationType] = updatedStatus;
|
|
275
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
276
|
+
await this.saveStatuses();
|
|
277
|
+
return updatedStatus;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates an operation ID that combines category name and operation type.
|
|
283
|
+
* @param categoryName The name of the category.
|
|
284
|
+
* @param operationType The type of operation.
|
|
285
|
+
* @returns Combined operation ID string.
|
|
286
|
+
*/
|
|
287
|
+
private createOperationId(categoryName: string, operationType: OperationType): string {
|
|
288
|
+
return `${categoryName}_${operationType}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
159
292
|
* Creates an initial status for a new category-wide operation.
|
|
160
293
|
* The operation is identified by the categoryName.
|
|
161
294
|
* @param categoryName The name of the category (also used as feedName and operationId).
|
|
@@ -165,22 +298,116 @@ export class OnboardStatusManager {
|
|
|
165
298
|
* @returns A promise that resolves to the initial OnboardStatus.
|
|
166
299
|
*/
|
|
167
300
|
public async createInitialStatus(categoryName: string, operationType: OperationType, serverName?: string): Promise<OnboardStatus> {
|
|
168
|
-
//
|
|
169
|
-
const operationId = categoryName;
|
|
301
|
+
// Create operation ID that includes both category and operation type
|
|
302
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
170
303
|
|
|
171
304
|
const initialStatus: OnboardStatus = {
|
|
172
|
-
onboardingId: operationId,
|
|
173
|
-
feedName: categoryName,
|
|
174
|
-
serverName,
|
|
305
|
+
onboardingId: operationId,
|
|
306
|
+
feedName: categoryName,
|
|
307
|
+
serverName,
|
|
175
308
|
status: OnboardingProcessStatus.PENDING,
|
|
176
|
-
|
|
177
|
-
lastUpdated: new Date().toISOString(),
|
|
309
|
+
steps: [{ stepName: 'Initiated', timestamp: new Date().toISOString() }],
|
|
310
|
+
lastUpdated: new Date().toISOString(),
|
|
178
311
|
operationType,
|
|
179
312
|
errorMessage: undefined,
|
|
180
313
|
prInfo: undefined
|
|
181
314
|
};
|
|
182
|
-
|
|
183
|
-
|
|
315
|
+
|
|
316
|
+
// Save directly without using updateStatus, into the new structure
|
|
317
|
+
await this.withLock(async () => {
|
|
318
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
319
|
+
categoryOps[operationType] = initialStatus;
|
|
320
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
321
|
+
await this.saveStatuses();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return initialStatus;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Finds a succeeded operation for a given feed name and operation type that has a matching feed configuration.
|
|
330
|
+
* @param feedName The name of the feed.
|
|
331
|
+
* @param operationType The type of operation.
|
|
332
|
+
* @param currentConfig The current feed configuration to compare against.
|
|
333
|
+
* @returns A promise that resolves to the OnboardStatus of the succeeded operation, or undefined if not found.
|
|
334
|
+
*/
|
|
335
|
+
public async findSucceededOperation(
|
|
336
|
+
feedName: string,
|
|
337
|
+
operationType: OperationType,
|
|
338
|
+
currentConfig: FeedConfiguration
|
|
339
|
+
): Promise<OnboardStatus | undefined> {
|
|
340
|
+
return await this.withLock(async () => {
|
|
341
|
+
for (const categoryName in this.activeCategoryOperations) {
|
|
342
|
+
const operations = this.activeCategoryOperations[categoryName];
|
|
343
|
+
if (operations) {
|
|
344
|
+
const specificOperationStatus = operations[operationType];
|
|
345
|
+
if (
|
|
346
|
+
specificOperationStatus &&
|
|
347
|
+
specificOperationStatus.feedName === feedName &&
|
|
348
|
+
specificOperationStatus.status === OnboardingProcessStatus.SUCCEEDED
|
|
349
|
+
) {
|
|
350
|
+
// The FeedConfiguration is stored in the result of the SUCCEEDED operation
|
|
351
|
+
const savedConfig = specificOperationStatus.result?.feedConfiguration as FeedConfiguration;
|
|
352
|
+
|
|
353
|
+
if (savedConfig && this.isFeedConfigurationEqual(savedConfig, currentConfig)) {
|
|
354
|
+
return specificOperationStatus;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Compares two feed configurations for equality.
|
|
365
|
+
* @param config1 First feed configuration.
|
|
366
|
+
* @param config2 Second feed configuration.
|
|
367
|
+
* @returns True if the configurations are identical, false otherwise.
|
|
368
|
+
*/
|
|
369
|
+
private isFeedConfigurationEqual(config1: FeedConfiguration, config2: FeedConfiguration): boolean {
|
|
370
|
+
// Compare basic properties
|
|
371
|
+
if (config1.name !== config2.name) return false;
|
|
372
|
+
|
|
373
|
+
// Compare arrays with specific order handling
|
|
374
|
+
const compareArrays = <T>(arr1: T[], arr2: T[], sortFn?: (a: T, b: T) => number): boolean => {
|
|
375
|
+
if (arr1.length !== arr2.length) return false;
|
|
376
|
+
const sorted1 = sortFn ? [...arr1].sort(sortFn) : arr1;
|
|
377
|
+
const sorted2 = sortFn ? [...arr2].sort(sortFn) : arr2;
|
|
378
|
+
return JSON.stringify(sorted1) === JSON.stringify(sorted2);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Compare requirements array
|
|
382
|
+
if (!compareArrays(config1.requirements || [], config2.requirements || [])) return false;
|
|
383
|
+
|
|
384
|
+
// Compare MCP servers with name-based sorting
|
|
385
|
+
const sortByName = (a: McpConfig, b: McpConfig) => (a.name || '').localeCompare(b.name || '');
|
|
386
|
+
if (!compareArrays(config1.mcpServers || [], config2.mcpServers || [], sortByName)) return false;
|
|
387
|
+
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Finds an existing non-completed operation for a given feed name, server name, and operation type.
|
|
393
|
+
* @param feedName The name of the feed.
|
|
394
|
+
* @param operationType The type of operation.
|
|
395
|
+
* @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
|
|
396
|
+
*/
|
|
397
|
+
public async _findExistingNonCompletedOperation(
|
|
398
|
+
feedName: string,
|
|
399
|
+
operationType: OperationType
|
|
400
|
+
): Promise<OnboardStatus | undefined> {
|
|
401
|
+
const allCategoryStatuses = await this.getAllStatuses();
|
|
402
|
+
// feedName is the categoryName
|
|
403
|
+
const categoryOperations = allCategoryStatuses[feedName];
|
|
404
|
+
if (categoryOperations) {
|
|
405
|
+
const specificOperationStatus = categoryOperations[operationType];
|
|
406
|
+
if (specificOperationStatus && NON_COMPLETED_ONBOARDING_STATUSES.includes(specificOperationStatus.status)) {
|
|
407
|
+
return specificOperationStatus;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
184
411
|
}
|
|
185
412
|
}
|
|
186
413
|
|