|
1 | 1 | import axios from "axios" |
2 | 2 | import * as yaml from "yaml" |
| 3 | +import * as fs from "fs/promises" |
| 4 | +import * as path from "path" |
3 | 5 | import { z } from "zod" |
4 | 6 | import { getRooCodeApiUrl } from "@roo-code/cloud" |
5 | 7 | import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types" |
@@ -37,39 +39,49 @@ export class RemoteConfigLoader { |
37 | 39 | const cached = this.getFromCache(cacheKey) |
38 | 40 | if (cached) return cached |
39 | 41 |
|
40 | | - const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/modes`) |
| 42 | + try { |
| 43 | + const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/modes`) |
41 | 44 |
|
42 | | - // Parse and validate YAML response |
43 | | - const yamlData = yaml.parse(data) |
44 | | - const validated = modeMarketplaceResponse.parse(yamlData) |
| 45 | + // Parse and validate YAML response |
| 46 | + const yamlData = yaml.parse(data) |
| 47 | + const validated = modeMarketplaceResponse.parse(yamlData) |
45 | 48 |
|
46 | | - const items: MarketplaceItem[] = validated.items.map((item) => ({ |
47 | | - type: "mode" as const, |
48 | | - ...item, |
49 | | - })) |
| 49 | + const items: MarketplaceItem[] = validated.items.map((item) => ({ |
| 50 | + type: "mode" as const, |
| 51 | + ...item, |
| 52 | + })) |
50 | 53 |
|
51 | | - this.setCache(cacheKey, items) |
52 | | - return items |
| 54 | + this.setCache(cacheKey, items) |
| 55 | + return items |
| 56 | + } catch (error) { |
| 57 | + console.warn("Failed to fetch modes from remote API, trying local fallback:", error) |
| 58 | + return this.fetchLocalModes() |
| 59 | + } |
53 | 60 | } |
54 | 61 |
|
55 | 62 | private async fetchMcps(): Promise<MarketplaceItem[]> { |
56 | 63 | const cacheKey = "mcps" |
57 | 64 | const cached = this.getFromCache(cacheKey) |
58 | 65 | if (cached) return cached |
59 | 66 |
|
60 | | - const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/mcps`) |
| 67 | + try { |
| 68 | + const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/mcps`) |
61 | 69 |
|
62 | | - // Parse and validate YAML response |
63 | | - const yamlData = yaml.parse(data) |
64 | | - const validated = mcpMarketplaceResponse.parse(yamlData) |
| 70 | + // Parse and validate YAML response |
| 71 | + const yamlData = yaml.parse(data) |
| 72 | + const validated = mcpMarketplaceResponse.parse(yamlData) |
65 | 73 |
|
66 | | - const items: MarketplaceItem[] = validated.items.map((item) => ({ |
67 | | - type: "mcp" as const, |
68 | | - ...item, |
69 | | - })) |
| 74 | + const items: MarketplaceItem[] = validated.items.map((item) => ({ |
| 75 | + type: "mcp" as const, |
| 76 | + ...item, |
| 77 | + })) |
70 | 78 |
|
71 | | - this.setCache(cacheKey, items) |
72 | | - return items |
| 79 | + this.setCache(cacheKey, items) |
| 80 | + return items |
| 81 | + } catch (error) { |
| 82 | + console.warn("Failed to fetch MCPs from remote API, trying local fallback:", error) |
| 83 | + return this.fetchLocalMcps() |
| 84 | + } |
73 | 85 | } |
74 | 86 |
|
75 | 87 | private async fetchWithRetry<T>(url: string, maxRetries = 3): Promise<T> { |
@@ -126,4 +138,104 @@ export class RemoteConfigLoader { |
126 | 138 | clearCache(): void { |
127 | 139 | this.cache.clear() |
128 | 140 | } |
| 141 | + |
| 142 | + /** |
| 143 | + * Fallback method to load MCPs from local marketplace data file |
| 144 | + */ |
| 145 | + private async fetchLocalMcps(): Promise<MarketplaceItem[]> { |
| 146 | + try { |
| 147 | + // Try to load from local marketplace-data directory |
| 148 | + // Look for marketplace-data in current directory, parent directories, or relative to __dirname |
| 149 | + const possiblePaths = [ |
| 150 | + path.join(process.cwd(), "marketplace-data", "mcps.yaml"), |
| 151 | + path.join(process.cwd(), "..", "marketplace-data", "mcps.yaml"), |
| 152 | + path.join(process.cwd(), "..", "..", "marketplace-data", "mcps.yaml"), |
| 153 | + path.join(process.cwd(), "..", "..", "..", "marketplace-data", "mcps.yaml"), |
| 154 | + path.join(__dirname, "..", "..", "..", "marketplace-data", "mcps.yaml"), |
| 155 | + ] |
| 156 | + |
| 157 | + let data: string | null = null |
| 158 | + let usedPath: string | null = null |
| 159 | + |
| 160 | + for (const localMcpPath of possiblePaths) { |
| 161 | + try { |
| 162 | + data = await fs.readFile(localMcpPath, "utf-8") |
| 163 | + usedPath = localMcpPath |
| 164 | + break |
| 165 | + } catch (error) { |
| 166 | + // Continue to next path |
| 167 | + continue |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if (!data) { |
| 172 | + throw new Error("Could not find mcps.yaml in any expected location") |
| 173 | + } |
| 174 | + |
| 175 | + // Parse and validate YAML response |
| 176 | + const yamlData = yaml.parse(data) |
| 177 | + const validated = mcpMarketplaceResponse.parse(yamlData) |
| 178 | + |
| 179 | + const items: MarketplaceItem[] = validated.items.map((item) => ({ |
| 180 | + type: "mcp" as const, |
| 181 | + ...item, |
| 182 | + })) |
| 183 | + |
| 184 | + console.log(`Loaded ${items.length} MCP items from local marketplace data at ${usedPath}`) |
| 185 | + return items |
| 186 | + } catch (error) { |
| 187 | + console.warn("Failed to load local MCP data:", error) |
| 188 | + return [] |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + /** |
| 193 | + * Fallback method to load modes from local marketplace data file |
| 194 | + */ |
| 195 | + private async fetchLocalModes(): Promise<MarketplaceItem[]> { |
| 196 | + try { |
| 197 | + // Try to load from local marketplace-data directory |
| 198 | + // Look for marketplace-data in current directory, parent directories, or relative to __dirname |
| 199 | + const possiblePaths = [ |
| 200 | + path.join(process.cwd(), "marketplace-data", "modes.yaml"), |
| 201 | + path.join(process.cwd(), "..", "marketplace-data", "modes.yaml"), |
| 202 | + path.join(process.cwd(), "..", "..", "marketplace-data", "modes.yaml"), |
| 203 | + path.join(process.cwd(), "..", "..", "..", "marketplace-data", "modes.yaml"), |
| 204 | + path.join(__dirname, "..", "..", "..", "marketplace-data", "modes.yaml"), |
| 205 | + ] |
| 206 | + |
| 207 | + let data: string | null = null |
| 208 | + let usedPath: string | null = null |
| 209 | + |
| 210 | + for (const localModePath of possiblePaths) { |
| 211 | + try { |
| 212 | + data = await fs.readFile(localModePath, "utf-8") |
| 213 | + usedPath = localModePath |
| 214 | + break |
| 215 | + } catch (error) { |
| 216 | + // Continue to next path |
| 217 | + continue |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + if (!data) { |
| 222 | + throw new Error("Could not find modes.yaml in any expected location") |
| 223 | + } |
| 224 | + |
| 225 | + // Parse and validate YAML response |
| 226 | + const yamlData = yaml.parse(data) |
| 227 | + const validated = modeMarketplaceResponse.parse(yamlData) |
| 228 | + |
| 229 | + const items: MarketplaceItem[] = validated.items.map((item) => ({ |
| 230 | + type: "mode" as const, |
| 231 | + ...item, |
| 232 | + })) |
| 233 | + |
| 234 | + console.log(`Loaded ${items.length} mode items from local marketplace data at ${usedPath}`) |
| 235 | + return items |
| 236 | + } catch (error) { |
| 237 | + console.warn("Failed to load local mode data:", error) |
| 238 | + return [] |
| 239 | + } |
| 240 | + } |
129 | 241 | } |
0 commit comments