fluxplugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +50 -0
- package/dist/browser/chunks/chunk-Q75AOCXD.js +2544 -0
- package/dist/browser/chunks/chunk-ZZ25CRMR.js +759 -0
- package/dist/browser/index.js +138 -0
- package/dist/browser/manager-ui.js +165 -0
- package/dist/browser/react.js +73 -0
- package/dist/cli.js +1635 -0
- package/dist/node/chunks/chunk-BZ7NZF7I.js +990 -0
- package/dist/node/chunks/chunk-XPLGAEBH.js +2623 -0
- package/dist/node/cli-api.js +843 -0
- package/dist/node/index.js +138 -0
- package/dist/node/main.js +61 -0
- package/dist/node/preload.js +56 -0
- package/dist/types/cli/bin.d.ts +1 -0
- package/dist/types/cli/build.d.ts +7 -0
- package/dist/types/cli/cli.d.ts +2 -0
- package/dist/types/cli/config.d.ts +9 -0
- package/dist/types/cli/host-templates.d.ts +6 -0
- package/dist/types/cli/host.d.ts +16 -0
- package/dist/types/cli/index.d.ts +6 -0
- package/dist/types/cli/shared-modules.d.ts +8 -0
- package/dist/types/cli/templates.d.ts +10 -0
- package/dist/types/core/api.d.ts +11 -0
- package/dist/types/core/common.d.ts +69 -0
- package/dist/types/core/deps.d.ts +34 -0
- package/dist/types/core/events.d.ts +58 -0
- package/dist/types/core/hooks.d.ts +55 -0
- package/dist/types/core/index.d.ts +18 -0
- package/dist/types/core/ipc.d.ts +148 -0
- package/dist/types/core/manager.d.ts +155 -0
- package/dist/types/core/manifest.d.ts +88 -0
- package/dist/types/core/permissions.d.ts +45 -0
- package/dist/types/core/plugin.d.ts +214 -0
- package/dist/types/core/protocol.d.ts +86 -0
- package/dist/types/core/registry.d.ts +88 -0
- package/dist/types/core/routes.d.ts +105 -0
- package/dist/types/core/schema.d.ts +99 -0
- package/dist/types/core/semver.d.ts +16 -0
- package/dist/types/core/slots.d.ts +75 -0
- package/dist/types/core/storage.d.ts +55 -0
- package/dist/types/core/styles.d.ts +34 -0
- package/dist/types/main/adapters.d.ts +81 -0
- package/dist/types/main/discover.d.ts +21 -0
- package/dist/types/main/framework.d.ts +78 -0
- package/dist/types/main/fs-storage.d.ts +33 -0
- package/dist/types/main/index.d.ts +8 -0
- package/dist/types/main/registry.d.ts +75 -0
- package/dist/types/main/security.d.ts +24 -0
- package/dist/types/main/serve-web.d.ts +14 -0
- package/dist/types/main/tar.d.ts +10 -0
- package/dist/types/manager-ui/index.d.ts +7 -0
- package/dist/types/preload/index.d.ts +27 -0
- package/dist/types/react/base-css.d.ts +2 -0
- package/dist/types/react/components.d.ts +89 -0
- package/dist/types/react/http-bridge.d.ts +10 -0
- package/dist/types/react/index.d.ts +6 -0
- package/dist/types/react/palette.d.ts +10 -0
- package/dist/types/react/router.d.ts +47 -0
- package/dist/types/react/runtime.d.ts +99 -0
- package/docs/ARCHITECTURE.md +31 -0
- package/docs/PLUGIN-AUTHORS.md +11 -0
- package/docs/RELEASE.md +20 -0
- package/docs/SECURITY.md +18 -0
- package/docs/screenshots/command-palette.png +0 -0
- package/docs/screenshots/dashboard.png +0 -0
- package/docs/screenshots/plugin-details.png +0 -0
- package/docs/screenshots/plugin-manager.png +0 -0
- package/docs/screenshots/stats.png +0 -0
- package/package.json +112 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { FrameworkError, type Disposable } from "./common.js";
|
|
2
|
+
import type { Permission } from "./manifest.js";
|
|
3
|
+
import { type Infer, type Schema } from "./schema.js";
|
|
4
|
+
import { Registry } from "./registry.js";
|
|
5
|
+
/** A shared description of one IPC endpoint. Import it from both main and renderer code to get end-to-end types. */
|
|
6
|
+
export interface IpcContract<I = unknown, O = unknown> {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly input?: Schema<I>;
|
|
9
|
+
readonly output?: Schema<O>;
|
|
10
|
+
/** Permission the *calling* plugin must hold when the call originates from a plugin. */
|
|
11
|
+
readonly permission?: Permission;
|
|
12
|
+
/** Phantom fields carrying the types (never set at runtime). */
|
|
13
|
+
readonly _in?: I;
|
|
14
|
+
readonly _out?: O;
|
|
15
|
+
}
|
|
16
|
+
export interface IpcCallContext {
|
|
17
|
+
/** Plugin that owns the handler. */
|
|
18
|
+
pluginId: string;
|
|
19
|
+
/** Plugin (or `host`) that issued the call. */
|
|
20
|
+
caller: string;
|
|
21
|
+
/** Opaque identifier of the sender (e.g. webContents id) provided by the transport. */
|
|
22
|
+
senderId?: number | string;
|
|
23
|
+
}
|
|
24
|
+
export type IpcHandler<I, O> = (input: I, ctx: IpcCallContext) => O | Promise<O>;
|
|
25
|
+
export interface IpcDefinition<I = unknown, O = unknown> extends IpcContract<I, O> {
|
|
26
|
+
readonly handler: IpcHandler<I, O>;
|
|
27
|
+
readonly contract: IpcContract<I, O>;
|
|
28
|
+
}
|
|
29
|
+
export declare function defineIpcContract<I = void, O = unknown>(c: {
|
|
30
|
+
name: string;
|
|
31
|
+
input?: Schema<I>;
|
|
32
|
+
output?: Schema<O>;
|
|
33
|
+
permission?: Permission;
|
|
34
|
+
}): IpcContract<I, O>;
|
|
35
|
+
/** Declares an IPC endpoint with its handler. Types of `handler` are inferred from the schemas. */
|
|
36
|
+
export declare function defineIPC<IS extends Schema<any> | undefined = undefined, OS extends Schema<any> | undefined = undefined>(def: {
|
|
37
|
+
name: string;
|
|
38
|
+
input?: IS;
|
|
39
|
+
output?: OS;
|
|
40
|
+
permission?: Permission;
|
|
41
|
+
handler: IpcHandler<IS extends Schema<any> ? Infer<IS> : void, OS extends Schema<any> ? Infer<OS> : unknown>;
|
|
42
|
+
}): IpcDefinition<IS extends Schema<any> ? Infer<IS> : void, OS extends Schema<any> ? Infer<OS> : unknown>;
|
|
43
|
+
export type Unsubscribe = () => void;
|
|
44
|
+
/** What the main-side registry needs from the environment to talk to renderers. */
|
|
45
|
+
export interface IpcTransport {
|
|
46
|
+
/** Sends an event from main to every renderer (or one target when `target` is given). */
|
|
47
|
+
broadcast(channel: string, payload: unknown, target?: number | string): void;
|
|
48
|
+
}
|
|
49
|
+
interface HandlerEntry {
|
|
50
|
+
name: string;
|
|
51
|
+
handler: IpcHandler<any, any>;
|
|
52
|
+
input?: Schema<any>;
|
|
53
|
+
output?: Schema<any>;
|
|
54
|
+
permission?: Permission;
|
|
55
|
+
owner: string;
|
|
56
|
+
}
|
|
57
|
+
export interface InvokeOptions {
|
|
58
|
+
caller?: string;
|
|
59
|
+
senderId?: number | string;
|
|
60
|
+
}
|
|
61
|
+
export declare class IpcError extends FrameworkError {
|
|
62
|
+
readonly channel: string;
|
|
63
|
+
constructor(message: string, code: string, channel: string);
|
|
64
|
+
}
|
|
65
|
+
/** Serializable error shape sent back to renderers (stack traces never cross the boundary). */
|
|
66
|
+
export interface IpcErrorPayload {
|
|
67
|
+
code: string;
|
|
68
|
+
message: string;
|
|
69
|
+
issues?: {
|
|
70
|
+
path: string;
|
|
71
|
+
message: string;
|
|
72
|
+
}[];
|
|
73
|
+
}
|
|
74
|
+
export declare function serializeError(e: unknown): IpcErrorPayload;
|
|
75
|
+
export declare class IpcRegistry extends Registry<HandlerEntry> {
|
|
76
|
+
private oneWay;
|
|
77
|
+
private transport;
|
|
78
|
+
/** Hook for the manager: ensures the owner plugin is active (`onIPC:<name>`) and checks caller permissions. */
|
|
79
|
+
beforeInvoke?: (name: string, opts: InvokeOptions) => Promise<void>;
|
|
80
|
+
/** Permission check provided by the manager. */
|
|
81
|
+
assertCallerPermission?: (caller: string, permission: Permission, channel: string) => void;
|
|
82
|
+
/** Observer used for diagnostics. */
|
|
83
|
+
onCall?: (info: {
|
|
84
|
+
name: string;
|
|
85
|
+
owner: string;
|
|
86
|
+
caller: string;
|
|
87
|
+
ms: number;
|
|
88
|
+
error?: unknown;
|
|
89
|
+
}) => void;
|
|
90
|
+
constructor();
|
|
91
|
+
setTransport(t: IpcTransport | undefined): void;
|
|
92
|
+
handle<I, O>(def: IpcContract<I, O>, handler: IpcHandler<I, O>, owner: string): Disposable;
|
|
93
|
+
handle<I = unknown, O = unknown>(name: string, handler: IpcHandler<I, O>, owner: string, opts?: {
|
|
94
|
+
input?: Schema<I>;
|
|
95
|
+
output?: Schema<O>;
|
|
96
|
+
permission?: Permission;
|
|
97
|
+
}): Disposable;
|
|
98
|
+
/** Registers a full `IpcDefinition` (contract + handler). */
|
|
99
|
+
register(def: IpcDefinition<any, any>, owner: string): Disposable;
|
|
100
|
+
/** Dispatches a call: validate input → permission → handler → validate output. */
|
|
101
|
+
invoke<O = unknown>(name: string, input: unknown, opts?: InvokeOptions): Promise<O>;
|
|
102
|
+
/** Fire-and-forget messages from renderer/other plugins to main (`ipc.on`). */
|
|
103
|
+
on(name: string, fn: (payload: unknown, ctx: IpcCallContext) => void, owner: string): Disposable;
|
|
104
|
+
/** Delivers a one-way message to main-side listeners. */
|
|
105
|
+
dispatch(name: string, payload: unknown, opts?: InvokeOptions): number;
|
|
106
|
+
/** Main → renderer push. */
|
|
107
|
+
send(channel: string, payload: unknown, target?: number | string): void;
|
|
108
|
+
removeListenersByOwner(owner: string): void;
|
|
109
|
+
}
|
|
110
|
+
export type PreloadMember = {
|
|
111
|
+
kind: "invoke";
|
|
112
|
+
channel: string;
|
|
113
|
+
} | {
|
|
114
|
+
kind: "send";
|
|
115
|
+
channel: string;
|
|
116
|
+
} | {
|
|
117
|
+
kind: "event";
|
|
118
|
+
channel: string;
|
|
119
|
+
};
|
|
120
|
+
export type PreloadApiSpec = Record<string, PreloadMember>;
|
|
121
|
+
export type PreloadMemberInput = string | PreloadMember;
|
|
122
|
+
/** Helpers mirroring the declarative shape so plugin code reads naturally. */
|
|
123
|
+
export declare const preloadMember: {
|
|
124
|
+
invoke: (channel: string) => PreloadMember;
|
|
125
|
+
send: (channel: string) => PreloadMember;
|
|
126
|
+
event: (channel: string) => PreloadMember;
|
|
127
|
+
};
|
|
128
|
+
export interface PreloadExposure {
|
|
129
|
+
id: string;
|
|
130
|
+
namespace: string;
|
|
131
|
+
spec: PreloadApiSpec;
|
|
132
|
+
owner: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Registry of the APIs plugins want available to the renderer under `window.plugins.<namespace>`.
|
|
136
|
+
* The sandboxed preload cannot execute plugin code, so exposures are *data*: each member maps to an IPC
|
|
137
|
+
* channel. The generic preload bridge (see `fluxplugin/preload`) turns them into callable functions.
|
|
138
|
+
*/
|
|
139
|
+
export declare class PreloadRegistry extends Registry<PreloadExposure> {
|
|
140
|
+
constructor();
|
|
141
|
+
expose(namespace: string, members: Record<string, PreloadMemberInput>, owner: string): Disposable;
|
|
142
|
+
/** JSON-serializable manifest sent to the preload bridge. */
|
|
143
|
+
manifest(): Record<string, PreloadApiSpec>;
|
|
144
|
+
}
|
|
145
|
+
/** Augment in your host or plugin to get typed access to `window.plugins`: `interface PluginApis { analytics: AnalyticsApi }` */
|
|
146
|
+
export interface PluginApis {
|
|
147
|
+
}
|
|
148
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { LogStore, type Disposable } from "./common.js";
|
|
2
|
+
import { EventBus } from "./events.js";
|
|
3
|
+
import { HookSystem } from "./hooks.js";
|
|
4
|
+
import { IpcRegistry, PreloadRegistry } from "./ipc.js";
|
|
5
|
+
import { type Permission, type PluginManifest } from "./manifest.js";
|
|
6
|
+
import { PermissionManager } from "./permissions.js";
|
|
7
|
+
import type { PluginContext, PluginDefinition, PluginInfo, Runtime } from "./plugin.js";
|
|
8
|
+
import { CommandRegistry, MenuRegistry, ServiceRegistry } from "./registry.js";
|
|
9
|
+
import { RouteRegistry } from "./routes.js";
|
|
10
|
+
import { ExtensionPointRegistry, SlotRegistry } from "./slots.js";
|
|
11
|
+
import { type PluginConfig, type PluginState_, type StorageBackend } from "./storage.js";
|
|
12
|
+
import { StyleRegistry } from "./styles.js";
|
|
13
|
+
export interface PluginSource {
|
|
14
|
+
/** Raw manifest (validated on registration). */
|
|
15
|
+
manifest: unknown;
|
|
16
|
+
/** Absolute plugin directory (main process). */
|
|
17
|
+
root?: string;
|
|
18
|
+
/** Human-readable origin, e.g. `dir:/plugins/foo`. */
|
|
19
|
+
origin?: string;
|
|
20
|
+
/** Imports the plugin entry for this runtime. Must return a *fresh* module on every call (hot reload). */
|
|
21
|
+
load(): Promise<PluginDefinition<any>>;
|
|
22
|
+
}
|
|
23
|
+
export type Phase = "load" | "initialize" | "activate" | "ready" | "deactivate" | "unload";
|
|
24
|
+
export interface IpcBridge {
|
|
25
|
+
invoke(name: string, input: unknown, caller: string): Promise<unknown>;
|
|
26
|
+
send(channel: string, payload: unknown, caller: string): void;
|
|
27
|
+
on(channel: string, fn: (payload: unknown) => void): Disposable;
|
|
28
|
+
}
|
|
29
|
+
export interface HostApiProvider<T = unknown> {
|
|
30
|
+
factory: (who: {
|
|
31
|
+
pluginId: string;
|
|
32
|
+
root?: string;
|
|
33
|
+
manifest: PluginManifest;
|
|
34
|
+
}) => T;
|
|
35
|
+
permission?: Permission;
|
|
36
|
+
}
|
|
37
|
+
export interface PluginManagerOptions {
|
|
38
|
+
runtime: Runtime;
|
|
39
|
+
storage?: StorageBackend;
|
|
40
|
+
permissions?: PermissionManager;
|
|
41
|
+
logs?: LogStore;
|
|
42
|
+
frameworkVersion?: string;
|
|
43
|
+
timeouts?: Partial<Record<Phase, number>>;
|
|
44
|
+
/** Plugins crashing this many times inside `crashWindowMs` are disabled automatically. Default 3. */
|
|
45
|
+
maxCrashes?: number;
|
|
46
|
+
crashWindowMs?: number;
|
|
47
|
+
/** Persists the list of disabled plugin ids. */
|
|
48
|
+
stateStore?: {
|
|
49
|
+
load(): Promise<string[]>;
|
|
50
|
+
save(ids: string[]): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
/** Renderer runtimes route IPC through this bridge instead of the (empty) local registry. */
|
|
53
|
+
ipcBridge?: IpcBridge;
|
|
54
|
+
/** Called when a plugin emits an event with `{ broadcast: true }` (forward it to the other process). */
|
|
55
|
+
onBroadcastEvent?: (name: string, payload: unknown, source: string) => void;
|
|
56
|
+
/** Reject slot contributions to slots the host did not declare. */
|
|
57
|
+
strictSlots?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare class PluginManager {
|
|
60
|
+
private readonly opts;
|
|
61
|
+
readonly runtime: Runtime;
|
|
62
|
+
readonly logs: LogStore;
|
|
63
|
+
readonly permissions: PermissionManager;
|
|
64
|
+
readonly events: EventBus;
|
|
65
|
+
readonly hooks: HookSystem;
|
|
66
|
+
readonly commands: CommandRegistry;
|
|
67
|
+
readonly menus: MenuRegistry;
|
|
68
|
+
readonly routes: RouteRegistry<any>;
|
|
69
|
+
readonly services: ServiceRegistry;
|
|
70
|
+
readonly slots: SlotRegistry<any>;
|
|
71
|
+
readonly extensionPoints: ExtensionPointRegistry;
|
|
72
|
+
readonly ipc: IpcRegistry;
|
|
73
|
+
readonly preload: PreloadRegistry;
|
|
74
|
+
readonly styles: StyleRegistry;
|
|
75
|
+
readonly storageBackend: StorageBackend;
|
|
76
|
+
private readonly records;
|
|
77
|
+
private readonly states;
|
|
78
|
+
private readonly hostApis;
|
|
79
|
+
private readonly timeouts;
|
|
80
|
+
private readonly log;
|
|
81
|
+
private listeners;
|
|
82
|
+
private _version;
|
|
83
|
+
private disposedFlag;
|
|
84
|
+
constructor(opts: PluginManagerOptions);
|
|
85
|
+
get version(): number;
|
|
86
|
+
subscribe: (l: () => void) => (() => void);
|
|
87
|
+
private changed;
|
|
88
|
+
/** Exposes a controlled capability to plugins (`context.host.api(name)`), optionally gated by a permission. */
|
|
89
|
+
provide<T>(name: string, factory: HostApiProvider<T>["factory"], permission?: Permission): Disposable;
|
|
90
|
+
/** Persisted state (disabled plugins) must be loaded before registering sources. */
|
|
91
|
+
init(): Promise<void>;
|
|
92
|
+
private persistedDisabled;
|
|
93
|
+
/**
|
|
94
|
+
* Discovers + validates one plugin. Invalid plugins are recorded in the `error` state (so the UI can show why)
|
|
95
|
+
* and never throw — one broken plugin must not stop the others.
|
|
96
|
+
*/
|
|
97
|
+
register(source: PluginSource): PluginInfo;
|
|
98
|
+
unregister(id: string): Promise<void>;
|
|
99
|
+
has(id: string): boolean;
|
|
100
|
+
list(): PluginInfo[];
|
|
101
|
+
info(id: string): PluginInfo;
|
|
102
|
+
/** Reactive per-plugin state store (same instance as `ctx.state`). Survives reloads. */
|
|
103
|
+
stateOf(id: string): PluginState_;
|
|
104
|
+
/** Validated manifest (or the best-effort one for plugins rejected at validation). */
|
|
105
|
+
manifest(id: string): PluginManifest;
|
|
106
|
+
getConfig<T extends Record<string, any> = Record<string, any>>(id: string): PluginConfig<T> | undefined;
|
|
107
|
+
/** Active plugin's context (for hosts and tests). */
|
|
108
|
+
contextOf(id: string): PluginContext | undefined;
|
|
109
|
+
diagnostics(id: string): {
|
|
110
|
+
info: PluginInfo;
|
|
111
|
+
logs: ReturnType<LogStore["query"]>;
|
|
112
|
+
contributions: Record<string, number>;
|
|
113
|
+
};
|
|
114
|
+
private mustGet;
|
|
115
|
+
private graph;
|
|
116
|
+
/** Activates every plugin that wants to start with the application (`onStartup`, `*`, or no activation events). */
|
|
117
|
+
startup(): Promise<void>;
|
|
118
|
+
/** Activates `id` and, first, everything it depends on. Rejects with a `LifecycleError` on failure. */
|
|
119
|
+
activate(id: string): Promise<void>;
|
|
120
|
+
private ensureReady;
|
|
121
|
+
private phase;
|
|
122
|
+
private setState;
|
|
123
|
+
private bringUp;
|
|
124
|
+
/** Records a failure, tears down everything the plugin registered and auto-disables repeat offenders. */
|
|
125
|
+
private fail;
|
|
126
|
+
/** Disposes every contribution of a plugin (store + owner-based sweep as a safety net). */
|
|
127
|
+
private teardown;
|
|
128
|
+
private sweep;
|
|
129
|
+
/** Deactivates `id` after deactivating every active plugin that depends on it. Never throws for plugin errors. */
|
|
130
|
+
deactivate(id: string): Promise<void>;
|
|
131
|
+
private deactivateOne;
|
|
132
|
+
unload(id: string): Promise<void>;
|
|
133
|
+
disable(id: string): Promise<void>;
|
|
134
|
+
enable(id: string, opts?: {
|
|
135
|
+
activate?: boolean;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
/** Development hot reload: deactivate → unload → load a fresh module → activate (if it was active). */
|
|
138
|
+
reload(id: string): Promise<void>;
|
|
139
|
+
private persistDisabled;
|
|
140
|
+
dispose(): Promise<void>;
|
|
141
|
+
/** Activates plugins whose manifest lists `event` (or `*`). Used internally and by hosts for custom events. */
|
|
142
|
+
trigger(event: string): Promise<void>;
|
|
143
|
+
private eventMatches;
|
|
144
|
+
private wireLazyActivation;
|
|
145
|
+
/**
|
|
146
|
+
* Before a plugin is active, its statically declared commands/menus (manifest `contributes`) are visible
|
|
147
|
+
* as lightweight proxies: using one activates the plugin on demand. Replaced by the real registrations on activate.
|
|
148
|
+
*/
|
|
149
|
+
private installLazyProxies;
|
|
150
|
+
private removeLazyProxies;
|
|
151
|
+
private applyDeclarative;
|
|
152
|
+
private createContext;
|
|
153
|
+
}
|
|
154
|
+
/** `onRoute:/students/:id` style patterns: `:param` matches one segment, `*` matches the rest. */
|
|
155
|
+
export declare function routePatternMatches(pattern: string, path: string): boolean;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Infer, type Issue } from "./schema.js";
|
|
2
|
+
/** Version of the plugin API implemented by this framework build. Plugins declare compatibility via `engines`. */
|
|
3
|
+
export declare const FRAMEWORK_VERSION = "1.0.0";
|
|
4
|
+
export declare const FRAMEWORK_ENGINE_KEY = "fluxplugin";
|
|
5
|
+
/** Built-in permission identifiers. Hosts may add their own with the `x.` namespace (e.g. `x.billing.read`). */
|
|
6
|
+
export declare const BUILTIN_PERMISSIONS: readonly ["filesystem.read", "filesystem.write", "network", "database", "ipc", "window", "notifications", "storage", "services", "menus", "commands", "routes", "ui", "events", "hooks", "styles", "clipboard", "shell"];
|
|
7
|
+
export type BuiltinPermission = (typeof BUILTIN_PERMISSIONS)[number];
|
|
8
|
+
export type Permission = BuiltinPermission | `x.${string}`;
|
|
9
|
+
export declare const menuLocations: readonly ["application", "context", "sidebar", "toolbar", "commandPalette", "tray", "settings"];
|
|
10
|
+
export type MenuLocation = (typeof menuLocations)[number];
|
|
11
|
+
export declare const manifestSchema: import("./schema.js").Schema<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
activationEvents: string[];
|
|
16
|
+
deactivationEvents: string[];
|
|
17
|
+
permissions: Permission[];
|
|
18
|
+
dependencies: Record<string, string>;
|
|
19
|
+
optionalDependencies: Record<string, string>;
|
|
20
|
+
peerDependencies: Record<string, string>;
|
|
21
|
+
engines: Record<string, string>;
|
|
22
|
+
contributes: {
|
|
23
|
+
ipc?: string[] | undefined;
|
|
24
|
+
services?: string[] | undefined;
|
|
25
|
+
menus?: {
|
|
26
|
+
id: string;
|
|
27
|
+
location: "application" | "context" | "sidebar" | "toolbar" | "commandPalette" | "tray" | "settings";
|
|
28
|
+
label: string;
|
|
29
|
+
command?: string | undefined;
|
|
30
|
+
}[] | undefined;
|
|
31
|
+
commands?: {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
category?: string | undefined;
|
|
35
|
+
}[] | undefined;
|
|
36
|
+
routes?: {
|
|
37
|
+
path: string;
|
|
38
|
+
icon?: string | undefined;
|
|
39
|
+
title?: string | undefined;
|
|
40
|
+
permission?: Permission | undefined;
|
|
41
|
+
}[] | undefined;
|
|
42
|
+
hooks?: string[] | undefined;
|
|
43
|
+
slots?: string[] | undefined;
|
|
44
|
+
configuration?: unknown;
|
|
45
|
+
};
|
|
46
|
+
styles?: string[] | undefined;
|
|
47
|
+
description?: string | undefined;
|
|
48
|
+
author?: string | {
|
|
49
|
+
name: string;
|
|
50
|
+
email?: string | undefined;
|
|
51
|
+
url?: string | undefined;
|
|
52
|
+
} | undefined;
|
|
53
|
+
homepage?: string | undefined;
|
|
54
|
+
repository?: string | {
|
|
55
|
+
url: string;
|
|
56
|
+
type?: string | undefined;
|
|
57
|
+
} | undefined;
|
|
58
|
+
license?: string | undefined;
|
|
59
|
+
icon?: string | undefined;
|
|
60
|
+
main?: string | undefined;
|
|
61
|
+
preload?: string | undefined;
|
|
62
|
+
renderer?: string | undefined;
|
|
63
|
+
tailwind?: {
|
|
64
|
+
content?: string[] | undefined;
|
|
65
|
+
safelist?: string[] | undefined;
|
|
66
|
+
precompiled?: string | undefined;
|
|
67
|
+
} | undefined;
|
|
68
|
+
integrity?: Record<string, string> | undefined;
|
|
69
|
+
signature?: {
|
|
70
|
+
value: string;
|
|
71
|
+
algorithm: "ed25519";
|
|
72
|
+
publicKey: string;
|
|
73
|
+
} | undefined;
|
|
74
|
+
}>;
|
|
75
|
+
export type PluginManifest = Infer<typeof manifestSchema>;
|
|
76
|
+
export interface ManifestValidation {
|
|
77
|
+
ok: boolean;
|
|
78
|
+
manifest?: PluginManifest;
|
|
79
|
+
issues: Issue[];
|
|
80
|
+
warnings: string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validates a raw manifest object (e.g. parsed `plugin.json`). Also checks engine compatibility
|
|
84
|
+
* against the running framework version and reports non-fatal warnings.
|
|
85
|
+
*/
|
|
86
|
+
export declare function validateManifest(raw: unknown, opts?: {
|
|
87
|
+
frameworkVersion?: string;
|
|
88
|
+
}): ManifestValidation;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Disposable } from "./common.js";
|
|
2
|
+
import type { Permission } from "./manifest.js";
|
|
3
|
+
export interface PermissionSnapshot {
|
|
4
|
+
declared: Permission[];
|
|
5
|
+
granted: Permission[];
|
|
6
|
+
denied: Permission[];
|
|
7
|
+
}
|
|
8
|
+
export interface PermissionManagerOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When true (default) every permission declared in a manifest is granted automatically.
|
|
11
|
+
* Set to false for third-party plugins: declared permissions then need an explicit `grant()`
|
|
12
|
+
* (typically after the user approved them in the Plugin Manager UI), or a matching entry in `approved`.
|
|
13
|
+
*/
|
|
14
|
+
autoGrant?: boolean;
|
|
15
|
+
/** Previously approved permissions (e.g. restored from disk). */
|
|
16
|
+
approved?: Record<string, Permission[]>;
|
|
17
|
+
/** Called whenever grants change so the host can persist them. */
|
|
18
|
+
onChange?: (pluginId: string, snapshot: PermissionSnapshot) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tracks which permissions each plugin declared and which were granted.
|
|
22
|
+
* Every scoped API of `PluginContext` calls `assert()` before doing anything privileged.
|
|
23
|
+
*/
|
|
24
|
+
export declare class PermissionManager {
|
|
25
|
+
private readonly opts;
|
|
26
|
+
private declared;
|
|
27
|
+
private granted;
|
|
28
|
+
private listeners;
|
|
29
|
+
private readonly autoGrant;
|
|
30
|
+
constructor(opts?: PermissionManagerOptions);
|
|
31
|
+
/** Restores grants persisted from a previous session (call before `declare`). */
|
|
32
|
+
restore(approved: Record<string, readonly Permission[]>): void;
|
|
33
|
+
/** Registers a plugin's requested permissions (called when its manifest is loaded). */
|
|
34
|
+
declare(pluginId: string, permissions: readonly Permission[]): void;
|
|
35
|
+
forget(pluginId: string): void;
|
|
36
|
+
has(pluginId: string, permission: Permission): boolean;
|
|
37
|
+
hasAll(pluginId: string, permissions: readonly Permission[]): boolean;
|
|
38
|
+
assert(pluginId: string, permission: Permission, api?: string): void;
|
|
39
|
+
grant(pluginId: string, permission: Permission): void;
|
|
40
|
+
grantAll(pluginId: string): void;
|
|
41
|
+
revoke(pluginId: string, permission: Permission): void;
|
|
42
|
+
snapshot(pluginId: string): PermissionSnapshot;
|
|
43
|
+
onChange(l: (pluginId: string) => void): Disposable;
|
|
44
|
+
private notify;
|
|
45
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { Disposable, Logger } from "./common.js";
|
|
2
|
+
import type { EventContext, EventListener, ListenerOptions, EmitResult } from "./events.js";
|
|
3
|
+
import type { AroundNext, HookContext, HookOptions } from "./hooks.js";
|
|
4
|
+
import type { IpcCallContext, IpcContract, IpcDefinition, IpcHandler, PreloadMemberInput } from "./ipc.js";
|
|
5
|
+
import type { Permission, PluginManifest } from "./manifest.js";
|
|
6
|
+
import type { CommandDefinition, MenuItemDefinition } from "./registry.js";
|
|
7
|
+
import type { RouteDefinition } from "./routes.js";
|
|
8
|
+
import type { Schema } from "./schema.js";
|
|
9
|
+
import type { SlotContribution } from "./slots.js";
|
|
10
|
+
import type { ConfigDefinition, PluginConfig, PluginState_, PluginStorage } from "./storage.js";
|
|
11
|
+
import type { StyleInput } from "./styles.js";
|
|
12
|
+
export type Runtime = "main" | "renderer";
|
|
13
|
+
export type PluginState = "discovered" | "validated" | "loaded" | "initialized" | "activated" | "ready" | "deactivated" | "unloaded" | "disabled" | "error";
|
|
14
|
+
export type MenuInput = Omit<MenuItemDefinition, "id" | "location"> & {
|
|
15
|
+
id: string;
|
|
16
|
+
location: MenuItemDefinition["location"];
|
|
17
|
+
};
|
|
18
|
+
export interface PluginContext<Cfg extends Record<string, any> = Record<string, any>> {
|
|
19
|
+
readonly pluginId: string;
|
|
20
|
+
readonly manifest: PluginManifest;
|
|
21
|
+
readonly runtime: Runtime;
|
|
22
|
+
/** Absolute directory of the plugin on disk (main process only). */
|
|
23
|
+
readonly root: string | undefined;
|
|
24
|
+
readonly logger: Logger;
|
|
25
|
+
readonly config: PluginConfig<Cfg>;
|
|
26
|
+
readonly storage: PluginStorage;
|
|
27
|
+
/** Volatile reactive state shared with `usePluginState` in React. */
|
|
28
|
+
readonly state: PluginState_;
|
|
29
|
+
readonly events: {
|
|
30
|
+
on<T = unknown>(pattern: string, listener: EventListener<T>, opts?: ListenerOptions): Disposable;
|
|
31
|
+
once<T = unknown>(pattern: string, listener: EventListener<T>, opts?: Omit<ListenerOptions, "once">): Disposable;
|
|
32
|
+
emit(name: string, payload?: unknown, opts?: {
|
|
33
|
+
broadcast?: boolean;
|
|
34
|
+
}): EmitResult;
|
|
35
|
+
emitAsync(name: string, payload?: unknown): Promise<EmitResult>;
|
|
36
|
+
};
|
|
37
|
+
readonly commands: {
|
|
38
|
+
register(command: CommandDefinition<any[], any>): Disposable;
|
|
39
|
+
execute<R = unknown>(id: string, ...args: unknown[]): Promise<R>;
|
|
40
|
+
list(): CommandDefinition[];
|
|
41
|
+
};
|
|
42
|
+
readonly routes: {
|
|
43
|
+
register<C = unknown>(route: RouteDefinition<C>): Disposable;
|
|
44
|
+
registerLayout<C = unknown>(id: string, component: C): Disposable;
|
|
45
|
+
};
|
|
46
|
+
readonly menus: {
|
|
47
|
+
register(item: MenuInput): Disposable;
|
|
48
|
+
};
|
|
49
|
+
readonly ui: {
|
|
50
|
+
/** Contributes a component to a host-defined slot. */
|
|
51
|
+
register<C = unknown>(contribution: SlotContribution<C>): Disposable;
|
|
52
|
+
/**
|
|
53
|
+
* Extends a host extension point. With `component` it behaves like a slot contribution
|
|
54
|
+
* (`ui.extend("user.profile", { mode: "append", component })`); other keys are validated data contributions.
|
|
55
|
+
*/
|
|
56
|
+
extend(point: string, contribution: Record<string, unknown> & {
|
|
57
|
+
mode?: SlotContribution["mode"];
|
|
58
|
+
component?: unknown;
|
|
59
|
+
priority?: number;
|
|
60
|
+
}): Disposable;
|
|
61
|
+
};
|
|
62
|
+
readonly ipc: {
|
|
63
|
+
handle<I, O>(contract: IpcContract<I, O>, handler: IpcHandler<I, O>): Disposable;
|
|
64
|
+
handle<I = unknown, O = unknown>(name: string, handler: IpcHandler<I, O>, opts?: {
|
|
65
|
+
input?: Schema<I>;
|
|
66
|
+
output?: Schema<O>;
|
|
67
|
+
permission?: Permission;
|
|
68
|
+
}): Disposable;
|
|
69
|
+
define(def: IpcDefinition<any, any>): Disposable;
|
|
70
|
+
on(name: string, handler: (payload: unknown, ctx: IpcCallContext) => void): Disposable;
|
|
71
|
+
/** Main → renderer push (main) / renderer → main fire-and-forget (renderer). */
|
|
72
|
+
send(channel: string, payload?: unknown): void;
|
|
73
|
+
invoke<I, O>(contract: IpcContract<I, O>, input: I): Promise<O>;
|
|
74
|
+
invoke<O = unknown>(name: string, input?: unknown): Promise<O>;
|
|
75
|
+
};
|
|
76
|
+
readonly preload: {
|
|
77
|
+
expose(namespace: string, members: Record<string, PreloadMemberInput>): Disposable;
|
|
78
|
+
};
|
|
79
|
+
readonly services: {
|
|
80
|
+
register<T>(name: string, implementation: T): Disposable;
|
|
81
|
+
get<T = unknown>(name: string): T | undefined;
|
|
82
|
+
getAsync<T = unknown>(name: string): Promise<T>;
|
|
83
|
+
};
|
|
84
|
+
readonly hooks: {
|
|
85
|
+
use(name: string, handler: (...args: any[]) => any, opts?: Omit<HookOptions, "owner">): Disposable;
|
|
86
|
+
run<I, R>(operation: string, input: I, exec: (input: I, ctx: HookContext<I, R>) => R | Promise<R>): Promise<R>;
|
|
87
|
+
filter<V, X = unknown>(name: string, value: V, extra?: X): Promise<V>;
|
|
88
|
+
};
|
|
89
|
+
readonly styles: {
|
|
90
|
+
add(style: StyleInput): Disposable;
|
|
91
|
+
};
|
|
92
|
+
/** Controlled APIs exposed by the host (see `host.provide`), permission-checked per plugin. */
|
|
93
|
+
readonly host: {
|
|
94
|
+
api<T = unknown>(name: string): T;
|
|
95
|
+
has(name: string): boolean;
|
|
96
|
+
};
|
|
97
|
+
readonly permissions: {
|
|
98
|
+
has(permission: Permission): boolean;
|
|
99
|
+
assert(permission: Permission): void;
|
|
100
|
+
list(): {
|
|
101
|
+
declared: Permission[];
|
|
102
|
+
granted: Permission[];
|
|
103
|
+
denied: Permission[];
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
readonly lifecycle: {
|
|
107
|
+
readonly state: PluginState;
|
|
108
|
+
/** Registers cleanup executed on deactivate (in reverse order). */
|
|
109
|
+
onDeactivate(fn: () => void | Promise<void>): void;
|
|
110
|
+
/** Anything pushed here is disposed automatically when the plugin deactivates. */
|
|
111
|
+
readonly subscriptions: {
|
|
112
|
+
add<T extends Disposable>(d: T): T;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
type Ctx<Cfg extends Record<string, any>> = PluginContext<Cfg>;
|
|
117
|
+
export interface PluginDefinition<Cfg extends Record<string, any> = Record<string, any>> {
|
|
118
|
+
/** Optional: when present it must match the manifest id. */
|
|
119
|
+
id?: string;
|
|
120
|
+
name?: string;
|
|
121
|
+
version?: string;
|
|
122
|
+
/** Configuration schema; `context.config` and the auto-generated settings form use it. */
|
|
123
|
+
config?: ConfigDefinition<Cfg>;
|
|
124
|
+
routes?: RouteDefinition<any>[];
|
|
125
|
+
layouts?: Record<string, unknown>;
|
|
126
|
+
commands?: CommandDefinition<any[], any>[];
|
|
127
|
+
menus?: MenuInput[];
|
|
128
|
+
hooks?: {
|
|
129
|
+
name: string;
|
|
130
|
+
handler: (...args: any[]) => any;
|
|
131
|
+
priority?: number;
|
|
132
|
+
}[];
|
|
133
|
+
/** Service name → implementation, or a factory receiving the context. */
|
|
134
|
+
services?: Record<string, unknown> | ((ctx: Ctx<Cfg>) => Record<string, unknown>);
|
|
135
|
+
ipc?: IpcDefinition<any, any>[];
|
|
136
|
+
events?: {
|
|
137
|
+
name: string;
|
|
138
|
+
handler: EventListener<any>;
|
|
139
|
+
priority?: number;
|
|
140
|
+
once?: boolean;
|
|
141
|
+
}[];
|
|
142
|
+
/** Slot contributions. */
|
|
143
|
+
components?: SlotContribution<any>[];
|
|
144
|
+
/** Extension point contributions. */
|
|
145
|
+
extensions?: {
|
|
146
|
+
point: string;
|
|
147
|
+
contribution: Record<string, unknown>;
|
|
148
|
+
priority?: number;
|
|
149
|
+
}[];
|
|
150
|
+
styles?: (string | StyleInput)[];
|
|
151
|
+
/** Renderer-visible APIs: `{ analytics: { getStats: "analytics.getStats" } }`. */
|
|
152
|
+
preload?: Record<string, Record<string, PreloadMemberInput>>;
|
|
153
|
+
onLoad?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
154
|
+
onInitialize?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
155
|
+
/** Imperative registration point (alias: `activate`). Runs after declarative contributions are registered. */
|
|
156
|
+
onActivate?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
157
|
+
activate?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
158
|
+
onReady?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
159
|
+
onDeactivate?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
160
|
+
deactivate?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
161
|
+
onUnload?(ctx: Ctx<Cfg>): void | Promise<void>;
|
|
162
|
+
}
|
|
163
|
+
/** Identity helper giving full type inference for plugin definitions (and config types). */
|
|
164
|
+
export declare function definePlugin<Cfg extends Record<string, any> = Record<string, any>>(def: PluginDefinition<Cfg>): PluginDefinition<Cfg>;
|
|
165
|
+
export declare const defineRoute: <C = unknown>(r: RouteDefinition<C>) => RouteDefinition<C>;
|
|
166
|
+
export declare const defineCommand: <A extends unknown[] = any[], R = unknown>(c: CommandDefinition<A, R>) => CommandDefinition<A, R>;
|
|
167
|
+
export declare const defineService: <T extends Record<string, unknown>>(name: string, impl: T) => {
|
|
168
|
+
name: string;
|
|
169
|
+
implementation: T;
|
|
170
|
+
};
|
|
171
|
+
export declare const defineHook: <I = any, R = any>(name: string, handler: (ctx: HookContext<I, R>, next: AroundNext<R>) => any, priority?: number) => {
|
|
172
|
+
name: string;
|
|
173
|
+
handler: typeof handler;
|
|
174
|
+
priority?: number;
|
|
175
|
+
};
|
|
176
|
+
export declare const defineEvent: <T = unknown>(name: string, handler: EventListener<T>, opts?: ListenerOptions) => {
|
|
177
|
+
name: string;
|
|
178
|
+
handler: EventListener<T>;
|
|
179
|
+
priority?: number;
|
|
180
|
+
once?: boolean;
|
|
181
|
+
};
|
|
182
|
+
export declare const defineComponent: <C = unknown>(c: SlotContribution<C>) => SlotContribution<C>;
|
|
183
|
+
export declare const defineMenu: (m: MenuInput) => MenuInput;
|
|
184
|
+
export type { EventContext };
|
|
185
|
+
/** Information about a plugin as exposed to UIs (manager screens, CLI...). Serializable. */
|
|
186
|
+
export interface PluginInfo {
|
|
187
|
+
id: string;
|
|
188
|
+
name: string;
|
|
189
|
+
version: string;
|
|
190
|
+
description?: string;
|
|
191
|
+
author?: string;
|
|
192
|
+
state: PluginState;
|
|
193
|
+
enabled: boolean;
|
|
194
|
+
runtime: Runtime;
|
|
195
|
+
permissions: {
|
|
196
|
+
declared: Permission[];
|
|
197
|
+
granted: Permission[];
|
|
198
|
+
denied: Permission[];
|
|
199
|
+
};
|
|
200
|
+
dependencies: Record<string, string>;
|
|
201
|
+
dependents: string[];
|
|
202
|
+
activationEvents: string[];
|
|
203
|
+
contributes: PluginManifest["contributes"];
|
|
204
|
+
error?: {
|
|
205
|
+
message: string;
|
|
206
|
+
phase: string;
|
|
207
|
+
time: number;
|
|
208
|
+
};
|
|
209
|
+
crashes: number;
|
|
210
|
+
hasConfig: boolean;
|
|
211
|
+
root?: string;
|
|
212
|
+
origin?: string;
|
|
213
|
+
warnings: string[];
|
|
214
|
+
}
|