plugin-manager.ts•6.56 kB
import * as fs from "fs/promises";
import * as path from "path";
export interface PluginInstallRequest {
projectPath: string;
pluginUrl?: string;
pluginName?: string;
pluginSource?: string; // URL or local path or npm package
enabled?: boolean;
}
export interface PluginConfig {
name: string;
status: boolean;
description: string;
parameters: Record<string, any>;
}
// Popular RPG Maker MZ plugins registry
const PLUGIN_REGISTRY: Record<string, string> = {
"VisuMZ_0_CoreEngine": "https://raw.githubusercontent.com/VisuStella/VisualNovelChoice/master/VisuMZ_0_CoreEngine.js",
// Add more popular plugins here
};
export async function installPlugin(request: PluginInstallRequest): Promise<{ success: boolean; plugin?: string; error?: string }> {
try {
const pluginsDir = path.join(request.projectPath, "js", "plugins");
await fs.mkdir(pluginsDir, { recursive: true });
let pluginContent: string;
let pluginName: string;
// Determine plugin source
if (request.pluginSource) {
if (request.pluginSource.startsWith("http")) {
// Download from URL
const response = await fetch(request.pluginSource);
if (!response.ok) {
return { success: false, error: `Failed to download plugin: ${response.statusText}` };
}
pluginContent = await response.text();
pluginName = request.pluginName || path.basename(new URL(request.pluginSource).pathname);
} else if (await fs.access(request.pluginSource).then(() => true).catch(() => false)) {
// Local file
pluginContent = await fs.readFile(request.pluginSource, "utf-8");
pluginName = request.pluginName || path.basename(request.pluginSource);
} else {
return { success: false, error: "Invalid plugin source" };
}
} else if (request.pluginName && PLUGIN_REGISTRY[request.pluginName]) {
// From registry
const url = PLUGIN_REGISTRY[request.pluginName];
const response = await fetch(url);
if (!response.ok) {
return { success: false, error: `Failed to download plugin from registry` };
}
pluginContent = await response.text();
pluginName = request.pluginName;
} else {
return { success: false, error: "No valid plugin source provided" };
}
// Save plugin file
const pluginPath = path.join(pluginsDir, pluginName);
await fs.writeFile(pluginPath, pluginContent, "utf-8");
// Update plugins.js
await updatePluginsConfig(request.projectPath, pluginName, request.enabled !== false);
return { success: true, plugin: pluginName };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async function updatePluginsConfig(projectPath: string, pluginName: string, enabled: boolean): Promise<void> {
const pluginsJsPath = path.join(projectPath, "js", "plugins.js");
let pluginsConfig: PluginConfig[] = [];
// Read existing config if exists
try {
const content = await fs.readFile(pluginsJsPath, "utf-8");
const match = content.match(/var \$plugins\s*=\s*(\[[\s\S]*?\]);/);
if (match) {
pluginsConfig = JSON.parse(match[1]);
}
} catch {
// File doesn't exist, will create new
}
// Add or update plugin
const existingIndex = pluginsConfig.findIndex(p => p.name === pluginName);
const pluginEntry: PluginConfig = {
name: pluginName.replace(".js", ""),
status: enabled,
description: "",
parameters: {}
};
if (existingIndex >= 0) {
pluginsConfig[existingIndex] = pluginEntry;
} else {
pluginsConfig.push(pluginEntry);
}
// Write back
const content = `// Generated by RPG Maker MZ MCP
var $plugins = ${JSON.stringify(pluginsConfig, null, 2)};
`;
await fs.writeFile(pluginsJsPath, content, "utf-8");
}
export async function listInstalledPlugins(projectPath: string): Promise<{ success: boolean; plugins?: PluginConfig[]; error?: string }> {
try {
const pluginsJsPath = path.join(projectPath, "js", "plugins.js");
const content = await fs.readFile(pluginsJsPath, "utf-8");
const match = content.match(/var \$plugins\s*=\s*(\[[\s\S]*?\]);/);
if (match) {
const plugins = JSON.parse(match[1]);
return { success: true, plugins };
}
return { success: true, plugins: [] };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
export async function uninstallPlugin(projectPath: string, pluginName: string): Promise<{ success: boolean; error?: string }> {
try {
// Remove from plugins.js
const pluginsJsPath = path.join(projectPath, "js", "plugins.js");
const content = await fs.readFile(pluginsJsPath, "utf-8");
const match = content.match(/var \$plugins\s*=\s*(\[[\s\S]*?\]);/);
if (match) {
let plugins = JSON.parse(match[1]);
plugins = plugins.filter((p: PluginConfig) => p.name !== pluginName.replace(".js", ""));
const newContent = `// Generated by RPG Maker MZ MCP\nvar $plugins = ${JSON.stringify(plugins, null, 2)};\n`;
await fs.writeFile(pluginsJsPath, newContent, "utf-8");
}
// Delete plugin file
const pluginPath = path.join(projectPath, "js", "plugins", pluginName);
await fs.unlink(pluginPath);
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
export async function enablePlugin(projectPath: string, pluginName: string, enabled: boolean): Promise<{ success: boolean; error?: string }> {
try {
const pluginsJsPath = path.join(projectPath, "js", "plugins.js");
const content = await fs.readFile(pluginsJsPath, "utf-8");
const match = content.match(/var \$plugins\s*=\s*(\[[\s\S]*?\]);/);
if (match) {
const plugins = JSON.parse(match[1]);
const plugin = plugins.find((p: PluginConfig) => p.name === pluginName.replace(".js", ""));
if (plugin) {
plugin.status = enabled;
const newContent = `// Generated by RPG Maker MZ MCP\nvar $plugins = ${JSON.stringify(plugins, null, 2)};\n`;
await fs.writeFile(pluginsJsPath, newContent, "utf-8");
return { success: true };
}
return { success: false, error: "Plugin not found" };
}
return { success: false, error: "No plugins config found" };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}