@abraca/plugin 2.3.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/dist/abracadabra-plugin.cjs +557 -0
- package/dist/abracadabra-plugin.cjs.map +1 -0
- package/dist/abracadabra-plugin.esm.js +549 -0
- package/dist/abracadabra-plugin.esm.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/package.json +32 -0
- package/src/host.ts +221 -0
- package/src/index.ts +67 -0
- package/src/manifest.ts +173 -0
- package/src/registry.ts +287 -0
- package/src/sandbox.ts +381 -0
- package/src/source-spec.ts +81 -0
- package/src/types.ts +353 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PluginHost` — capability-guarded wrapper around the browser globals
|
|
3
|
+
* a plugin is allowed to call. Each guard consults the plugin's
|
|
4
|
+
* manifest before forwarding to the underlying API. A plugin that
|
|
5
|
+
* declared `network:api.example.com` can only `fetch` against
|
|
6
|
+
* `api.example.com`; a plugin without `clipboard:write` cannot write
|
|
7
|
+
* the clipboard; etc.
|
|
8
|
+
*
|
|
9
|
+
* Phase 1 contract: this is opt-in. Plugins that import `createPluginHost`
|
|
10
|
+
* and use its members get the gate for free. Plugins that grab
|
|
11
|
+
* `globalThis.fetch` directly bypass the gate — sandboxing (iframe / Web
|
|
12
|
+
* Worker / Wasm) lands in Phase F.2 and closes that loophole.
|
|
13
|
+
*
|
|
14
|
+
* The `CapabilityDenied` error class lets hosts (and tests) distinguish
|
|
15
|
+
* policy failures from other runtime errors.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PluginCapability, PluginManifest } from "./manifest.ts";
|
|
19
|
+
|
|
20
|
+
// ── Errors ────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when a plugin tries to use an API it didn't declare in its
|
|
24
|
+
* manifest. Distinct from runtime failures (network 500, file not found,
|
|
25
|
+
* etc.) so the host can show the user "the plugin tried to do X without
|
|
26
|
+
* permission" instead of a generic error.
|
|
27
|
+
*/
|
|
28
|
+
export class CapabilityDenied extends Error {
|
|
29
|
+
readonly capability: PluginCapability | string;
|
|
30
|
+
readonly pluginId: string;
|
|
31
|
+
|
|
32
|
+
constructor(pluginId: string, capability: PluginCapability | string, detail?: string) {
|
|
33
|
+
super(
|
|
34
|
+
`plugin '${pluginId}' is not authorised to use '${capability}'${detail ? ` (${detail})` : ""}`,
|
|
35
|
+
);
|
|
36
|
+
this.name = "CapabilityDenied";
|
|
37
|
+
this.capability = capability;
|
|
38
|
+
this.pluginId = pluginId;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Host shape ────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/** Subset of the global `fetch` signature the host exposes. */
|
|
45
|
+
export type GuardedFetch = (
|
|
46
|
+
input: RequestInfo | URL,
|
|
47
|
+
init?: RequestInit,
|
|
48
|
+
) => Promise<Response>;
|
|
49
|
+
|
|
50
|
+
/** Subset of `navigator.clipboard` the host exposes. */
|
|
51
|
+
export interface GuardedClipboard {
|
|
52
|
+
readText(): Promise<string>;
|
|
53
|
+
writeText(text: string): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Permission-aware desktop notification. */
|
|
57
|
+
export interface GuardedNotifications {
|
|
58
|
+
show(title: string, options?: NotificationOptions): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Everything a plugin is allowed to touch through the host contract.
|
|
63
|
+
* Each plugin gets its own instance via `createPluginHost(manifest)`.
|
|
64
|
+
*/
|
|
65
|
+
export interface PluginHost {
|
|
66
|
+
readonly pluginId: string;
|
|
67
|
+
readonly capabilities: ReadonlySet<PluginCapability>;
|
|
68
|
+
fetch: GuardedFetch;
|
|
69
|
+
clipboard: GuardedClipboard;
|
|
70
|
+
notifications: GuardedNotifications;
|
|
71
|
+
/** Test whether a capability is granted without invoking the wrapped API. */
|
|
72
|
+
can(capability: PluginCapability | string): boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Constructor ───────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export interface PluginHostOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Override the underlying `fetch`. Tests inject a stub here; production
|
|
80
|
+
* hosts leave this unset and use the global.
|
|
81
|
+
*/
|
|
82
|
+
fetch?: typeof fetch;
|
|
83
|
+
/** Override `navigator.clipboard`. */
|
|
84
|
+
clipboard?: {
|
|
85
|
+
readText?(): Promise<string>;
|
|
86
|
+
writeText?(text: string): Promise<void>;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Override the host's notification surface. The default uses the
|
|
90
|
+
* browser's `Notification` API — fine for web hosts; native hosts
|
|
91
|
+
* (Tauri, Electron) inject their own.
|
|
92
|
+
*/
|
|
93
|
+
showNotification?(title: string, options?: NotificationOptions): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build a `PluginHost` from a manifest. The set of granted capabilities
|
|
98
|
+
* is `manifest.capabilities.required ∪ optional`; runtime gates check
|
|
99
|
+
* membership before forwarding to the underlying API.
|
|
100
|
+
*
|
|
101
|
+
* Network capabilities support host patterns:
|
|
102
|
+
* - `network` — any host
|
|
103
|
+
* - `network:api.foo.io` — exact match
|
|
104
|
+
* - `network:*.foo.io` — wildcard subdomain match
|
|
105
|
+
* - `network:foo.io,bar.io` — comma-separated list
|
|
106
|
+
*/
|
|
107
|
+
export function createPluginHost(
|
|
108
|
+
manifest: Pick<PluginManifest, "id" | "capabilities">,
|
|
109
|
+
options: PluginHostOptions = {},
|
|
110
|
+
): PluginHost {
|
|
111
|
+
const granted = new Set<PluginCapability>([
|
|
112
|
+
...(manifest.capabilities.required ?? []),
|
|
113
|
+
...(manifest.capabilities.optional ?? []),
|
|
114
|
+
]);
|
|
115
|
+
const pluginId = manifest.id;
|
|
116
|
+
|
|
117
|
+
const can = (cap: PluginCapability | string): boolean => granted.has(cap as PluginCapability);
|
|
118
|
+
|
|
119
|
+
const guardedFetch: GuardedFetch = async (input, init) => {
|
|
120
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
121
|
+
const host = extractHost(url);
|
|
122
|
+
if (!host) {
|
|
123
|
+
throw new CapabilityDenied(pluginId, "network", "could not parse URL host");
|
|
124
|
+
}
|
|
125
|
+
if (!matchesNetworkCapability(granted, host)) {
|
|
126
|
+
throw new CapabilityDenied(
|
|
127
|
+
pluginId,
|
|
128
|
+
`network:${host}`,
|
|
129
|
+
"declare it in capabilities.required or capabilities.optional",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const real = options.fetch ?? globalThis.fetch;
|
|
133
|
+
if (!real) throw new Error("fetch is not available in this runtime");
|
|
134
|
+
return real(input, init);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const guardedClipboard: GuardedClipboard = {
|
|
138
|
+
async readText() {
|
|
139
|
+
if (!granted.has("clipboard:read")) {
|
|
140
|
+
throw new CapabilityDenied(pluginId, "clipboard:read");
|
|
141
|
+
}
|
|
142
|
+
if (options.clipboard?.readText) return options.clipboard.readText();
|
|
143
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.readText) {
|
|
144
|
+
return navigator.clipboard.readText();
|
|
145
|
+
}
|
|
146
|
+
throw new Error("clipboard.readText is not available in this runtime");
|
|
147
|
+
},
|
|
148
|
+
async writeText(text: string) {
|
|
149
|
+
if (!granted.has("clipboard:write")) {
|
|
150
|
+
throw new CapabilityDenied(pluginId, "clipboard:write");
|
|
151
|
+
}
|
|
152
|
+
if (options.clipboard?.writeText) return options.clipboard.writeText(text);
|
|
153
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
154
|
+
return navigator.clipboard.writeText(text);
|
|
155
|
+
}
|
|
156
|
+
throw new Error("clipboard.writeText is not available in this runtime");
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const guardedNotifications: GuardedNotifications = {
|
|
161
|
+
async show(title: string, opts?: NotificationOptions) {
|
|
162
|
+
if (!granted.has("notifications:show")) {
|
|
163
|
+
throw new CapabilityDenied(pluginId, "notifications:show");
|
|
164
|
+
}
|
|
165
|
+
if (options.showNotification) return options.showNotification(title, opts);
|
|
166
|
+
if (typeof Notification !== "undefined") {
|
|
167
|
+
new Notification(title, opts);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
throw new Error("notifications are not available in this runtime");
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
pluginId,
|
|
176
|
+
capabilities: granted,
|
|
177
|
+
fetch: guardedFetch,
|
|
178
|
+
clipboard: guardedClipboard,
|
|
179
|
+
notifications: guardedNotifications,
|
|
180
|
+
can,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
function extractHost(rawUrl: string): string | null {
|
|
187
|
+
try {
|
|
188
|
+
return new URL(rawUrl, "http://placeholder").host;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Network capability matcher — `network`, `network:exact.host`,
|
|
197
|
+
* `network:*.wildcard.host`, `network:a,b,c` comma-list. Exported so
|
|
198
|
+
* the iframe sandbox can reuse the same rules without forking the
|
|
199
|
+
* vocabulary.
|
|
200
|
+
*/
|
|
201
|
+
export function matchesNetworkCapability(granted: ReadonlySet<PluginCapability>, host: string): boolean {
|
|
202
|
+
if (granted.has("network")) return true;
|
|
203
|
+
for (const cap of granted) {
|
|
204
|
+
if (!cap.startsWith("network:")) continue;
|
|
205
|
+
const patterns = cap.slice("network:".length).split(",");
|
|
206
|
+
for (const pattern of patterns) {
|
|
207
|
+
if (matchesHostPattern(pattern.trim(), host)) return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function matchesHostPattern(pattern: string, host: string): boolean {
|
|
214
|
+
if (!pattern) return false;
|
|
215
|
+
if (pattern === host) return true;
|
|
216
|
+
if (pattern.startsWith("*.")) {
|
|
217
|
+
const suffix = pattern.slice(2);
|
|
218
|
+
return host === suffix || host.endsWith(`.${suffix}`);
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abraca/plugin` — public surface.
|
|
3
|
+
*
|
|
4
|
+
* Shared types + registry runtime consumed by cou-shell and `@abraca/nuxt`.
|
|
5
|
+
* The package is types-and-class-only: no host imports, no React/Vue/Nuxt
|
|
6
|
+
* dependencies at runtime.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
type AbraPlugin,
|
|
11
|
+
type AbraYDoc,
|
|
12
|
+
type AbraTiptapExtension,
|
|
13
|
+
type EditorPluginCtx,
|
|
14
|
+
type DragHandlePluginCtx,
|
|
15
|
+
type ClientPluginCtx,
|
|
16
|
+
type CommandPaletteCtx,
|
|
17
|
+
type MentionPluginCtx,
|
|
18
|
+
type RefLike,
|
|
19
|
+
type EditorRefLike,
|
|
20
|
+
type AbraMentionProvider,
|
|
21
|
+
type AbraMentionItem,
|
|
22
|
+
type AbraAwarenessContribution,
|
|
23
|
+
type AbraCommandItem,
|
|
24
|
+
type AbraNodePanelSlot,
|
|
25
|
+
type NodePanelCtx,
|
|
26
|
+
type AbraSettingsPanel,
|
|
27
|
+
type AbraKeyboardShortcut,
|
|
28
|
+
type CollaborationUser,
|
|
29
|
+
type RunnerCleanup,
|
|
30
|
+
type ServerRunnerDefinition,
|
|
31
|
+
type ServerRunnerContextBase,
|
|
32
|
+
} from "./types.ts";
|
|
33
|
+
|
|
34
|
+
export { PluginRegistry } from "./registry.ts";
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
type ExternalPluginEntry,
|
|
38
|
+
normalizePluginUrl,
|
|
39
|
+
isSafePluginUrl,
|
|
40
|
+
} from "./source-spec.ts";
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
type PluginCapability,
|
|
44
|
+
type PluginManifestAuthor,
|
|
45
|
+
type PluginPricing,
|
|
46
|
+
type PluginVersionStatus,
|
|
47
|
+
type PluginManifestContributes,
|
|
48
|
+
type PluginManifest,
|
|
49
|
+
type PluginRegistryEntry,
|
|
50
|
+
} from "./manifest.ts";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
CapabilityDenied,
|
|
54
|
+
createPluginHost,
|
|
55
|
+
matchesNetworkCapability,
|
|
56
|
+
type PluginHost,
|
|
57
|
+
type PluginHostOptions,
|
|
58
|
+
type GuardedFetch,
|
|
59
|
+
type GuardedClipboard,
|
|
60
|
+
type GuardedNotifications,
|
|
61
|
+
} from "./host.ts";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
loadSandboxedPlugin,
|
|
65
|
+
type SandboxedPlugin,
|
|
66
|
+
type SandboxOptions,
|
|
67
|
+
} from "./sandbox.ts";
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin manifest v1 — the declarative `manifest.json` colocated with a
|
|
3
|
+
* plugin's bundle. Hosts parse this before executing the bundle, and the
|
|
4
|
+
* registry server (Phase C) validates it server-side too.
|
|
5
|
+
*
|
|
6
|
+
* Phase A scope: types only. Zod validators + JSON Schema generation live
|
|
7
|
+
* in `@abraca/schema` (Phase B).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Capability tokens declared by a plugin's manifest. The host enforces these
|
|
12
|
+
* at the plugin-host boundary — a plugin without `network:*` cannot `fetch()`,
|
|
13
|
+
* a plugin without `clipboard:write` cannot write the clipboard, etc.
|
|
14
|
+
*
|
|
15
|
+
* Phase 1 enforcement is contract-based (we wrap host APIs). Phase 4 adds
|
|
16
|
+
* Wasm / Web Worker / iframe sandboxing so the contract is enforced at the
|
|
17
|
+
* runtime boundary, not just the host-API boundary.
|
|
18
|
+
*/
|
|
19
|
+
export type PluginCapability =
|
|
20
|
+
/** Outbound HTTP — `network` (any host) or `network:<host>` (exact match, wildcards allowed). */
|
|
21
|
+
| `network${"" | `:${string}`}`
|
|
22
|
+
/** Filesystem reads — only meaningful in Tauri / native hosts. */
|
|
23
|
+
| "fs:read"
|
|
24
|
+
/** Filesystem writes — Tauri / native only. */
|
|
25
|
+
| "fs:write"
|
|
26
|
+
/** Read the clipboard. */
|
|
27
|
+
| "clipboard:read"
|
|
28
|
+
/** Write the clipboard. */
|
|
29
|
+
| "clipboard:write"
|
|
30
|
+
/** Read any Y.Doc the host has loaded. */
|
|
31
|
+
| "doc-read"
|
|
32
|
+
/** Mutate Y.Docs (transact_mut against the host's provider). */
|
|
33
|
+
| "doc-write"
|
|
34
|
+
/** Broadcast presence / awareness state. */
|
|
35
|
+
| "awareness"
|
|
36
|
+
/** Register an RPC v1 handler on the service runner. */
|
|
37
|
+
| "server-runner"
|
|
38
|
+
/** Contribute items to the global command palette. */
|
|
39
|
+
| "command-palette"
|
|
40
|
+
/** Contribute editor toolbar items. */
|
|
41
|
+
| "toolbar"
|
|
42
|
+
/** Contribute bubble-menu items (text selection). */
|
|
43
|
+
| "bubble-menu"
|
|
44
|
+
/** Contribute slash-command suggestion items. */
|
|
45
|
+
| "slash-command"
|
|
46
|
+
/** Contribute drag-handle items. */
|
|
47
|
+
| "drag-handle"
|
|
48
|
+
/** Register a page-type renderer with the given slug. */
|
|
49
|
+
| `page-type:${string}`
|
|
50
|
+
/** Send messages on the in-doc chat channel. */
|
|
51
|
+
| "chat:send"
|
|
52
|
+
/** Read in-doc chat history. */
|
|
53
|
+
| "chat:read"
|
|
54
|
+
/** Show user-visible notifications. */
|
|
55
|
+
| "notifications:show";
|
|
56
|
+
|
|
57
|
+
export interface PluginManifestAuthor {
|
|
58
|
+
/** GitHub login. Identifies the author in the registry. */
|
|
59
|
+
github?: string;
|
|
60
|
+
/** Human-readable display name. */
|
|
61
|
+
name?: string;
|
|
62
|
+
url?: string;
|
|
63
|
+
email?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Pricing label as defined in the registry — see registry docs for semantics. */
|
|
67
|
+
export type PluginPricing = "free" | "optional" | "paid";
|
|
68
|
+
|
|
69
|
+
/** Submission / publication status as exposed by the registry's catalog API. */
|
|
70
|
+
export type PluginVersionStatus =
|
|
71
|
+
| "live"
|
|
72
|
+
| "pending"
|
|
73
|
+
| "rejected"
|
|
74
|
+
| "superseded";
|
|
75
|
+
|
|
76
|
+
/** Declared contributions — the plugin's static claim about what it ships. */
|
|
77
|
+
export interface PluginManifestContributes {
|
|
78
|
+
pageTypes?: readonly string[];
|
|
79
|
+
extensions?: readonly string[];
|
|
80
|
+
awarenessFields?: readonly string[];
|
|
81
|
+
serverRunners?: readonly string[];
|
|
82
|
+
commands?: readonly string[];
|
|
83
|
+
settingsPanels?: readonly string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The static manifest colocated with the plugin entry. Versioned via the
|
|
88
|
+
* top-level `manifestVersion` field — the only field guaranteed to exist
|
|
89
|
+
* verbatim across all manifest versions.
|
|
90
|
+
*/
|
|
91
|
+
export interface PluginManifest {
|
|
92
|
+
/** Schema version of this manifest format. Currently always `1`. */
|
|
93
|
+
manifestVersion: 1;
|
|
94
|
+
|
|
95
|
+
/** Unique lowercase-kebab slug. Stable across versions. */
|
|
96
|
+
id: string;
|
|
97
|
+
|
|
98
|
+
/** Display name. Falls back to `id`. */
|
|
99
|
+
name?: string;
|
|
100
|
+
|
|
101
|
+
/** Plugin version (semver). */
|
|
102
|
+
version: string;
|
|
103
|
+
|
|
104
|
+
/** Author / maintainer info. */
|
|
105
|
+
author: PluginManifestAuthor;
|
|
106
|
+
|
|
107
|
+
/** SPDX license identifier (e.g. `MIT`, `Apache-2.0`). */
|
|
108
|
+
license: string;
|
|
109
|
+
|
|
110
|
+
/** One- to two-sentence summary shown in browsers and scorecards. */
|
|
111
|
+
description: string;
|
|
112
|
+
|
|
113
|
+
/** Long-form description / changelog — markdown allowed. */
|
|
114
|
+
readme?: string;
|
|
115
|
+
|
|
116
|
+
homepage?: string;
|
|
117
|
+
|
|
118
|
+
/** `github:user/repo` shorthand or any URL. */
|
|
119
|
+
repository?: string;
|
|
120
|
+
|
|
121
|
+
/** Minimum Abracadabra SDK version required (semver range). */
|
|
122
|
+
minAbracadabraVersion?: string;
|
|
123
|
+
|
|
124
|
+
/** Path to the bundle entry, relative to the manifest. */
|
|
125
|
+
entry: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* SHA-256 of the bundle entry, hex-encoded. Verified by hosts before
|
|
129
|
+
* `import()`. Mismatch → refuse to load.
|
|
130
|
+
*/
|
|
131
|
+
integrity: string;
|
|
132
|
+
|
|
133
|
+
/** Pricing tier as defined in the registry. */
|
|
134
|
+
pricing?: PluginPricing;
|
|
135
|
+
|
|
136
|
+
/** Screenshot paths (relative to manifest), shown on the detail page. */
|
|
137
|
+
screenshots?: readonly string[];
|
|
138
|
+
|
|
139
|
+
/** Free-form category tags for catalog filtering. */
|
|
140
|
+
categories?: readonly string[];
|
|
141
|
+
|
|
142
|
+
/** Required capabilities — host MUST grant these or refuse the install. */
|
|
143
|
+
capabilities: {
|
|
144
|
+
required: readonly PluginCapability[];
|
|
145
|
+
optional?: readonly PluginCapability[];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Declared contributions. Static check; verified against runtime registrations. */
|
|
149
|
+
contributes?: PluginManifestContributes;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Peer dependencies the plugin expects the host to provide. Hosts publish
|
|
153
|
+
* the versions they ship via `globalThis.__ABRACA_SHARED__`.
|
|
154
|
+
*/
|
|
155
|
+
peerDeps?: Record<string, string>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Registry-decorated manifest — what the catalog API returns. */
|
|
159
|
+
export interface PluginRegistryEntry {
|
|
160
|
+
manifest: PluginManifest;
|
|
161
|
+
/** Resolved bundle URL (R2 / jsDelivr / etc). */
|
|
162
|
+
artifactUrl: string;
|
|
163
|
+
/** Registry-side artifact hash, mirrors `manifest.integrity`. */
|
|
164
|
+
artifactSha256: string;
|
|
165
|
+
/** Status as seen by the registry. */
|
|
166
|
+
status: PluginVersionStatus;
|
|
167
|
+
publishedAt: string;
|
|
168
|
+
/**
|
|
169
|
+
* Scorecard summary — `null` until the registry has scanned this version.
|
|
170
|
+
* Concrete shape lives in `@abraca/schema` (Phase H).
|
|
171
|
+
*/
|
|
172
|
+
scorecard: unknown | null;
|
|
173
|
+
}
|