Файли
openidea/src/features/plugins/index.ts
T
l0ng-ai 0024a1c705 feat(plugins): keep a plugin's signer across updates and announce updates
- Index format: fields are only added; an IDE reading a newer `format`
  still applies its revocations and `moved`, lists no plugins and asks to
  be updated, instead of failing to read the index.
- Key continuity: an installed plugin updates silently only to a package
  signed with the same key, or a key it endorsed (`KeyEntry.endorsement`,
  `index --previous-key`, `openidea-plugin endorse`). Another signer, or an
  unsigned package replacing a signed one, is shown on the install page and
  needs the user's confirmation (`Consent.signerChange`).
- Official key: release builds carry the official publisher's public key
  (`OPENIDEA_OFFICIAL_PLUGIN_KEYS`, derived from PLUGIN_SIGNING_KEY);
  `openidea.*` plugins must be signed with it or a key it endorsed,
  whatever the index lists.
- Updates: the daily marketplace check announces new versions once, with
  Update All (also in the gear menu and as an action). An update that asks
  for nothing new installs with the grants the installed version has.
- Index entries need `engine`; `categories` beyond the first three are
  ignored instead of rejecting the manifest.
2026-11-04 17:47:46 +08:00

148 рядки
5.7 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* Plugins: Settings › Plugins (installed list, enable/disable, install from
* disk with a permission page, uninstall, theme selection) and the runtime
* bridge to the plugin host (`ide_host::plugins`): UI contributions are
* projected by `projection.ts` / `points.tsx`.
*
* Actions: `plugins.settings`, `plugins.installFromDisk`, `plugins.reloadAll`,
* `plugins.importIntellijTheme` (a theme plugin file; `arg`: its path),
* `plugins.importInstalledIntellijTheme` (from the JetBrains IDEs on this machine),
* plus one action per plugin command: `plugin.<pluginId>.<commandId>`
* (searchable in Search Everywhere › Actions and bindable in the keymap editor;
* a string arg is forwarded to the plugin).
*
* Editor contribution `plugins.lineMarkers`: gutter icons from plugin
* `line-markers` providers (`lineMarkerGutter.ts`).
*/
import { registerEditorContribution } from "@/editor/api";
import { lazyComponent } from "@/ui";
import { executeAction, registerActions } from "@/platform/actions";
import { useProject } from "@/platform/project";
import { editorService, registerOverlay, registerSettingsPage } from "@/workbench/contributions";
import {
followPluginChanges,
providePluginThemePreviews,
startPluginBridge,
syncProjection,
trackPluginTheme,
} from "./bridge";
import { ConsentDialogHost, provideConsentDialog } from "./consent";
import { trackIconTheme } from "./iconThemes";
import { pluginLineMarkerGutter } from "./lineMarkerGutter";
import { defineBuiltinUiPoints } from "./points";
import { startViewUi } from "./uiPoints";
import { importIntellijTheme, showInstalledIntellijThemes } from "./intellijImport";
import { IntellijThemesDialogHost } from "./IntellijThemesDialog";
import { installFromDisk, refreshPlugins, reloadPlugin, SETTINGS_PAGE_ID, updateAllPlugins, usePlugins } from "./store";
function registerPluginsPage(): () => void {
return registerSettingsPage({
id: SETTINGS_PAGE_ID,
title: "Plugins",
// After Editor, as in IntelliJ IDEA.
order: 250,
keywords: ["plugin", "extension", "wasm", "install", "theme", "marketplace"],
component: lazyComponent(() => import("./PluginsPage").then((m) => m.PluginsPage)),
});
}
/** Settings › Plugins for the Settings window: the page plus the plugin theme. */
export function activateSettingsPages(): () => void {
const disposers = [
registerPluginsPage(),
registerOverlay({ id: "plugins.consent", component: ConsentDialogHost }),
registerOverlay({ id: "plugins.intellijThemes", component: IntellijThemesDialogHost }),
provideConsentDialog(),
defineBuiltinUiPoints(),
followPluginChanges(),
syncProjection(),
trackPluginTheme(),
trackIconTheme(),
];
void refreshPlugins();
return () => disposers.reverse().forEach((d) => d());
}
export function activate(): () => void {
const disposers = [
registerPluginsPage(),
registerOverlay({ id: "plugins.consent", component: ConsentDialogHost }),
registerOverlay({ id: "plugins.intellijThemes", component: IntellijThemesDialogHost }),
provideConsentDialog(),
providePluginThemePreviews(),
registerActions([
{
id: "plugins.settings",
title: "Plugins…",
category: "Plugins",
icon: "plugin",
keywords: ["extensions", "manage plugins"],
run: () => void executeAction("file.settings", SETTINGS_PAGE_ID),
},
{
id: "plugins.installFromDisk",
title: "Install Plugin from Disk…",
category: "Plugins",
icon: "plugin",
keywords: ["extension", "wasm", "plugin.toml"],
run: async ({ arg }) => {
await installFromDisk(typeof arg === "string" ? arg : undefined);
},
},
{
id: "plugins.updateAll",
title: "Update All Plugins",
category: "Plugins",
icon: "plugin",
keywords: ["upgrade", "marketplace", "new version"],
run: async () => {
await updateAllPlugins();
},
},
{
id: "plugins.importIntellijTheme",
title: "Import IntelliJ Theme from File…",
category: "Plugins",
icon: "plugin",
keywords: ["intellij", "jetbrains", "theme", "jar", "theme.json", "color scheme"],
// Installing needs a project's window.
when: "projectOpen",
run: async ({ arg }) => {
await importIntellijTheme(typeof arg === "string" ? arg : undefined);
},
},
{
id: "plugins.importInstalledIntellijTheme",
title: "Import Theme from IntelliJ IDE…",
category: "Plugins",
icon: "plugin",
keywords: ["intellij", "jetbrains", "theme", "color scheme", "migrate"],
when: "projectOpen",
run: () => showInstalledIntellijThemes(),
},
{
id: "plugins.reloadAll",
title: "Reload Plugins",
category: "Plugins",
icon: "refresh",
keywords: ["dev plugin", "rebuild"],
run: async () => {
const ids = usePlugins.getState().plugins.map((p) => p.id);
for (const id of ids) await reloadPlugin(id);
},
},
]),
startPluginBridge(),
// Plugin custom editors (tab kind) and context menu entries.
startViewUi((input) => void editorService().openInput(input)),
// Gutter icons from plugins' `line-markers` providers (next to the LSP ones).
registerEditorContribution({ id: "plugins.lineMarkers", order: 57, extensions: pluginLineMarkerGutter }),
// Dev plugins (`<project>/.openidea/plugins`) depend on the open project.
useProject.subscribe((s, prev) => {
if (s.project?.root !== prev.project?.root || s.project?.trusted !== prev.project?.trusted) void refreshPlugins();
}),
];
void refreshPlugins();
return () => disposers.reverse().forEach((d) => d());
}