дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
fix(plugins): install plugin packages from a .zip (#896)
Root cause: Install Plugin from Disk only opened a folder picker and the host could only copy folders, so a zipped plugin could not be installed. The picker now accepts a .zip or a package's plugin.toml, and the host's install_from_path unpacks a zip to a temporary folder and runs it through the existing install_from_dir validation. The package may sit at the archive root or in its only folder (archiver metadata such as __MACOSX is ignored). Archives with unsafe paths or symbolic links, more than 10,000 entries or over 1 GB unpacked are refused, and a zip without plugin.toml (e.g. an IntelliJ plugin) gets a clear error. Fixes #892 Co-authored-by: florentvdry <[email protected]>
Цей коміт міститься в:
1 джерело
eb633f6ca6
коміт
db5251efe6
12 files changed
+208
-21
No files matched your search
@@ -853,7 +853,7 @@ export const commands = {
|
||||
*/
|
||||
pluginList: () => typedError<PluginsSnapshot, IdeError>(__TAURI_INVOKE("plugin_list")),
|
||||
pluginSetEnabled: (id: string, enabled: boolean) => typedError<null, IdeError>(__TAURI_INVOKE("plugin_set_enabled", { id, enabled })),
|
||||
/** Installs the plugin package in `path` (a folder with `plugin.toml`). */
|
||||
/** Installs the plugin package at `path`: a folder, its `plugin.toml`, or a `.zip` of the folder. */
|
||||
pluginInstallDir: (path: string) => typedError<PluginInfo, IdeError>(__TAURI_INVOKE("plugin_install_dir", { path })),
|
||||
/**
|
||||
* Plugin packages available locally for installation: `<resources>/plugins`
|
||||
@@ -1688,7 +1688,7 @@ export type CatalogPlugin = {
|
||||
authors: string[],
|
||||
homepage: string | null,
|
||||
changeNotes: string | null,
|
||||
/** Package directory; install with `install_from_dir(path)`. */
|
||||
/** Package directory; install with `install_from_path(path)`. */
|
||||
path: IpcPath,
|
||||
hasCode: boolean,
|
||||
permissions: Permission[],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import { Channel } from "@tauri-apps/api/core";
|
||||
import type { ActiveTheme, CatalogPlugin, PluginEvent, PluginInfo, PluginsSnapshot } from "@/bindings";
|
||||
import { tx } from "@/platform/i18n";
|
||||
import { call, commands, IpcError, isTauri } from "@/platform/ipc";
|
||||
|
||||
export interface PluginsBackend {
|
||||
@@ -21,7 +22,8 @@ export interface PluginsBackend {
|
||||
/** The selected theme's resolved frame colour (`#rrggbb[aa]`), which new windows open in. */
|
||||
setThemeFrame(color: string | null): Promise<void>;
|
||||
/** Folder picker; `null` when cancelled. */
|
||||
pickFolder(): Promise<string | null>;
|
||||
/** A package to install: a `.zip`, or the `plugin.toml` of a folder. */
|
||||
pickPackage(): Promise<string | null>;
|
||||
/** Subscribes to backend plugin events (this window only). */
|
||||
onEvent(handler: (e: PluginEvent) => void): () => void;
|
||||
}
|
||||
@@ -45,9 +47,13 @@ export const tauriPluginsBackend: PluginsBackend = {
|
||||
setThemeFrame: async (color) => {
|
||||
await call(commands.pluginSetThemeFrame(color));
|
||||
},
|
||||
pickFolder: async () => {
|
||||
pickPackage: async () => {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const picked = await open({ directory: true, multiple: false, title: "Install Plugin from Disk" });
|
||||
const picked = await open({
|
||||
multiple: false,
|
||||
title: tx("Install Plugin from Disk"),
|
||||
filters: [{ name: tx("Plugin package"), extensions: ["zip", "toml"] }],
|
||||
});
|
||||
return (Array.isArray(picked) ? picked[0] : picked) ?? null;
|
||||
},
|
||||
onEvent: (handler) => {
|
||||
@@ -247,7 +253,7 @@ export function createMemoryPluginsBackend(
|
||||
return t;
|
||||
},
|
||||
setThemeFrame: async () => {},
|
||||
pickFolder: async () => null,
|
||||
pickPackage: async () => null,
|
||||
onEvent: (handler) => {
|
||||
listeners.add(handler);
|
||||
return () => void listeners.delete(handler);
|
||||
|
||||
@@ -124,25 +124,25 @@ export function compareVersions(a: string, b: string): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Asks for a plugin folder (containing plugin.toml) and installs it. Returns the new plugin id. */
|
||||
/** Asks for a plugin package (a `.zip` or a `plugin.toml`) and installs it. Returns the new plugin id. */
|
||||
export async function installFromDisk(path?: string): Promise<string | null> {
|
||||
const backend = pluginsBackend();
|
||||
let dir: string | null = path ?? null;
|
||||
let source: string | null = path ?? null;
|
||||
try {
|
||||
dir ??= await backend.pickFolder();
|
||||
source ??= await backend.pickPackage();
|
||||
} catch (e) {
|
||||
notifications().notify({
|
||||
group: "plugins",
|
||||
level: "error",
|
||||
title: "Could not open the folder picker",
|
||||
title: "Could not open the file picker",
|
||||
body: errorText(e),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
if (!dir) return null;
|
||||
if (!source) return null;
|
||||
usePlugins.setState({ installing: true });
|
||||
try {
|
||||
const info = await backend.installDir(dir);
|
||||
const info = await backend.installDir(source);
|
||||
notifications().notify({
|
||||
group: "plugins",
|
||||
level: info.status === "failed" ? "warning" : "info",
|
||||
|
||||
@@ -523,7 +523,8 @@
|
||||
"Could not install plugin": "无法安装插件",
|
||||
"Could not invalidate caches": "无法清除缓存",
|
||||
"Could not open project": "无法打开项目",
|
||||
"Could not open the folder picker": "无法打开文件夹选择器",
|
||||
"Could not open the file picker": "无法打开文件选择器",
|
||||
"Plugin package": "插件包",
|
||||
"Could not reach GitHub": "无法连接到 GitHub",
|
||||
"Could not save": "无法保存",
|
||||
"Coverage": "覆盖率",
|
||||
|
||||
Reference in new issue
Заблокувати користувача