Move plugin data to config path; improve uninstall UI
Some checks failed
Build NapCat Artifacts / Build-Framework (push) Has been cancelled
Build NapCat Artifacts / Build-Shell (push) Has been cancelled

Change plugin data storage to use core.context.pathWrapper.configPath/plugins/<id> instead of pluginPath/data across OB11 plugin managers. Ensure plugin config directory is created when building the plugin context, use the central path for cleanup/uninstall, and update getPluginDataPath accordingly. Update web UI uninstall flow to prompt for cleaning configuration files using dialog.confirm (showing the config path) and performUninstall helper instead of window.confirm. Also include rebuilt native binaries (napi2native) for Linux x64 and arm64.
This commit is contained in:
手瓜一十雪
2026-02-18 16:49:43 +08:00
parent 4f47af233f
commit 9998207346
5 changed files with 52 additions and 41 deletions

View File

@@ -196,9 +196,14 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> i
* 创建插件上下文 * 创建插件上下文
*/ */
private createPluginContext (entry: PluginEntry): NapCatPluginContext { private createPluginContext (entry: PluginEntry): NapCatPluginContext {
const dataPath = path.join(entry.pluginPath, 'data'); const dataPath = path.join(this.core.context.pathWrapper.configPath, 'plugins', entry.id);
const configPath = path.join(dataPath, 'config.json'); const configPath = path.join(dataPath, 'config.json');
// 确保插件配置目录存在
if (!fs.existsSync(dataPath)) {
fs.mkdirSync(dataPath, { recursive: true });
}
// 创建插件专用日志器 // 创建插件专用日志器
const pluginPrefix = `[Plugin: ${entry.id}]`; const pluginPrefix = `[Plugin: ${entry.id}]`;
const coreLogger = this.logger; const coreLogger = this.logger;
@@ -358,7 +363,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> i
} }
const pluginPath = entry.pluginPath; const pluginPath = entry.pluginPath;
const dataPath = path.join(pluginPath, 'data'); const dataPath = path.join(this.core.context.pathWrapper.configPath, 'plugins', pluginId);
if (entry.loaded) { if (entry.loaded) {
await this.unloadPlugin(entry); await this.unloadPlugin(entry);
@@ -372,7 +377,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> i
fs.rmSync(pluginPath, { recursive: true, force: true }); fs.rmSync(pluginPath, { recursive: true, force: true });
} }
// 清理数据 // 清理插件配置数据
if (cleanData && fs.existsSync(dataPath)) { if (cleanData && fs.existsSync(dataPath)) {
fs.rmSync(dataPath, { recursive: true, force: true }); fs.rmSync(dataPath, { recursive: true, force: true });
} }
@@ -440,11 +445,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> i
* 获取插件数据目录路径 * 获取插件数据目录路径
*/ */
public getPluginDataPath (pluginId: string): string { public getPluginDataPath (pluginId: string): string {
const entry = this.plugins.get(pluginId); return path.join(this.core.context.pathWrapper.configPath, 'plugins', pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
return path.join(entry.pluginPath, 'data');
} }
/** /**

View File

@@ -173,9 +173,14 @@ export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> impleme
* 创建插件上下文 * 创建插件上下文
*/ */
private createPluginContext (entry: PluginEntry): NapCatPluginContext { private createPluginContext (entry: PluginEntry): NapCatPluginContext {
const dataPath = path.join(entry.pluginPath, 'data'); const dataPath = path.join(this.core.context.pathWrapper.configPath, 'plugins', entry.id);
const configPath = path.join(dataPath, 'config.json'); const configPath = path.join(dataPath, 'config.json');
// 确保插件配置目录存在
if (!fs.existsSync(dataPath)) {
fs.mkdirSync(dataPath, { recursive: true });
}
// 创建插件专用日志器 // 创建插件专用日志器
const pluginPrefix = `[Plugin: ${entry.id}]`; const pluginPrefix = `[Plugin: ${entry.id}]`;
const coreLogger = this.logger; const coreLogger = this.logger;
@@ -323,7 +328,7 @@ export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> impleme
} }
const pluginPath = entry.pluginPath; const pluginPath = entry.pluginPath;
const dataPath = path.join(pluginPath, 'data'); const dataPath = path.join(this.core.context.pathWrapper.configPath, 'plugins', pluginId);
// 先卸载插件 // 先卸载插件
await this.unloadPlugin(entry); await this.unloadPlugin(entry);
@@ -336,7 +341,7 @@ export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> impleme
fs.rmSync(pluginPath, { recursive: true, force: true }); fs.rmSync(pluginPath, { recursive: true, force: true });
} }
// 清理数据 // 清理插件配置数据
if (cleanData && fs.existsSync(dataPath)) { if (cleanData && fs.existsSync(dataPath)) {
fs.rmSync(dataPath, { recursive: true, force: true }); fs.rmSync(dataPath, { recursive: true, force: true });
} }
@@ -404,11 +409,7 @@ export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> impleme
* 获取插件数据目录路径 * 获取插件数据目录路径
*/ */
public getPluginDataPath (pluginId: string): string { public getPluginDataPath (pluginId: string): string {
const entry = this.plugins.get(pluginId); return path.join(this.core.context.pathWrapper.configPath, 'plugins', pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
return path.join(entry.pluginPath, 'data');
} }
/** /**

View File

@@ -66,25 +66,38 @@ export default function PluginPage () {
content: ( content: (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<p>{plugin.name}? </p> <p>{plugin.name}? </p>
<p className="text-small text-default-500"></p> <p className="text-small text-default-500"></p>
</div> </div>
), ),
// This 'dialog' utility might not support returning a value from UI interacting.
// We might need to implement a custom confirmation flow if we want a checkbox.
// Alternatively, use two buttons? "Uninstall & Clean", "Uninstall Only"?
// Standard dialog usually has Confirm/Cancel.
// Let's stick to a simpler "Uninstall" and then maybe a second prompt? Or just clean data?
// User requested: "Uninstall prompts whether to clean data".
// Let's use `window.confirm` for the second step or assume `dialog.confirm` is flexible enough?
// I will implement a two-step confirmation or try to modify the dialog hook if visible (not visible here).
// Let's use a standard `window.confirm` for the data cleanup question if the custom dialog doesn't support complex return.
// Better: Inside onConfirm, ask again?
onConfirm: async () => { onConfirm: async () => {
// Ask for data cleanup // Ask for data cleanup
// Since we are in an async callback, we can use another dialog or confirm. dialog.confirm({
// Native confirm is ugly but works reliably for logic: title: '删除配置',
const cleanData = window.confirm(`是否同时清理插件「${plugin.name}」的数据文件?\n点击“确定”清理数据点击“取消”仅卸载插件。`); content: (
<div className="flex flex-col gap-2">
<p>{plugin.name}</p>
<div className="text-small text-default-500">
<p>配置目录: config/plugins/{plugin.id}</p>
<p>"确定""取消"</p>
</div>
</div>
),
confirmText: '清理并卸载',
cancelText: '仅卸载',
onConfirm: async () => {
await performUninstall(true);
},
onCancel: async () => {
await performUninstall(false);
}
});
},
onCancel: () => {
resolve();
}
});
const performUninstall = async (cleanData: boolean) => {
const loadingToast = toast.loading('卸载中...'); const loadingToast = toast.loading('卸载中...');
try { try {
await PluginManager.uninstallPlugin(plugin.id, cleanData); await PluginManager.uninstallPlugin(plugin.id, cleanData);
@@ -95,11 +108,7 @@ export default function PluginPage () {
toast.error(e.message, { id: loadingToast }); toast.error(e.message, { id: loadingToast });
reject(e); reject(e);
} }
}, };
onCancel: () => {
resolve();
}
});
}); });
}; };