Skip to content

Commit 6ccecf2

Browse files
jrcte
authored andcommitted
Move marketplace types to @roo-code/types (#4671)
And do some cleanup & consolidation.
1 parent e247085 commit 6ccecf2

24 files changed

+147
-237
lines changed

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from "./experiment.js"
77
export * from "./global-settings.js"
88
export * from "./history.js"
99
export * from "./ipc.js"
10+
export * from "./marketplace.js"
1011
export * from "./mcp.js"
1112
export * from "./message.js"
1213
export * from "./mode.js"

packages/types/src/marketplace.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { z } from "zod"
2+
3+
/**
4+
* Schema for MCP parameter definitions
5+
*/
6+
export const mcpParameterSchema = z.object({
7+
name: z.string().min(1),
8+
key: z.string().min(1),
9+
placeholder: z.string().optional(),
10+
optional: z.boolean().optional().default(false),
11+
})
12+
13+
export type McpParameter = z.infer<typeof mcpParameterSchema>
14+
15+
/**
16+
* Schema for MCP installation method with name
17+
*/
18+
export const mcpInstallationMethodSchema = z.object({
19+
name: z.string().min(1),
20+
content: z.string().min(1),
21+
parameters: z.array(mcpParameterSchema).optional(),
22+
prerequisites: z.array(z.string()).optional(),
23+
})
24+
25+
export type McpInstallationMethod = z.infer<typeof mcpInstallationMethodSchema>
26+
27+
/**
28+
* Component type validation
29+
*/
30+
export const marketplaceItemTypeSchema = z.enum(["mode", "mcp"] as const)
31+
32+
export type MarketplaceItemType = z.infer<typeof marketplaceItemTypeSchema>
33+
34+
/**
35+
* Base schema for common marketplace item fields
36+
*/
37+
const baseMarketplaceItemSchema = z.object({
38+
id: z.string().min(1),
39+
name: z.string().min(1, "Name is required"),
40+
description: z.string(),
41+
author: z.string().optional(),
42+
authorUrl: z.string().url("Author URL must be a valid URL").optional(),
43+
tags: z.array(z.string()).optional(),
44+
prerequisites: z.array(z.string()).optional(),
45+
})
46+
47+
/**
48+
* Type-specific schemas for YAML parsing (without type field, added programmatically)
49+
*/
50+
export const modeMarketplaceItemSchema = baseMarketplaceItemSchema.extend({
51+
content: z.string().min(1), // YAML content for modes
52+
})
53+
54+
export type ModeMarketplaceItem = z.infer<typeof modeMarketplaceItemSchema>
55+
56+
export const mcpMarketplaceItemSchema = baseMarketplaceItemSchema.extend({
57+
url: z.string().url(), // Required url field
58+
content: z.union([z.string().min(1), z.array(mcpInstallationMethodSchema)]), // Single config or array of methods
59+
parameters: z.array(mcpParameterSchema).optional(),
60+
})
61+
62+
export type McpMarketplaceItem = z.infer<typeof mcpMarketplaceItemSchema>
63+
64+
/**
65+
* Unified marketplace item schema using discriminated union
66+
*/
67+
export const marketplaceItemSchema = z.discriminatedUnion("type", [
68+
// Mode marketplace item
69+
modeMarketplaceItemSchema.extend({
70+
type: z.literal("mode"),
71+
}),
72+
// MCP marketplace item
73+
mcpMarketplaceItemSchema.extend({
74+
type: z.literal("mcp"),
75+
}),
76+
])
77+
78+
export type MarketplaceItem = z.infer<typeof marketplaceItemSchema>
79+
80+
/**
81+
* Installation options for marketplace items
82+
*/
83+
export const installMarketplaceItemOptionsSchema = z.object({
84+
target: z.enum(["global", "project"]).optional().default("project"),
85+
parameters: z.record(z.string(), z.any()).optional(),
86+
})
87+
88+
export type InstallMarketplaceItemOptions = z.infer<typeof installMarketplaceItemOptionsSchema>

src/services/marketplace/MarketplaceManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from "path"
44
import * as yaml from "yaml"
55
import { RemoteConfigLoader } from "./RemoteConfigLoader"
66
import { SimpleInstaller } from "./SimpleInstaller"
7-
import { MarketplaceItem, MarketplaceItemType } from "./types"
7+
import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types"
88
import { GlobalFileNames } from "../../shared/globalFileNames"
99
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
1010
import { t } from "../../i18n"

src/services/marketplace/RemoteConfigLoader.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import axios from "axios"
22
import * as yaml from "yaml"
33
import { z } from "zod"
44
import { getRooCodeApiUrl } from "@roo-code/cloud"
5-
import { MarketplaceItem, MarketplaceItemType } from "./types"
6-
import { modeMarketplaceItemSchema, mcpMarketplaceItemSchema } from "./schemas"
5+
import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types"
6+
import { modeMarketplaceItemSchema, mcpMarketplaceItemSchema } from "@roo-code/types"
77

88
// Response schemas for YAML API responses
99
const modeMarketplaceResponse = z.object({
@@ -43,8 +43,8 @@ export class RemoteConfigLoader {
4343
const yamlData = yaml.parse(data)
4444
const validated = modeMarketplaceResponse.parse(yamlData)
4545

46-
const items = validated.items.map((item) => ({
47-
type: "mode" as MarketplaceItemType,
46+
const items: MarketplaceItem[] = validated.items.map((item) => ({
47+
type: "mode" as const,
4848
...item,
4949
}))
5050

@@ -63,8 +63,8 @@ export class RemoteConfigLoader {
6363
const yamlData = yaml.parse(data)
6464
const validated = mcpMarketplaceResponse.parse(yamlData)
6565

66-
const items = validated.items.map((item) => ({
67-
type: "mcp" as MarketplaceItemType,
66+
const items: MarketplaceItem[] = validated.items.map((item) => ({
67+
type: "mcp" as const,
6868
...item,
6969
}))
7070

src/services/marketplace/SimpleInstaller.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as vscode from "vscode"
22
import * as path from "path"
33
import * as fs from "fs/promises"
44
import * as yaml from "yaml"
5-
import { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "./types"
5+
import type { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "@roo-code/types"
66
import { GlobalFileNames } from "../../shared/globalFileNames"
77
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
88

@@ -23,7 +23,7 @@ export class SimpleInstaller {
2323
case "mcp":
2424
return await this.installMcp(item, target, options)
2525
default:
26-
throw new Error(`Unsupported item type: ${item.type}`)
26+
throw new Error(`Unsupported item type: ${(item as any).type}`)
2727
}
2828
}
2929

@@ -135,7 +135,8 @@ export class SimpleInstaller {
135135
}
136136

137137
// Merge parameters (method-specific override global)
138-
const allParameters = [...(item.parameters || []), ...methodParameters]
138+
const itemParameters = item.type === "mcp" ? item.parameters || [] : []
139+
const allParameters = [...itemParameters, ...methodParameters]
139140
const uniqueParameters = Array.from(new Map(allParameters.map((p) => [p.key, p])).values())
140141

141142
// Replace parameters if provided
@@ -158,7 +159,8 @@ export class SimpleInstaller {
158159
methodParameters = method.parameters || []
159160

160161
// Re-merge parameters with the newly selected method
161-
const allParametersForNewMethod = [...(item.parameters || []), ...methodParameters]
162+
const itemParametersForNewMethod = item.type === "mcp" ? item.parameters || [] : []
163+
const allParametersForNewMethod = [...itemParametersForNewMethod, ...methodParameters]
162164
const uniqueParametersForNewMethod = Array.from(
163165
new Map(allParametersForNewMethod.map((p) => [p.key, p])).values(),
164166
)
@@ -239,7 +241,7 @@ export class SimpleInstaller {
239241
await this.removeMcp(item, target)
240242
break
241243
default:
242-
throw new Error(`Unsupported item type: ${item.type}`)
244+
throw new Error(`Unsupported item type: ${(item as any).type}`)
243245
}
244246
}
245247

src/services/marketplace/__tests__/MarketplaceManager.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MarketplaceManager } from "../MarketplaceManager"
2-
import { MarketplaceItem } from "../types"
2+
import type { MarketplaceItem } from "@roo-code/types"
33

44
// Mock axios
55
jest.mock("axios")
@@ -116,6 +116,7 @@ describe("MarketplaceManager", () => {
116116
name: "Test MCP",
117117
description: "A test MCP",
118118
type: "mcp",
119+
url: "https://example.com/mcp",
119120
content: '{"command": "node", "args": ["server.js"]}',
120121
},
121122
]
@@ -204,6 +205,7 @@ describe("MarketplaceManager", () => {
204205
name: "Test MCP",
205206
description: "A test MCP",
206207
type: "mcp",
208+
url: "https://example.com/mcp",
207209
content: '{"command": "node", "args": ["server.js"]}',
208210
}
209211

@@ -244,6 +246,7 @@ describe("MarketplaceManager", () => {
244246
name: "Test MCP",
245247
description: "A test MCP",
246248
type: "mcp",
249+
url: "https://example.com/mcp",
247250
content: '{"command": "node", "args": ["server.js"]}',
248251
}
249252

src/services/marketplace/__tests__/RemoteConfigLoader.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios from "axios"
22
import { RemoteConfigLoader } from "../RemoteConfigLoader"
3-
import { MarketplaceItemType } from "../types"
3+
import type { MarketplaceItemType } from "@roo-code/types"
44

55
// Mock axios
66
jest.mock("axios")

src/services/marketplace/__tests__/SimpleInstaller.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SimpleInstaller } from "../SimpleInstaller"
22
import * as fs from "fs/promises"
33
import * as yaml from "yaml"
44
import * as vscode from "vscode"
5-
import { MarketplaceItem } from "../types"
5+
import type { MarketplaceItem } from "@roo-code/types"
66
import * as path from "path"
77

88
jest.mock("fs/promises")
@@ -126,6 +126,7 @@ describe("SimpleInstaller", () => {
126126
name: "Test MCP",
127127
description: "A test MCP server for testing",
128128
type: "mcp",
129+
url: "https://example.com/mcp",
129130
content: JSON.stringify({
130131
command: "test-server",
131132
args: ["--test"],

src/services/marketplace/__tests__/marketplace-setting-check.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe("Marketplace Setting Check", () => {
7575
type: "mcp" as const,
7676
description: "Test description",
7777
content: "test content",
78+
url: "https://example.com/test-mcp",
7879
},
7980
mpInstallOptions: { target: "project" as const },
8081
}

src/services/marketplace/__tests__/nested-parameters.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from "vitest"
2-
import { mcpInstallationMethodSchema, mcpMarketplaceItemYamlSchema } from "../schemas"
3-
import { McpInstallationMethod, McpMarketplaceItem } from "../types"
2+
import { mcpInstallationMethodSchema, mcpMarketplaceItemSchema } from "@roo-code/types"
3+
import type { McpInstallationMethod, McpMarketplaceItem } from "@roo-code/types"
44

55
describe("Nested Parameters", () => {
66
describe("McpInstallationMethod Schema", () => {
@@ -95,7 +95,7 @@ describe("Nested Parameters", () => {
9595
],
9696
}
9797

98-
const result = mcpMarketplaceItemYamlSchema.parse(item)
98+
const result = mcpMarketplaceItemSchema.parse(item)
9999
expect(result.parameters).toHaveLength(1)
100100
expect(result.parameters![0].key).toBe("api_key")
101101

@@ -131,7 +131,7 @@ describe("Nested Parameters", () => {
131131
],
132132
}
133133

134-
const result = mcpMarketplaceItemYamlSchema.parse(item)
134+
const result = mcpMarketplaceItemSchema.parse(item)
135135
expect(result.parameters).toHaveLength(1)
136136

137137
const methods = result.content as McpInstallationMethod[]
@@ -160,7 +160,7 @@ describe("Nested Parameters", () => {
160160
],
161161
}
162162

163-
const result = mcpMarketplaceItemYamlSchema.parse(item)
163+
const result = mcpMarketplaceItemSchema.parse(item)
164164
expect(result.parameters).toBeUndefined()
165165

166166
const methods = result.content as McpInstallationMethod[]
@@ -182,7 +182,7 @@ describe("Nested Parameters", () => {
182182
],
183183
}
184184

185-
const result = mcpMarketplaceItemYamlSchema.parse(item)
185+
const result = mcpMarketplaceItemSchema.parse(item)
186186
expect(result.parameters).toBeUndefined()
187187

188188
const methods = result.content as McpInstallationMethod[]
@@ -221,7 +221,7 @@ describe("Nested Parameters", () => {
221221
}
222222

223223
// This should validate successfully - the conflict resolution happens at runtime
224-
const result = mcpMarketplaceItemYamlSchema.parse(item)
224+
const result = mcpMarketplaceItemSchema.parse(item)
225225
expect(result.parameters![0].key).toBe("version")
226226

227227
const methods = result.content as McpInstallationMethod[]

0 commit comments

Comments
 (0)