mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-17 23:51:08 +00:00
Merge fd85c22b5c into c4547d25c5
This commit is contained in:
commit
341e237e4d
23 changed files with 193 additions and 72 deletions
|
|
@ -102,6 +102,9 @@ export const modeConfigSchema = z.object({
|
|||
customInstructions: z.string().optional(),
|
||||
groups: groupEntryArraySchema,
|
||||
source: z.enum(["global", "project"]).optional(),
|
||||
// Marketplace origin metadata (optional)
|
||||
installedFromMarketplace: z.boolean().optional(),
|
||||
marketplaceItemId: z.string().optional(),
|
||||
})
|
||||
|
||||
export type ModeConfig = z.infer<typeof modeConfigSchema>
|
||||
|
|
|
|||
|
|
@ -552,6 +552,58 @@ export class CustomModesManager {
|
|||
vscode.window.showErrorMessage(t("common:customModes.errors.deleteFailed", { error: errorMessage }))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deletes a custom mode only from the specified source (project or global)
|
||||
* without affecting the other scope. Also handles rules folder cleanup.
|
||||
* @param slug - The mode slug
|
||||
* @param source - "project" or "global"
|
||||
* @param fromMarketplace - Whether this deletion was initiated by marketplace flows
|
||||
*/
|
||||
public async deleteCustomModeForSource(
|
||||
slug: string,
|
||||
source: "project" | "global",
|
||||
fromMarketplace = false,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.queueWrite(async () => {
|
||||
const settingsPath = await this.getCustomModesFilePath()
|
||||
const roomodesPath = await this.getWorkspaceRoomodes()
|
||||
|
||||
let targetPath: string
|
||||
let modeToDelete: ModeConfig | undefined
|
||||
|
||||
if (source === "project") {
|
||||
if (!roomodesPath) {
|
||||
throw new Error(t("common:customModes.errors.noWorkspaceForProject"))
|
||||
}
|
||||
targetPath = roomodesPath
|
||||
const roomodesModes = await this.loadModesFromFile(roomodesPath)
|
||||
modeToDelete = roomodesModes.find((m) => m.slug === slug)
|
||||
} else {
|
||||
targetPath = settingsPath
|
||||
const settingsModes = await this.loadModesFromFile(settingsPath)
|
||||
modeToDelete = settingsModes.find((m) => m.slug === slug)
|
||||
}
|
||||
|
||||
if (!modeToDelete) {
|
||||
throw new Error(t("common:customModes.errors.modeNotFound"))
|
||||
}
|
||||
|
||||
// Delete only from the selected source file
|
||||
await this.updateModesInFile(targetPath, (modes) => modes.filter((m) => m.slug !== slug))
|
||||
|
||||
// Delete associated rules folder using the located mode (preserves correct scope)
|
||||
await this.deleteRulesFolder(slug, modeToDelete, fromMarketplace)
|
||||
|
||||
// Refresh state and clear caches
|
||||
this.clearCache()
|
||||
await this.refreshMergedState()
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.deleteFailed", { error: errorMessage }))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the rules folder for a specific mode
|
||||
|
|
|
|||
3
src/i18n/locales/ca/marketplace.json
generated
3
src/i18n/locales/ca/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Error en instal·lar \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Eliminant element: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" eliminat correctament",
|
||||
"removeError": "Error en eliminar \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Error en eliminar \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Mode no trobat"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/de/marketplace.json
generated
3
src/i18n/locales/de/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Installation von \"{{itemName}}\" fehlgeschlagen: {{errorMessage}}",
|
||||
"removing": "Element wird entfernt: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" erfolgreich entfernt",
|
||||
"removeError": "Entfernung von \"{{itemName}}\" fehlgeschlagen: {{errorMessage}}"
|
||||
"removeError": "Entfernung von \"{{itemName}}\" fehlgeschlagen: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Modus nicht gefunden"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Failed to install \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Removing item: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" removed successfully",
|
||||
"removeError": "Failed to remove \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Failed to remove \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Mode not found"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/es/marketplace.json
generated
3
src/i18n/locales/es/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Error al instalar \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Eliminando elemento: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" eliminado correctamente",
|
||||
"removeError": "Error al eliminar \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Error al eliminar \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Modo no encontrado"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/fr/marketplace.json
generated
3
src/i18n/locales/fr/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Échec de l'installation de \"{{itemName}}\" : {{errorMessage}}",
|
||||
"removing": "Suppression de l'élément : \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" supprimé avec succès",
|
||||
"removeError": "Échec de la suppression de \"{{itemName}}\" : {{errorMessage}}"
|
||||
"removeError": "Échec de la suppression de \"{{itemName}}\" : {{errorMessage}}",
|
||||
"notInstalledForTarget": "Mode non trouvé"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/hi/marketplace.json
generated
3
src/i18n/locales/hi/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "\"{{itemName}}\" इंस्टॉल करने में विफल: {{errorMessage}}",
|
||||
"removing": "आइटम हटा रहे हैं: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" सफलतापूर्वक हटाया गया",
|
||||
"removeError": "\"{{itemName}}\" हटाने में विफल: {{errorMessage}}"
|
||||
"removeError": "\"{{itemName}}\" हटाने में विफल: {{errorMessage}}",
|
||||
"notInstalledForTarget": "मोड नहीं मिला"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/id/marketplace.json
generated
3
src/i18n/locales/id/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Gagal menginstal \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Menghapus item: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" berhasil dihapus",
|
||||
"removeError": "Gagal menghapus \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Gagal menghapus \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Mode tidak ditemukan"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/it/marketplace.json
generated
3
src/i18n/locales/it/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Installazione di \"{{itemName}}\" fallita: {{errorMessage}}",
|
||||
"removing": "Rimozione elemento: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" rimosso con successo",
|
||||
"removeError": "Rimozione di \"{{itemName}}\" fallita: {{errorMessage}}"
|
||||
"removeError": "Rimozione di \"{{itemName}}\" fallita: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Modalità non trovata"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/ja/marketplace.json
generated
3
src/i18n/locales/ja/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "\"{{itemName}}\"のインストールに失敗しました: {{errorMessage}}",
|
||||
"removing": "アイテムを削除中: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\"の削除が完了しました",
|
||||
"removeError": "\"{{itemName}}\"の削除に失敗しました: {{errorMessage}}"
|
||||
"removeError": "\"{{itemName}}\"の削除に失敗しました: {{errorMessage}}",
|
||||
"notInstalledForTarget": "モードが見つかりません"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/ko/marketplace.json
generated
3
src/i18n/locales/ko/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "\"{{itemName}}\" 설치 실패: {{errorMessage}}",
|
||||
"removing": "항목 제거 중: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" 제거 완료",
|
||||
"removeError": "\"{{itemName}}\" 제거 실패: {{errorMessage}}"
|
||||
"removeError": "\"{{itemName}}\" 제거 실패: {{errorMessage}}",
|
||||
"notInstalledForTarget": "모드를 찾을 수 없습니다"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/nl/marketplace.json
generated
3
src/i18n/locales/nl/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Installatie van \"{{itemName}}\" mislukt: {{errorMessage}}",
|
||||
"removing": "Item verwijderen: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" succesvol verwijderd",
|
||||
"removeError": "Verwijdering van \"{{itemName}}\" mislukt: {{errorMessage}}"
|
||||
"removeError": "Verwijdering van \"{{itemName}}\" mislukt: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Modus niet gevonden"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/pl/marketplace.json
generated
3
src/i18n/locales/pl/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Instalacja \"{{itemName}}\" nie powiodła się: {{errorMessage}}",
|
||||
"removing": "Usuwanie elementu: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" usunięto pomyślnie",
|
||||
"removeError": "Usunięcie \"{{itemName}}\" nie powiodło się: {{errorMessage}}"
|
||||
"removeError": "Usunięcie \"{{itemName}}\" nie powiodło się: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Nie znaleziono trybu"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/pt-BR/marketplace.json
generated
3
src/i18n/locales/pt-BR/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Falha ao instalar \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Removendo item: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" removido com sucesso",
|
||||
"removeError": "Falha ao remover \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Falha ao remover \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Modo não encontrado"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/ru/marketplace.json
generated
3
src/i18n/locales/ru/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Не удалось установить \"{{itemName}}\": {{errorMessage}}",
|
||||
"removing": "Удаление элемента: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" успешно удален",
|
||||
"removeError": "Не удалось удалить \"{{itemName}}\": {{errorMessage}}"
|
||||
"removeError": "Не удалось удалить \"{{itemName}}\": {{errorMessage}}",
|
||||
"notInstalledForTarget": "Режим не найден"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/tr/marketplace.json
generated
3
src/i18n/locales/tr/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "\"{{itemName}}\" yüklenemedi: {{errorMessage}}",
|
||||
"removing": "Öğe kaldırılıyor: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" başarıyla kaldırıldı",
|
||||
"removeError": "\"{{itemName}}\" kaldırılamadı: {{errorMessage}}"
|
||||
"removeError": "\"{{itemName}}\" kaldırılamadı: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Mod bulunamadı"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/vi/marketplace.json
generated
3
src/i18n/locales/vi/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "Cài đặt \"{{itemName}}\" thất bại: {{errorMessage}}",
|
||||
"removing": "Đang xóa mục: \"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" đã được xóa thành công",
|
||||
"removeError": "Xóa \"{{itemName}}\" thất bại: {{errorMessage}}"
|
||||
"removeError": "Xóa \"{{itemName}}\" thất bại: {{errorMessage}}",
|
||||
"notInstalledForTarget": "Không tìm thấy chế độ"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/zh-CN/marketplace.json
generated
3
src/i18n/locales/zh-CN/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "\"{{itemName}}\" 安装失败:{{errorMessage}}",
|
||||
"removing": "正在移除项目:\"{{itemName}}\"",
|
||||
"removeSuccess": "\"{{itemName}}\" 移除成功",
|
||||
"removeError": "\"{{itemName}}\" 移除失败:{{errorMessage}}"
|
||||
"removeError": "\"{{itemName}}\" 移除失败:{{errorMessage}}",
|
||||
"notInstalledForTarget": "未找到模式"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/i18n/locales/zh-TW/marketplace.json
generated
3
src/i18n/locales/zh-TW/marketplace.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"installError": "「{{itemName}}」安裝失敗:{{errorMessage}}",
|
||||
"removing": "正在移除項目:「{{itemName}}」",
|
||||
"removeSuccess": "「{{itemName}}」移除成功",
|
||||
"removeError": "「{{itemName}}」移除失敗:{{errorMessage}}"
|
||||
"removeError": "「{{itemName}}」移除失敗:{{errorMessage}}",
|
||||
"notInstalledForTarget": "找不到模式"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ import * as path from "path"
|
|||
import * as vscode from "vscode"
|
||||
import * as yaml from "yaml"
|
||||
|
||||
import type { OrganizationSettings, MarketplaceItem, MarketplaceItemType, McpMarketplaceItem } from "@roo-code/types"
|
||||
import type {
|
||||
OrganizationSettings,
|
||||
MarketplaceItem,
|
||||
MarketplaceItemType,
|
||||
McpMarketplaceItem,
|
||||
ModeConfig,
|
||||
} from "@roo-code/types"
|
||||
import { customModesSettingsSchema } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
||||
|
|
@ -241,6 +248,35 @@ export class MarketplaceManager {
|
|||
return metadata
|
||||
}
|
||||
|
||||
// Helper: identify marketplace-installed modes
|
||||
private isMarketplaceInstalledMode(
|
||||
mode: ModeConfig,
|
||||
): mode is ModeConfig & { installedFromMarketplace: true; marketplaceItemId: string } {
|
||||
return (
|
||||
mode.installedFromMarketplace === true &&
|
||||
typeof mode.marketplaceItemId === "string" &&
|
||||
mode.marketplaceItemId.length > 0
|
||||
)
|
||||
}
|
||||
|
||||
// Helper: parse YAML and collect installed mode metadata with proper typing
|
||||
private collectInstalledModesFromYaml(content: string, out: Record<string, { type: string }>): void {
|
||||
try {
|
||||
const parsed = yaml.parse(content)
|
||||
const result = customModesSettingsSchema.safeParse(parsed)
|
||||
if (!result.success) {
|
||||
return
|
||||
}
|
||||
for (const mode of result.data.customModes) {
|
||||
if (this.isMarketplaceInstalledMode(mode)) {
|
||||
out[mode.marketplaceItemId] = { type: "mode" }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors here; caller handles file existence/errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for project-level installed items
|
||||
*/
|
||||
|
|
@ -255,16 +291,7 @@ export class MarketplaceManager {
|
|||
const projectModesPath = path.join(workspaceFolder.uri.fsPath, ".roomodes")
|
||||
try {
|
||||
const content = await fs.readFile(projectModesPath, "utf-8")
|
||||
const data = yaml.parse(content)
|
||||
if (data?.customModes && Array.isArray(data.customModes)) {
|
||||
for (const mode of data.customModes) {
|
||||
if (mode.slug) {
|
||||
metadata[mode.slug] = {
|
||||
type: "mode",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.collectInstalledModesFromYaml(content, metadata)
|
||||
} catch (error) {
|
||||
// File doesn't exist or can't be read, skip
|
||||
}
|
||||
|
|
@ -300,16 +327,7 @@ export class MarketplaceManager {
|
|||
const globalModesPath = path.join(globalSettingsPath, GlobalFileNames.customModes)
|
||||
try {
|
||||
const content = await fs.readFile(globalModesPath, "utf-8")
|
||||
const data = yaml.parse(content)
|
||||
if (data?.customModes && Array.isArray(data.customModes)) {
|
||||
for (const mode of data.customModes) {
|
||||
if (mode.slug) {
|
||||
metadata[mode.slug] = {
|
||||
type: "mode",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.collectInstalledModesFromYaml(content, metadata)
|
||||
} catch (error) {
|
||||
// File doesn't exist or can't be read, skip
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ import * as vscode from "vscode"
|
|||
import * as path from "path"
|
||||
import * as fs from "fs/promises"
|
||||
import * as yaml from "yaml"
|
||||
import type { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "@roo-code/types"
|
||||
import type { MarketplaceItem, McpParameter, ModeConfig } from "@roo-code/types"
|
||||
import { GlobalFileNames } from "../../shared/globalFileNames"
|
||||
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
|
||||
import type { CustomModesManager } from "../../core/config/CustomModesManager"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
export interface InstallOptions extends InstallMarketplaceItemOptions {
|
||||
export interface InstallOptions {
|
||||
target: "project" | "global"
|
||||
selectedIndex?: number // Which installation method to use (for array content)
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
|
||||
export class SimpleInstaller {
|
||||
|
|
@ -20,6 +22,7 @@ export class SimpleInstaller {
|
|||
|
||||
async installItem(item: MarketplaceItem, options: InstallOptions): Promise<{ filePath: string; line?: number }> {
|
||||
const { target } = options
|
||||
const itemType = item.type
|
||||
|
||||
switch (item.type) {
|
||||
case "mode":
|
||||
|
|
@ -27,7 +30,7 @@ export class SimpleInstaller {
|
|||
case "mcp":
|
||||
return await this.installMcp(item, target, options)
|
||||
default:
|
||||
throw new Error(`Unsupported item type: ${(item as any).type}`)
|
||||
throw new Error(`Unsupported item type: ${itemType}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +50,14 @@ export class SimpleInstaller {
|
|||
// If CustomModesManager is available, use importModeWithRules
|
||||
if (this.customModesManager) {
|
||||
// Transform marketplace content to import format (wrap in customModes array)
|
||||
const parsedMode = yaml.parse(item.content)
|
||||
// Annotate marketplace origin to disambiguate from user-created modes
|
||||
if (parsedMode && typeof parsedMode === "object") {
|
||||
;(parsedMode as ModeConfig).installedFromMarketplace = true
|
||||
;(parsedMode as ModeConfig).marketplaceItemId = item.id
|
||||
}
|
||||
const importData = {
|
||||
customModes: [yaml.parse(item.content)],
|
||||
customModes: [parsedMode],
|
||||
}
|
||||
const importYaml = yaml.stringify(importData)
|
||||
|
||||
|
|
@ -72,7 +81,7 @@ export class SimpleInstaller {
|
|||
// Find the line containing the slug of the added mode
|
||||
if (modeData?.slug) {
|
||||
const slugLineIndex = lines.findIndex(
|
||||
(l) => l.includes(`slug: ${modeData.slug}`) || l.includes(`slug: "${modeData.slug}"`),
|
||||
(l: string) => l.includes(`slug: ${modeData.slug}`) || l.includes(`slug: "${modeData.slug}"`),
|
||||
)
|
||||
if (slugLineIndex >= 0) {
|
||||
line = slugLineIndex + 1 // Convert to 1-based line number
|
||||
|
|
@ -88,6 +97,11 @@ export class SimpleInstaller {
|
|||
// Fallback to original implementation if CustomModesManager is not available
|
||||
const filePath = await this.getModeFilePath(target)
|
||||
const modeData = yaml.parse(item.content)
|
||||
// Annotate marketplace origin fields for fallback path as well
|
||||
if (modeData && typeof modeData === "object") {
|
||||
;(modeData as ModeConfig).installedFromMarketplace = true
|
||||
;(modeData as ModeConfig).marketplaceItemId = item.id
|
||||
}
|
||||
|
||||
// Read existing file or create new structure
|
||||
let existingData: any = { customModes: [] }
|
||||
|
|
@ -143,7 +157,7 @@ export class SimpleInstaller {
|
|||
const addedMode = existingData.customModes[addedModeIndex]
|
||||
if (addedMode?.slug) {
|
||||
const slugLineIndex = lines.findIndex(
|
||||
(l) => l.includes(`slug: ${addedMode.slug}`) || l.includes(`slug: "${addedMode.slug}"`),
|
||||
(l: string) => l.includes(`slug: ${addedMode.slug}`) || l.includes(`slug: "${addedMode.slug}"`),
|
||||
)
|
||||
if (slugLineIndex >= 0) {
|
||||
line = slugLineIndex + 1 // Convert to 1-based line number
|
||||
|
|
@ -280,6 +294,7 @@ export class SimpleInstaller {
|
|||
|
||||
async removeItem(item: MarketplaceItem, options: InstallOptions): Promise<void> {
|
||||
const { target } = options
|
||||
const itemType = item.type
|
||||
|
||||
switch (item.type) {
|
||||
case "mode":
|
||||
|
|
@ -289,7 +304,7 @@ export class SimpleInstaller {
|
|||
await this.removeMcp(item, target)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported item type: ${(item as any).type}`)
|
||||
throw new Error(`Unsupported item type: ${itemType}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,13 +334,23 @@ export class SimpleInstaller {
|
|||
throw new Error("Mode missing slug identifier")
|
||||
}
|
||||
|
||||
// Get the current modes to determine the source
|
||||
// Get the current modes and locate the exact marketplace-installed mode for the selected target
|
||||
const modes = await this.customModesManager.getCustomModes()
|
||||
const mode = modes.find((m) => m.slug === modeSlug)
|
||||
const candidate = modes.find(
|
||||
(m: ModeConfig) =>
|
||||
m.slug === modeSlug &&
|
||||
m.installedFromMarketplace === true &&
|
||||
m.marketplaceItemId === item.id &&
|
||||
m.source === target,
|
||||
)
|
||||
|
||||
// Use CustomModesManager to delete the mode configuration
|
||||
// This also handles rules folder deletion
|
||||
await this.customModesManager.deleteCustomMode(modeSlug, true)
|
||||
if (!candidate) {
|
||||
const msg = t("marketplace:installation.notInstalledForTarget")
|
||||
throw new Error(msg === "installation.notInstalledForTarget" ? "Mode not found" : msg)
|
||||
}
|
||||
|
||||
// Delete only from the selected source to avoid unintended removals
|
||||
await this.customModesManager.deleteCustomModeForSource(modeSlug, target, true)
|
||||
}
|
||||
|
||||
private async removeMcp(item: MarketplaceItem, target: "project" | "global"): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ describe("SimpleInstaller", () => {
|
|||
mockContext = {} as vscode.ExtensionContext
|
||||
mockCustomModesManager = {
|
||||
deleteCustomMode: vi.fn().mockResolvedValue(undefined),
|
||||
deleteCustomModeForSource: vi.fn().mockResolvedValue(undefined),
|
||||
importModeWithRules: vi.fn().mockResolvedValue({ success: true }),
|
||||
getCustomModes: vi.fn().mockResolvedValue([]),
|
||||
} as any
|
||||
|
|
@ -220,13 +221,13 @@ describe("SimpleInstaller", () => {
|
|||
it("should use CustomModesManager to delete mode and clean up rules folder", async () => {
|
||||
// Mock that the mode exists with project source
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
|
||||
{ slug: "test", name: "Test Mode", source: "project" } as any,
|
||||
{ slug: "test", name: "Test Mode", source: "project", installedFromMarketplace: true, marketplaceItemId: "test-mode" } as any,
|
||||
])
|
||||
|
||||
await installer.removeItem(mockModeItem, { target: "project" })
|
||||
|
||||
// Should call deleteCustomMode with fromMarketplace flag set to true
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
|
||||
// Should call scoped deletion for the selected source with fromMarketplace flag set to true
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).toHaveBeenCalledWith("test", "project", true)
|
||||
// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
|
||||
expect(fileExistsAtPath).not.toHaveBeenCalled()
|
||||
expect(mockFs.rm).not.toHaveBeenCalled()
|
||||
|
|
@ -235,13 +236,13 @@ describe("SimpleInstaller", () => {
|
|||
it("should handle global mode removal with rules cleanup", async () => {
|
||||
// Mock that the mode exists with global source
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
|
||||
{ slug: "test", name: "Test Mode", source: "global" } as any,
|
||||
{ slug: "test", name: "Test Mode", source: "global", installedFromMarketplace: true, marketplaceItemId: "test-mode" } as any,
|
||||
])
|
||||
|
||||
await installer.removeItem(mockModeItem, { target: "global" })
|
||||
|
||||
// Should call deleteCustomMode with fromMarketplace flag set to true
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
|
||||
// Should call scoped deletion for the selected source with fromMarketplace flag set to true
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).toHaveBeenCalledWith("test", "global", true)
|
||||
// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
|
||||
expect(fileExistsAtPath).not.toHaveBeenCalled()
|
||||
expect(mockFs.rm).not.toHaveBeenCalled()
|
||||
|
|
@ -250,13 +251,13 @@ describe("SimpleInstaller", () => {
|
|||
it("should handle case when rules folder does not exist", async () => {
|
||||
// Mock that the mode exists
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
|
||||
{ slug: "test", name: "Test Mode", source: "project" } as any,
|
||||
{ slug: "test", name: "Test Mode", source: "project", installedFromMarketplace: true, marketplaceItemId: "test-mode" } as any,
|
||||
])
|
||||
|
||||
await installer.removeItem(mockModeItem, { target: "project" })
|
||||
|
||||
// Should call deleteCustomMode with fromMarketplace flag set to true
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
|
||||
// Should call scoped deletion for the selected source with fromMarketplace flag set to true
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).toHaveBeenCalledWith("test", "project", true)
|
||||
// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
|
||||
expect(fileExistsAtPath).not.toHaveBeenCalled()
|
||||
expect(mockFs.rm).not.toHaveBeenCalled()
|
||||
|
|
@ -265,27 +266,26 @@ describe("SimpleInstaller", () => {
|
|||
it("should throw error if deleteCustomMode fails", async () => {
|
||||
// Mock that the mode exists
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
|
||||
{ slug: "test", name: "Test Mode", source: "project" } as any,
|
||||
{ slug: "test", name: "Test Mode", source: "project", installedFromMarketplace: true, marketplaceItemId: "test-mode" } as any,
|
||||
])
|
||||
// Mock that deleteCustomMode fails
|
||||
mockCustomModesManager.deleteCustomMode = vi.fn().mockRejectedValueOnce(new Error("Permission denied"))
|
||||
// Mock that scoped deletion fails
|
||||
;(mockCustomModesManager as any).deleteCustomModeForSource = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error("Permission denied"))
|
||||
|
||||
// Should throw the error from deleteCustomMode
|
||||
// Should throw the error from scoped deletion
|
||||
await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow("Permission denied")
|
||||
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).toHaveBeenCalledWith("test", "project", true)
|
||||
})
|
||||
|
||||
it("should handle mode not found in custom modes list", async () => {
|
||||
it("should throw when mode is not marketplace-installed for selected target", async () => {
|
||||
// Mock that the mode doesn't exist in the list
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([])
|
||||
|
||||
await installer.removeItem(mockModeItem, { target: "project" })
|
||||
await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow(/(Mode not found|customModes\.errors\.modeNotFound)/)
|
||||
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
|
||||
// Should not attempt to delete rules folder
|
||||
expect(fileExistsAtPath).not.toHaveBeenCalled()
|
||||
expect(mockFs.rm).not.toHaveBeenCalled()
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should throw error when mode content is invalid YAML", async () => {
|
||||
|
|
@ -333,9 +333,14 @@ describe("SimpleInstaller", () => {
|
|||
] as any,
|
||||
}
|
||||
|
||||
// Mock installed marketplace mode with matching marketplaceItemId and source
|
||||
vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
|
||||
{ slug: "test-array", name: "Test Array Mode", source: "project", installedFromMarketplace: true, marketplaceItemId: "test-mode" } as any,
|
||||
])
|
||||
|
||||
await installer.removeItem(arrayContentItem, { target: "project" })
|
||||
|
||||
expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test-array", true)
|
||||
expect((mockCustomModesManager as any).deleteCustomModeForSource).toHaveBeenCalledWith("test-array", "project", true)
|
||||
})
|
||||
|
||||
it("should throw error when CustomModesManager is not available", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue