Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
177 lines
5.8 KiB
JavaScript
177 lines
5.8 KiB
JavaScript
"use strict";
|
|
|
|
class PluginManager {
|
|
constructor(options) {
|
|
this.database = options.database;
|
|
this.packageStore = options.packageStore;
|
|
this.runtimeSupervisor = options.runtimeSupervisor;
|
|
this.contributionService = options.contributionService ?? {
|
|
initialize: () => this.runtimeSupervisor.startEnabled(),
|
|
onPluginDisabled() {},
|
|
onPluginEnabled: (pluginId) => this.runtimeSupervisor.start(pluginId),
|
|
};
|
|
this.beforeClose = options.beforeClose ?? null;
|
|
this.initialized = false;
|
|
this.initializePromise = null;
|
|
this.mutationTail = Promise.resolve();
|
|
this.shuttingDown = false;
|
|
this.shutdownPromise = null;
|
|
}
|
|
|
|
initialize() {
|
|
this.initializePromise ??= this.#initialize();
|
|
return this.initializePromise;
|
|
}
|
|
|
|
async #initialize() {
|
|
await this.packageStore.initialize();
|
|
await this.contributionService.initialize();
|
|
this.initialized = true;
|
|
}
|
|
|
|
async #ready() {
|
|
await this.initialize();
|
|
}
|
|
|
|
#mutate(operation) {
|
|
if (this.shuttingDown) return Promise.reject(new Error("Plugin manager is shutting down"));
|
|
const result = this.mutationTail.then(operation, operation);
|
|
this.mutationTail = result.then(() => undefined, () => undefined);
|
|
return result;
|
|
}
|
|
|
|
async list() {
|
|
await this.#ready();
|
|
return this.database.listPlugins();
|
|
}
|
|
|
|
install(archivePath, options) {
|
|
return this.#mutate(async () => {
|
|
await this.#ready();
|
|
let stoppedPlugin = null;
|
|
let plugin;
|
|
try {
|
|
plugin = await this.packageStore.install(archivePath, {
|
|
enable: options?.enable === true,
|
|
beforeActivate: async ({ pluginId, previousPlugin }) => {
|
|
if (!previousPlugin?.enabled) return;
|
|
this.database.setEnabled(pluginId, false);
|
|
this.contributionService.onPluginDisabled(pluginId);
|
|
stoppedPlugin = { pluginId, version: previousPlugin.activeVersion };
|
|
await this.runtimeSupervisor.stop(pluginId);
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (stoppedPlugin) {
|
|
const current = this.database.getActivePlugin(stoppedPlugin.pluginId);
|
|
if (current?.activeVersion === stoppedPlugin.version) {
|
|
this.database.setEnabled(stoppedPlugin.pluginId, true);
|
|
try {
|
|
await this.contributionService.onPluginEnabled(stoppedPlugin.pluginId);
|
|
} catch {
|
|
this.database.setEnabled(stoppedPlugin.pluginId, false);
|
|
}
|
|
}
|
|
}
|
|
throw error;
|
|
}
|
|
if (plugin?.enabled) {
|
|
try {
|
|
await this.contributionService.onPluginEnabled(plugin.id);
|
|
} catch (error) {
|
|
this.database.setEnabled(plugin.id, false);
|
|
this.contributionService.onPluginDisabled(plugin.id);
|
|
if (stoppedPlugin && stoppedPlugin.version !== plugin.activeVersion) {
|
|
try {
|
|
this.database.setActiveVersion(stoppedPlugin.pluginId, stoppedPlugin.version, {
|
|
enabled: true,
|
|
expectedActiveVersion: plugin.activeVersion,
|
|
});
|
|
await this.contributionService.onPluginEnabled(stoppedPlugin.pluginId);
|
|
} catch {
|
|
const restored = this.database.getActivePlugin(stoppedPlugin.pluginId);
|
|
if (restored?.activeVersion === stoppedPlugin.version && restored.enabled) {
|
|
this.database.setEnabled(stoppedPlugin.pluginId, false);
|
|
}
|
|
}
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
return plugin;
|
|
});
|
|
}
|
|
|
|
setEnabled(pluginId, enabled) {
|
|
return this.#mutate(async () => {
|
|
await this.#ready();
|
|
if (enabled) {
|
|
const plugin = this.database.getActivePlugin(pluginId);
|
|
if (plugin?.runtime?.quarantinedAt != null) {
|
|
this.database.clearQuarantine(pluginId, plugin.activeVersion);
|
|
}
|
|
this.database.setEnabled(pluginId, true);
|
|
try { await this.contributionService.onPluginEnabled(pluginId); }
|
|
catch (error) {
|
|
this.database.setEnabled(pluginId, false);
|
|
this.contributionService.onPluginDisabled(pluginId);
|
|
throw error;
|
|
}
|
|
} else {
|
|
this.database.setEnabled(pluginId, false);
|
|
this.contributionService.onPluginDisabled(pluginId);
|
|
await this.runtimeSupervisor.stop(pluginId);
|
|
}
|
|
return this.database.getActivePlugin(pluginId);
|
|
});
|
|
}
|
|
|
|
restart(pluginId) {
|
|
return this.#mutate(async () => {
|
|
await this.#ready();
|
|
await this.runtimeSupervisor.restart(pluginId);
|
|
return this.database.getActivePlugin(pluginId);
|
|
});
|
|
}
|
|
|
|
uninstall(pluginId) {
|
|
return this.#mutate(async () => {
|
|
await this.#ready();
|
|
const plugin = this.database.getActivePlugin(pluginId);
|
|
if (plugin) {
|
|
this.database.setEnabled(pluginId, false);
|
|
this.contributionService.onPluginDisabled(pluginId);
|
|
}
|
|
await this.runtimeSupervisor.stop(pluginId);
|
|
return this.packageStore.uninstall(pluginId);
|
|
});
|
|
}
|
|
|
|
shutdown() {
|
|
this.shutdownPromise ??= this.#shutdown();
|
|
return this.shutdownPromise;
|
|
}
|
|
|
|
async #shutdown() {
|
|
this.shuttingDown = true;
|
|
let supervisorError;
|
|
const supervisorShutdown = Promise.resolve()
|
|
.then(() => this.runtimeSupervisor.shutdown())
|
|
.catch((error) => { supervisorError = error; });
|
|
await this.mutationTail;
|
|
if (this.initializePromise) {
|
|
try { await this.initializePromise; } catch {}
|
|
}
|
|
await supervisorShutdown;
|
|
const errors = supervisorError ? [supervisorError] : [];
|
|
try { await this.beforeClose?.(); }
|
|
catch (error) { errors.push(error); }
|
|
try { this.database.close(); }
|
|
catch (error) { errors.push(error); }
|
|
if (errors.length === 1) throw errors[0];
|
|
if (errors.length > 1) throw new AggregateError(errors, "Plugin host shutdown failed");
|
|
}
|
|
}
|
|
|
|
module.exports = { PluginManager };
|