@mdwrk/extension-runtime 1.0.1
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/README.md +34 -0
- package/dist/catalog.d.ts +29 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +244 -0
- package/dist/catalog.js.map +1 -0
- package/dist/compatibility.d.ts +12 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/compatibility.js +108 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +7 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +34 -0
- package/dist/loader.js.map +1 -0
- package/dist/registry.d.ts +12 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +52 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +765 -0
- package/dist/runtime.js.map +1 -0
- package/dist/storage.d.ts +13 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +63 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +54 -0
- package/dist/validation.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +85 -0
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { EXTENSION_HOST_API_VERSION } from "@mdwrk/extension-host";
|
|
2
|
+
import { THEME_CONTRACT_VERSION } from "@mdwrk/theme-contract";
|
|
3
|
+
import { createFetchExtensionArtifactTransport, evaluateCatalogEntryPolicy, fetchInstallableCatalogPayload, validateCatalogDocument, } from "./catalog.js";
|
|
4
|
+
import { evaluateExtensionCompatibility } from "./compatibility.js";
|
|
5
|
+
import { createExtensionLoader } from "./loader.js";
|
|
6
|
+
import { createExtensionRegistry } from "./registry.js";
|
|
7
|
+
import { createExtensionConfigurationStore, EXTENSION_INSTALL_INDEX_KEY, getExtensionEnabledStateKey, getInstalledExtensionModuleKey, getInstalledExtensionRecordKey, } from "./storage.js";
|
|
8
|
+
import { EXTENSION_RUNTIME_VERSION } from "./version.js";
|
|
9
|
+
const createEmitter = () => {
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
return {
|
|
12
|
+
emit() {
|
|
13
|
+
for (const listener of Array.from(listeners)) {
|
|
14
|
+
listener();
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
subscribe(listener) {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
return () => listeners.delete(listener);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const toErrorRecord = (phase, error) => {
|
|
24
|
+
if (error instanceof Error) {
|
|
25
|
+
return {
|
|
26
|
+
phase,
|
|
27
|
+
message: error.message,
|
|
28
|
+
detail: error.stack,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
phase,
|
|
33
|
+
message: String(error),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const asDiagnostic = (code, severity, record) => {
|
|
37
|
+
if (typeof record === "string") {
|
|
38
|
+
return { code, severity, message: record };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
code,
|
|
42
|
+
severity,
|
|
43
|
+
message: record.message,
|
|
44
|
+
detail: record.detail,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
function createDeniedProxy(extensionId, capability, apiName, api, allow) {
|
|
48
|
+
const allowed = new Set(allow.map((key) => String(key)));
|
|
49
|
+
return new Proxy(api, {
|
|
50
|
+
get(target, property, receiver) {
|
|
51
|
+
if (typeof property === "string" && !allowed.has(property)) {
|
|
52
|
+
return async () => {
|
|
53
|
+
throw new Error(`Extension '${extensionId}' lacks capability '${capability}' required for ${apiName}.${property}().`);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return Reflect.get(target, property, receiver);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function createScopedHost(host, extensionId, capabilities) {
|
|
61
|
+
const granted = new Set(capabilities);
|
|
62
|
+
const commands = granted.has("command.invoke")
|
|
63
|
+
? host.commands
|
|
64
|
+
: createDeniedProxy(extensionId, "command.invoke", "commands", host.commands, []);
|
|
65
|
+
const views = granted.has("view.register")
|
|
66
|
+
? host.views
|
|
67
|
+
: createDeniedProxy(extensionId, "view.register", "views", host.views, []);
|
|
68
|
+
const actionRail = granted.has("actionRail.register")
|
|
69
|
+
? host.actionRail
|
|
70
|
+
: createDeniedProxy(extensionId, "actionRail.register", "actionRail", host.actionRail, []);
|
|
71
|
+
const settings = {
|
|
72
|
+
get: granted.has("settings.read") ? host.settings.get : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'settings.read'.`); },
|
|
73
|
+
watch: granted.has("settings.read") ? host.settings.watch : () => { throw new Error(`Extension '${extensionId}' lacks capability 'settings.read'.`); },
|
|
74
|
+
set: granted.has("settings.write") ? host.settings.set : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'settings.write'.`); },
|
|
75
|
+
remove: granted.has("settings.write") ? host.settings.remove : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'settings.write'.`); },
|
|
76
|
+
};
|
|
77
|
+
const notifications = {
|
|
78
|
+
info: granted.has("notification.publish") ? host.notifications.info : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'notification.publish'.`); },
|
|
79
|
+
warn: granted.has("notification.publish") ? host.notifications.warn : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'notification.publish'.`); },
|
|
80
|
+
error: granted.has("notification.publish") ? host.notifications.error : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'notification.publish'.`); },
|
|
81
|
+
};
|
|
82
|
+
const theme = {
|
|
83
|
+
getToken: granted.has("theme.read") ? host.theme.getToken : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
84
|
+
getTokenMap: granted.has("theme.read") ? host.theme.getTokenMap : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
85
|
+
getTokens: granted.has("theme.read") ? host.theme.getTokens : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
86
|
+
getClassNames: granted.has("theme.read") ? host.theme.getClassNames : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
87
|
+
getThemeBridge: granted.has("theme.read") ? host.theme.getThemeBridge : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
88
|
+
getThemeBridgeVariables: granted.has("theme.read") ? host.theme.getThemeBridgeVariables : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
89
|
+
exportTheme: granted.has("theme.read") ? host.theme.exportTheme : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
90
|
+
exportThemeCss: granted.has("theme.read") ? host.theme.exportThemeCss : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
91
|
+
supportsClass: granted.has("theme.read") ? host.theme.supportsClass : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.read'.`); },
|
|
92
|
+
setDraftToken: granted.has("theme.write") ? host.theme.setDraftToken : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.write'.`); },
|
|
93
|
+
setDraftTokens: granted.has("theme.write") ? host.theme.setDraftTokens : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.write'.`); },
|
|
94
|
+
previewTheme: granted.has("theme.write") ? host.theme.previewTheme : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.write'.`); },
|
|
95
|
+
applyDraft: granted.has("theme.write") ? host.theme.applyDraft : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.write'.`); },
|
|
96
|
+
discardDraft: granted.has("theme.write") ? host.theme.discardDraft : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'theme.write'.`); },
|
|
97
|
+
};
|
|
98
|
+
const editor = {
|
|
99
|
+
getActiveDocument: granted.has("editor.read") ? host.editor.getActiveDocument : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'editor.read'.`); },
|
|
100
|
+
getSelections: granted.has("selection.read") ? host.editor.getSelections : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'selection.read'.`); },
|
|
101
|
+
insertText: granted.has("editor.write") ? host.editor.insertText : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'editor.write'.`); },
|
|
102
|
+
replaceSelections: granted.has("editor.write") ? host.editor.replaceSelections : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'editor.write'.`); },
|
|
103
|
+
setDocumentContent: granted.has("editor.write") ? host.editor.setDocumentContent : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'editor.write'.`); },
|
|
104
|
+
};
|
|
105
|
+
const workspace = {
|
|
106
|
+
listProjects: granted.has("workspace.read") ? host.workspace.listProjects : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'workspace.read'.`); },
|
|
107
|
+
getActiveProject: granted.has("workspace.read") ? host.workspace.getActiveProject : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'workspace.read'.`); },
|
|
108
|
+
getActiveFile: granted.has("workspace.read") ? host.workspace.getActiveFile : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'workspace.read'.`); },
|
|
109
|
+
readFile: granted.has("workspace.read") ? host.workspace.readFile : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'workspace.read'.`); },
|
|
110
|
+
writeFile: granted.has("workspace.write") ? host.workspace.writeFile : async () => { throw new Error(`Extension '${extensionId}' lacks capability 'workspace.write'.`); },
|
|
111
|
+
};
|
|
112
|
+
const diagnostics = host.diagnostics;
|
|
113
|
+
const i18n = host.i18n;
|
|
114
|
+
const logger = host.logger;
|
|
115
|
+
return {
|
|
116
|
+
apiVersion: host.apiVersion,
|
|
117
|
+
commands,
|
|
118
|
+
views,
|
|
119
|
+
actionRail,
|
|
120
|
+
settings,
|
|
121
|
+
notifications,
|
|
122
|
+
theme,
|
|
123
|
+
editor,
|
|
124
|
+
workspace,
|
|
125
|
+
i18n,
|
|
126
|
+
diagnostics,
|
|
127
|
+
logger,
|
|
128
|
+
environment: {
|
|
129
|
+
...host.environment,
|
|
130
|
+
runtimeVersion: EXTENSION_RUNTIME_VERSION,
|
|
131
|
+
grantedCapabilities: capabilities,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function toBase64(value) {
|
|
136
|
+
const nodeBuffer = globalThis.Buffer;
|
|
137
|
+
if (nodeBuffer) {
|
|
138
|
+
return nodeBuffer.from(value, "utf8").toString("base64");
|
|
139
|
+
}
|
|
140
|
+
if (typeof btoa === "function") {
|
|
141
|
+
return btoa(unescape(encodeURIComponent(value)));
|
|
142
|
+
}
|
|
143
|
+
throw new Error("Base64 encoding is unavailable in this runtime.");
|
|
144
|
+
}
|
|
145
|
+
function createExternalModuleFactory(record, moduleCode) {
|
|
146
|
+
const source = `${moduleCode}\n//# sourceURL=${record.moduleUrl}`;
|
|
147
|
+
return async () => {
|
|
148
|
+
const url = `data:text/javascript;base64,${toBase64(source)}`;
|
|
149
|
+
return await import(url);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export function createExtensionRuntime(options) {
|
|
153
|
+
const registry = createExtensionRegistry();
|
|
154
|
+
const loader = createExtensionLoader();
|
|
155
|
+
const emitter = createEmitter();
|
|
156
|
+
const states = new Map();
|
|
157
|
+
const externalCatalogEntries = new Map();
|
|
158
|
+
const installedRegistrationDisposables = new Map();
|
|
159
|
+
const now = options.now ?? (() => Date.now());
|
|
160
|
+
const transport = options.transport ?? (typeof fetch === "function" ? createFetchExtensionArtifactTransport() : undefined);
|
|
161
|
+
let started = false;
|
|
162
|
+
let installedRehydrated = false;
|
|
163
|
+
let snapshotCache = null;
|
|
164
|
+
const emitSnapshotChange = () => {
|
|
165
|
+
snapshotCache = null;
|
|
166
|
+
emitter.emit();
|
|
167
|
+
};
|
|
168
|
+
const listCatalogEntries = () => Array.from(externalCatalogEntries.values())
|
|
169
|
+
.map((registered) => {
|
|
170
|
+
const policyIssues = evaluateCatalogEntryPolicy(registered.entry, options.trustPolicy);
|
|
171
|
+
return {
|
|
172
|
+
entryId: registered.entryId,
|
|
173
|
+
catalogId: registered.catalogId,
|
|
174
|
+
extensionId: registered.entry.extensionId,
|
|
175
|
+
packageName: registered.entry.packageName,
|
|
176
|
+
version: registered.entry.version,
|
|
177
|
+
displayName: registered.entry.displayName,
|
|
178
|
+
description: registered.entry.description,
|
|
179
|
+
publisher: registered.entry.publisher,
|
|
180
|
+
installed: registry.get(registered.entry.extensionId)?.source === "installed",
|
|
181
|
+
policyTrusted: policyIssues.length === 0,
|
|
182
|
+
policyIssues,
|
|
183
|
+
};
|
|
184
|
+
})
|
|
185
|
+
.sort((left, right) => left.displayName.defaultMessage.localeCompare(right.displayName.defaultMessage));
|
|
186
|
+
const listStates = () => registry.list().map((entry) => {
|
|
187
|
+
const state = states.get(entry.id);
|
|
188
|
+
const hostGranted = new Set(options.host.environment.grantedCapabilities ?? entry.manifest.capabilities);
|
|
189
|
+
const grantedCapabilities = entry.manifest.capabilities.filter((capability) => hostGranted.has(capability));
|
|
190
|
+
const missingCapabilities = entry.manifest.capabilities.filter((capability) => !hostGranted.has(capability));
|
|
191
|
+
const compatibility = evaluateExtensionCompatibility(entry.manifest, {
|
|
192
|
+
hostApiVersion: options.host.apiVersion || EXTENSION_HOST_API_VERSION,
|
|
193
|
+
hostVersion: options.host.environment.hostVersion,
|
|
194
|
+
runtimeVersion: EXTENSION_RUNTIME_VERSION,
|
|
195
|
+
themeContractVersion: THEME_CONTRACT_VERSION,
|
|
196
|
+
});
|
|
197
|
+
return {
|
|
198
|
+
id: entry.id,
|
|
199
|
+
manifest: entry.manifest,
|
|
200
|
+
source: entry.source,
|
|
201
|
+
activation: entry.activation,
|
|
202
|
+
enabled: state?.enabled ?? entry.manifest.enabledByDefault,
|
|
203
|
+
status: state?.status ?? (compatibility.compatible ? (entry.manifest.enabledByDefault ? "registered" : "disabled") : "incompatible"),
|
|
204
|
+
compatibility,
|
|
205
|
+
grantedCapabilities,
|
|
206
|
+
missingCapabilities,
|
|
207
|
+
diagnostics: Object.freeze([...(state?.diagnostics ?? [])]),
|
|
208
|
+
componentIds: Object.freeze(Array.from(state?.components.keys() ?? [])),
|
|
209
|
+
serviceTokens: Object.freeze(Array.from(state?.services.keys() ?? [])),
|
|
210
|
+
lastActivatedAt: state?.lastActivatedAt ?? null,
|
|
211
|
+
lastError: state?.lastError ?? null,
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
const getSnapshot = () => {
|
|
215
|
+
if (snapshotCache)
|
|
216
|
+
return snapshotCache;
|
|
217
|
+
snapshotCache = {
|
|
218
|
+
started,
|
|
219
|
+
extensions: listStates(),
|
|
220
|
+
catalogEntries: listCatalogEntries(),
|
|
221
|
+
};
|
|
222
|
+
return snapshotCache;
|
|
223
|
+
};
|
|
224
|
+
const ensureState = (entry) => {
|
|
225
|
+
const existing = states.get(entry.id);
|
|
226
|
+
if (existing) {
|
|
227
|
+
existing.entry = entry;
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
230
|
+
const initial = {
|
|
231
|
+
entry,
|
|
232
|
+
enabled: entry.manifest.enabledByDefault,
|
|
233
|
+
status: "registered",
|
|
234
|
+
diagnostics: [],
|
|
235
|
+
lastError: null,
|
|
236
|
+
lastActivatedAt: null,
|
|
237
|
+
loadedModule: null,
|
|
238
|
+
activeContext: null,
|
|
239
|
+
activationTask: null,
|
|
240
|
+
deactivationTask: null,
|
|
241
|
+
runtimeDisposables: [],
|
|
242
|
+
components: new Map(),
|
|
243
|
+
services: new Map(),
|
|
244
|
+
};
|
|
245
|
+
states.set(entry.id, initial);
|
|
246
|
+
return initial;
|
|
247
|
+
};
|
|
248
|
+
const publishDiagnostic = async (extensionId, record) => {
|
|
249
|
+
const state = states.get(extensionId);
|
|
250
|
+
if (state) {
|
|
251
|
+
state.diagnostics.push(record);
|
|
252
|
+
}
|
|
253
|
+
await options.host.diagnostics.publish(extensionId, record);
|
|
254
|
+
emitSnapshotChange();
|
|
255
|
+
};
|
|
256
|
+
const clearDiagnostics = async (extensionId) => {
|
|
257
|
+
const state = states.get(extensionId);
|
|
258
|
+
if (state) {
|
|
259
|
+
state.diagnostics = [];
|
|
260
|
+
}
|
|
261
|
+
await options.host.diagnostics.clear(extensionId);
|
|
262
|
+
emitSnapshotChange();
|
|
263
|
+
};
|
|
264
|
+
const assertRegistrationCapability = (state, capability) => {
|
|
265
|
+
const hostGranted = new Set(options.host.environment.grantedCapabilities ?? state.entry.manifest.capabilities);
|
|
266
|
+
if (!hostGranted.has(capability) || !state.entry.manifest.capabilities.includes(capability)) {
|
|
267
|
+
const error = toErrorRecord("activate", new Error(`Extension '${state.entry.id}' lacks required capability '${capability}' for this registration.`));
|
|
268
|
+
state.lastError = error;
|
|
269
|
+
state.diagnostics.push(asDiagnostic("EXT_RUNTIME_CAPABILITY_DENIED", "error", error));
|
|
270
|
+
void options.host.diagnostics.publish(state.entry.id, asDiagnostic("EXT_RUNTIME_CAPABILITY_DENIED", "error", error));
|
|
271
|
+
throw new Error(error.message);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
const buildContext = (state, scopedHost) => {
|
|
275
|
+
const config = createExtensionConfigurationStore(state.entry.id, options.storage);
|
|
276
|
+
const grantedSet = new Set(options.host.environment.grantedCapabilities ?? state.entry.manifest.capabilities);
|
|
277
|
+
const grantedCapabilities = state.entry.manifest.capabilities.filter((capability) => grantedSet.has(capability));
|
|
278
|
+
const registerDisposable = (disposable) => {
|
|
279
|
+
state.runtimeDisposables.push(disposable);
|
|
280
|
+
return disposable;
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
extensionId: state.entry.id,
|
|
284
|
+
manifest: state.entry.manifest,
|
|
285
|
+
capabilities: grantedCapabilities,
|
|
286
|
+
host: scopedHost,
|
|
287
|
+
environment: scopedHost.environment,
|
|
288
|
+
config,
|
|
289
|
+
registerCommand(command) {
|
|
290
|
+
return registerDisposable(options.registrationSink.registerCommand(state.entry.id, command));
|
|
291
|
+
},
|
|
292
|
+
registerView(view) {
|
|
293
|
+
assertRegistrationCapability(state, "view.register");
|
|
294
|
+
return registerDisposable(options.registrationSink.registerView(state.entry.id, view));
|
|
295
|
+
},
|
|
296
|
+
registerComponent(component) {
|
|
297
|
+
assertRegistrationCapability(state, "component.register");
|
|
298
|
+
state.components.set(component.id, component);
|
|
299
|
+
const disposable = options.registrationSink.registerComponent?.(state.entry.id, component) ?? {
|
|
300
|
+
dispose() {
|
|
301
|
+
state.components.delete(component.id);
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
return registerDisposable({
|
|
305
|
+
dispose() {
|
|
306
|
+
disposable.dispose();
|
|
307
|
+
state.components.delete(component.id);
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
registerActionRailItem(item) {
|
|
312
|
+
assertRegistrationCapability(state, "actionRail.register");
|
|
313
|
+
return registerDisposable(options.registrationSink.registerActionRailItem(state.entry.id, item));
|
|
314
|
+
},
|
|
315
|
+
registerSettingsSection(section) {
|
|
316
|
+
return registerDisposable(options.registrationSink.registerSettingsSection(state.entry.id, section));
|
|
317
|
+
},
|
|
318
|
+
registerLocaleCatalog(catalog) {
|
|
319
|
+
return registerDisposable(scopedHost.i18n.registerCatalog(state.entry.id, catalog));
|
|
320
|
+
},
|
|
321
|
+
registerLocaleCatalogLoader(loader) {
|
|
322
|
+
return registerDisposable(scopedHost.i18n.registerCatalogLoader(state.entry.id, loader));
|
|
323
|
+
},
|
|
324
|
+
registerService(token, service) {
|
|
325
|
+
state.services.set(token, service);
|
|
326
|
+
return registerDisposable({
|
|
327
|
+
dispose() {
|
|
328
|
+
state.services.delete(token);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
const applyStateFromEntry = async (state) => {
|
|
335
|
+
const compatibility = evaluateExtensionCompatibility(state.entry.manifest, {
|
|
336
|
+
hostApiVersion: options.host.apiVersion || EXTENSION_HOST_API_VERSION,
|
|
337
|
+
hostVersion: options.host.environment.hostVersion,
|
|
338
|
+
runtimeVersion: EXTENSION_RUNTIME_VERSION,
|
|
339
|
+
themeContractVersion: THEME_CONTRACT_VERSION,
|
|
340
|
+
});
|
|
341
|
+
state.status = !compatibility.compatible
|
|
342
|
+
? "incompatible"
|
|
343
|
+
: state.enabled
|
|
344
|
+
? (state.entry.activation === "eager" && started ? "activating" : "registered")
|
|
345
|
+
: "disabled";
|
|
346
|
+
if (!compatibility.compatible) {
|
|
347
|
+
const issueText = compatibility.issues.map((issue) => issue.message).join(" ");
|
|
348
|
+
const record = toErrorRecord("compatibility", new Error(issueText));
|
|
349
|
+
state.lastError = record;
|
|
350
|
+
await publishDiagnostic(state.entry.id, asDiagnostic("EXT_RUNTIME_COMPATIBILITY", "error", record));
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const activateState = async (state) => {
|
|
354
|
+
if (state.status === "active")
|
|
355
|
+
return;
|
|
356
|
+
if (state.activationTask)
|
|
357
|
+
return await state.activationTask;
|
|
358
|
+
const compatibility = evaluateExtensionCompatibility(state.entry.manifest, {
|
|
359
|
+
hostApiVersion: options.host.apiVersion || EXTENSION_HOST_API_VERSION,
|
|
360
|
+
hostVersion: options.host.environment.hostVersion,
|
|
361
|
+
runtimeVersion: EXTENSION_RUNTIME_VERSION,
|
|
362
|
+
themeContractVersion: THEME_CONTRACT_VERSION,
|
|
363
|
+
});
|
|
364
|
+
if (!compatibility.compatible) {
|
|
365
|
+
state.status = "incompatible";
|
|
366
|
+
const issueText = compatibility.issues.map((issue) => issue.message).join(" ");
|
|
367
|
+
const error = toErrorRecord("compatibility", new Error(issueText));
|
|
368
|
+
state.lastError = error;
|
|
369
|
+
await publishDiagnostic(state.entry.id, asDiagnostic("EXT_RUNTIME_COMPATIBILITY", "error", error));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
state.activationTask = (async () => {
|
|
373
|
+
state.enabled = true;
|
|
374
|
+
state.status = "activating";
|
|
375
|
+
emitSnapshotChange();
|
|
376
|
+
await clearDiagnostics(state.entry.id);
|
|
377
|
+
const hostGranted = new Set(options.host.environment.grantedCapabilities ?? state.entry.manifest.capabilities);
|
|
378
|
+
const grantedCapabilities = state.entry.manifest.capabilities.filter((capability) => hostGranted.has(capability));
|
|
379
|
+
const missingCapabilities = state.entry.manifest.capabilities.filter((capability) => !hostGranted.has(capability));
|
|
380
|
+
try {
|
|
381
|
+
if (missingCapabilities.length > 0) {
|
|
382
|
+
await publishDiagnostic(state.entry.id, {
|
|
383
|
+
severity: "warning",
|
|
384
|
+
code: "EXT_RUNTIME_CAPABILITY_TRIMMED",
|
|
385
|
+
message: `Host did not grant all requested capabilities: ${missingCapabilities.join(", ")}`,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
const module = await loader.load(state.entry);
|
|
389
|
+
const scopedHost = createScopedHost(options.host, state.entry.id, grantedCapabilities);
|
|
390
|
+
const context = buildContext(state, scopedHost);
|
|
391
|
+
state.loadedModule = module;
|
|
392
|
+
state.activeContext = context;
|
|
393
|
+
const activationResult = await module.activate(context);
|
|
394
|
+
if (activationResult && typeof activationResult === "object") {
|
|
395
|
+
const maybeDisposable = activationResult;
|
|
396
|
+
if (typeof maybeDisposable.dispose === "function") {
|
|
397
|
+
state.runtimeDisposables.push(maybeDisposable);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
const nestedDisposable = activationResult.dispose;
|
|
401
|
+
if (nestedDisposable && typeof nestedDisposable.dispose === "function") {
|
|
402
|
+
state.runtimeDisposables.push(nestedDisposable);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
state.status = "active";
|
|
407
|
+
state.lastActivatedAt = now();
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
const record = toErrorRecord("activate", error);
|
|
411
|
+
state.lastError = record;
|
|
412
|
+
state.status = "error";
|
|
413
|
+
for (const disposable of [...state.runtimeDisposables].reverse()) {
|
|
414
|
+
disposable.dispose();
|
|
415
|
+
}
|
|
416
|
+
state.runtimeDisposables = [];
|
|
417
|
+
state.components.clear();
|
|
418
|
+
state.services.clear();
|
|
419
|
+
state.activeContext = null;
|
|
420
|
+
state.loadedModule = null;
|
|
421
|
+
await publishDiagnostic(state.entry.id, asDiagnostic("EXT_RUNTIME_ACTIVATE_FAILED", "error", record));
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
state.activationTask = null;
|
|
425
|
+
emitSnapshotChange();
|
|
426
|
+
}
|
|
427
|
+
})();
|
|
428
|
+
return await state.activationTask;
|
|
429
|
+
};
|
|
430
|
+
const deactivateState = async (state) => {
|
|
431
|
+
if (state.status !== "active" && !state.activeContext) {
|
|
432
|
+
state.status = state.enabled ? "registered" : "disabled";
|
|
433
|
+
emitSnapshotChange();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (state.deactivationTask)
|
|
437
|
+
return await state.deactivationTask;
|
|
438
|
+
state.deactivationTask = (async () => {
|
|
439
|
+
state.status = "deactivating";
|
|
440
|
+
emitSnapshotChange();
|
|
441
|
+
try {
|
|
442
|
+
if (state.loadedModule?.deactivate && state.activeContext) {
|
|
443
|
+
await state.loadedModule.deactivate(state.activeContext);
|
|
444
|
+
}
|
|
445
|
+
for (const disposable of [...state.runtimeDisposables].reverse()) {
|
|
446
|
+
disposable.dispose();
|
|
447
|
+
}
|
|
448
|
+
state.runtimeDisposables = [];
|
|
449
|
+
state.components.clear();
|
|
450
|
+
state.services.clear();
|
|
451
|
+
state.activeContext = null;
|
|
452
|
+
state.loadedModule = null;
|
|
453
|
+
state.status = state.enabled ? "registered" : "disabled";
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
const record = toErrorRecord("deactivate", error);
|
|
457
|
+
state.lastError = record;
|
|
458
|
+
state.status = "error";
|
|
459
|
+
await publishDiagnostic(state.entry.id, asDiagnostic("EXT_RUNTIME_DEACTIVATE_FAILED", "error", record));
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
state.deactivationTask = null;
|
|
463
|
+
emitSnapshotChange();
|
|
464
|
+
}
|
|
465
|
+
})();
|
|
466
|
+
return await state.deactivationTask;
|
|
467
|
+
};
|
|
468
|
+
const readInstalledIndex = async () => {
|
|
469
|
+
return await options.storage.get(EXTENSION_INSTALL_INDEX_KEY) ?? [];
|
|
470
|
+
};
|
|
471
|
+
const writeInstalledIndex = async (next) => {
|
|
472
|
+
const unique = Array.from(new Set(next)).sort((left, right) => left.localeCompare(right));
|
|
473
|
+
await options.storage.set(EXTENSION_INSTALL_INDEX_KEY, unique);
|
|
474
|
+
};
|
|
475
|
+
const registerInstalledRecord = async (record, moduleCode) => {
|
|
476
|
+
const existing = registry.get(record.manifest.id);
|
|
477
|
+
if (existing?.source === "bundled") {
|
|
478
|
+
throw new Error(`Cannot install external extension '${record.manifest.id}' because a bundled extension with the same id is already registered.`);
|
|
479
|
+
}
|
|
480
|
+
if (existing?.source === "installed") {
|
|
481
|
+
const existingState = states.get(record.manifest.id);
|
|
482
|
+
if (existingState) {
|
|
483
|
+
await deactivateState(existingState);
|
|
484
|
+
}
|
|
485
|
+
installedRegistrationDisposables.get(record.manifest.id)?.dispose();
|
|
486
|
+
installedRegistrationDisposables.delete(record.manifest.id);
|
|
487
|
+
registry.unregister(record.manifest.id);
|
|
488
|
+
states.delete(record.manifest.id);
|
|
489
|
+
}
|
|
490
|
+
const entry = {
|
|
491
|
+
source: "installed",
|
|
492
|
+
manifest: record.manifest,
|
|
493
|
+
activation: "lazy",
|
|
494
|
+
installedRecord: record,
|
|
495
|
+
load: createExternalModuleFactory(record, moduleCode),
|
|
496
|
+
};
|
|
497
|
+
const disposable = registry.registerInstalled(entry);
|
|
498
|
+
installedRegistrationDisposables.set(record.manifest.id, disposable);
|
|
499
|
+
const registered = registry.get(record.manifest.id);
|
|
500
|
+
if (!registered) {
|
|
501
|
+
throw new Error(`Failed to register installed extension '${record.manifest.id}'.`);
|
|
502
|
+
}
|
|
503
|
+
const state = ensureState(registered);
|
|
504
|
+
const persistedEnabled = await options.storage.get(getExtensionEnabledStateKey(record.manifest.id));
|
|
505
|
+
state.enabled = persistedEnabled ?? record.manifest.enabledByDefault;
|
|
506
|
+
await applyStateFromEntry(state);
|
|
507
|
+
emitSnapshotChange();
|
|
508
|
+
};
|
|
509
|
+
const rehydrateInstalledExtensions = async () => {
|
|
510
|
+
if (installedRehydrated) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
installedRehydrated = true;
|
|
514
|
+
const installedIds = await readInstalledIndex();
|
|
515
|
+
for (const extensionId of installedIds) {
|
|
516
|
+
const record = await options.storage.get(getInstalledExtensionRecordKey(extensionId));
|
|
517
|
+
const moduleCode = await options.storage.get(getInstalledExtensionModuleKey(extensionId));
|
|
518
|
+
if (!record || !moduleCode) {
|
|
519
|
+
await options.storage.remove(getInstalledExtensionRecordKey(extensionId));
|
|
520
|
+
await options.storage.remove(getInstalledExtensionModuleKey(extensionId));
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
await registerInstalledRecord(record, moduleCode);
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
await publishDiagnostic(extensionId, asDiagnostic("EXT_RUNTIME_REHYDRATE_FAILED", "error", toErrorRecord("install", error)));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
return {
|
|
532
|
+
version: EXTENSION_RUNTIME_VERSION,
|
|
533
|
+
getSnapshot,
|
|
534
|
+
subscribe(listener) {
|
|
535
|
+
return emitter.subscribe(listener);
|
|
536
|
+
},
|
|
537
|
+
listCatalog() {
|
|
538
|
+
return registry.list();
|
|
539
|
+
},
|
|
540
|
+
listAvailableCatalogEntries() {
|
|
541
|
+
return listCatalogEntries();
|
|
542
|
+
},
|
|
543
|
+
get(id) {
|
|
544
|
+
return getSnapshot().extensions.find((extension) => extension.id === id);
|
|
545
|
+
},
|
|
546
|
+
registerBundledExtension(entry) {
|
|
547
|
+
const disposable = registry.registerBundled(entry);
|
|
548
|
+
const registered = registry.get(entry.manifest.id);
|
|
549
|
+
if (!registered) {
|
|
550
|
+
throw new Error(`Failed to register bundled extension '${entry.manifest.id}'.`);
|
|
551
|
+
}
|
|
552
|
+
const state = ensureState(registered);
|
|
553
|
+
state.enabled = registered.manifest.enabledByDefault;
|
|
554
|
+
void applyStateFromEntry(state);
|
|
555
|
+
emitSnapshotChange();
|
|
556
|
+
if (started && state.enabled && registered.activation === "eager") {
|
|
557
|
+
void activateState(state);
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
dispose() {
|
|
561
|
+
void deactivateState(state);
|
|
562
|
+
disposable.dispose();
|
|
563
|
+
states.delete(registered.id);
|
|
564
|
+
emitSnapshotChange();
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
registerCatalog(catalog, registrationOptions = {}) {
|
|
569
|
+
const issues = validateCatalogDocument(catalog);
|
|
570
|
+
if (issues.length > 0) {
|
|
571
|
+
throw new Error(`Invalid extension catalog: ${issues.join(" ")}`);
|
|
572
|
+
}
|
|
573
|
+
const catalogId = registrationOptions.catalogId ?? catalog.publisher ?? `catalog:${externalCatalogEntries.size + 1}`;
|
|
574
|
+
const addedEntryIds = [];
|
|
575
|
+
for (const entry of catalog.extensions) {
|
|
576
|
+
if (externalCatalogEntries.has(entry.entryId)) {
|
|
577
|
+
throw new Error(`External extension catalog entry '${entry.entryId}' is already registered.`);
|
|
578
|
+
}
|
|
579
|
+
externalCatalogEntries.set(entry.entryId, {
|
|
580
|
+
entryId: entry.entryId,
|
|
581
|
+
catalogId,
|
|
582
|
+
baseUrl: registrationOptions.baseUrl ?? catalog.baseUrl,
|
|
583
|
+
entry,
|
|
584
|
+
});
|
|
585
|
+
addedEntryIds.push(entry.entryId);
|
|
586
|
+
}
|
|
587
|
+
emitSnapshotChange();
|
|
588
|
+
return {
|
|
589
|
+
dispose() {
|
|
590
|
+
for (const entryId of addedEntryIds) {
|
|
591
|
+
externalCatalogEntries.delete(entryId);
|
|
592
|
+
}
|
|
593
|
+
emitSnapshotChange();
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
async loadCatalog(url, registrationOptions = {}) {
|
|
598
|
+
if (!transport) {
|
|
599
|
+
throw new Error("No extension artifact transport is configured for catalog loading.");
|
|
600
|
+
}
|
|
601
|
+
const catalog = await transport.fetchJson(url);
|
|
602
|
+
const baseUrl = new URL(".", url).toString();
|
|
603
|
+
return this.registerCatalog(catalog, {
|
|
604
|
+
catalogId: registrationOptions.catalogId ?? url,
|
|
605
|
+
baseUrl,
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
async start() {
|
|
609
|
+
await rehydrateInstalledExtensions();
|
|
610
|
+
started = true;
|
|
611
|
+
for (const entry of registry.list()) {
|
|
612
|
+
const state = ensureState(entry);
|
|
613
|
+
const persisted = await options.storage.get(getExtensionEnabledStateKey(entry.id));
|
|
614
|
+
state.enabled = persisted ?? entry.manifest.enabledByDefault;
|
|
615
|
+
await applyStateFromEntry(state);
|
|
616
|
+
}
|
|
617
|
+
emitSnapshotChange();
|
|
618
|
+
for (const entry of registry.list()) {
|
|
619
|
+
const state = ensureState(entry);
|
|
620
|
+
if (!state.enabled)
|
|
621
|
+
continue;
|
|
622
|
+
if (state.status === "incompatible")
|
|
623
|
+
continue;
|
|
624
|
+
if (entry.activation === "eager") {
|
|
625
|
+
await activateState(state);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
emitSnapshotChange();
|
|
629
|
+
},
|
|
630
|
+
async stop() {
|
|
631
|
+
const active = Array.from(states.values()).filter((state) => state.status === "active" || state.activeContext);
|
|
632
|
+
for (const state of active) {
|
|
633
|
+
await deactivateState(state);
|
|
634
|
+
}
|
|
635
|
+
started = false;
|
|
636
|
+
emitSnapshotChange();
|
|
637
|
+
},
|
|
638
|
+
async activate(id) {
|
|
639
|
+
const entry = registry.get(id);
|
|
640
|
+
if (!entry) {
|
|
641
|
+
throw new Error(`Unknown extension '${id}'.`);
|
|
642
|
+
}
|
|
643
|
+
const state = ensureState(entry);
|
|
644
|
+
if (!state.enabled) {
|
|
645
|
+
await this.setEnabled(id, true);
|
|
646
|
+
}
|
|
647
|
+
await activateState(state);
|
|
648
|
+
},
|
|
649
|
+
async ensureActivated(id) {
|
|
650
|
+
await this.activate(id);
|
|
651
|
+
},
|
|
652
|
+
async deactivate(id) {
|
|
653
|
+
const entry = registry.get(id);
|
|
654
|
+
if (!entry) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const state = ensureState(entry);
|
|
658
|
+
await deactivateState(state);
|
|
659
|
+
},
|
|
660
|
+
async setEnabled(id, enabled) {
|
|
661
|
+
const entry = registry.get(id);
|
|
662
|
+
if (!entry) {
|
|
663
|
+
throw new Error(`Unknown extension '${id}'.`);
|
|
664
|
+
}
|
|
665
|
+
const state = ensureState(entry);
|
|
666
|
+
state.enabled = enabled;
|
|
667
|
+
await options.storage.set(getExtensionEnabledStateKey(id), enabled);
|
|
668
|
+
if (!enabled) {
|
|
669
|
+
await deactivateState(state);
|
|
670
|
+
state.status = "disabled";
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
state.status = entry.activation === "eager" && started ? "activating" : "registered";
|
|
674
|
+
if (started && entry.activation === "eager") {
|
|
675
|
+
await activateState(state);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
emitSnapshotChange();
|
|
679
|
+
},
|
|
680
|
+
isEnabled(id) {
|
|
681
|
+
return states.get(id)?.enabled ?? registry.get(id)?.manifest.enabledByDefault ?? false;
|
|
682
|
+
},
|
|
683
|
+
async installFromCatalogEntry(entryId, installOptions = {}) {
|
|
684
|
+
if (!transport) {
|
|
685
|
+
throw new Error("No extension artifact transport is configured for external extension installation.");
|
|
686
|
+
}
|
|
687
|
+
if (!options.trustPolicy) {
|
|
688
|
+
throw new Error("External extension installation requires an explicit trust policy.");
|
|
689
|
+
}
|
|
690
|
+
const catalogEntry = externalCatalogEntries.get(entryId);
|
|
691
|
+
if (!catalogEntry) {
|
|
692
|
+
throw new Error(`Unknown external catalog entry '${entryId}'.`);
|
|
693
|
+
}
|
|
694
|
+
const payload = await fetchInstallableCatalogPayload(catalogEntry, transport, options.trustPolicy);
|
|
695
|
+
if (payload.manifest.kind !== "external") {
|
|
696
|
+
throw new Error(`Extension '${payload.manifest.id}' is not marked as an external installable package.`);
|
|
697
|
+
}
|
|
698
|
+
const compatibility = evaluateExtensionCompatibility(payload.manifest, {
|
|
699
|
+
hostApiVersion: options.host.apiVersion || EXTENSION_HOST_API_VERSION,
|
|
700
|
+
hostVersion: options.host.environment.hostVersion,
|
|
701
|
+
runtimeVersion: EXTENSION_RUNTIME_VERSION,
|
|
702
|
+
themeContractVersion: THEME_CONTRACT_VERSION,
|
|
703
|
+
});
|
|
704
|
+
if (!compatibility.compatible) {
|
|
705
|
+
throw new Error(`Extension '${payload.manifest.id}' is incompatible with this host: ${compatibility.issues.map((issue) => issue.message).join(" ")}`);
|
|
706
|
+
}
|
|
707
|
+
const timestamp = now();
|
|
708
|
+
const priorRecord = await options.storage.get(getInstalledExtensionRecordKey(payload.manifest.id));
|
|
709
|
+
const record = {
|
|
710
|
+
source: "installed",
|
|
711
|
+
catalogId: payload.catalogEntry.catalogId,
|
|
712
|
+
entryId: payload.catalogEntry.entryId,
|
|
713
|
+
manifest: payload.manifest,
|
|
714
|
+
signedManifest: payload.signedManifest,
|
|
715
|
+
manifestUrl: payload.catalogEntry.entry.urls.manifest,
|
|
716
|
+
signedManifestUrl: payload.catalogEntry.entry.urls.signedManifest,
|
|
717
|
+
moduleUrl: payload.catalogEntry.entry.urls.module,
|
|
718
|
+
moduleIntegrity: payload.catalogEntry.entry.integrity.module,
|
|
719
|
+
installedAt: priorRecord?.installedAt ?? timestamp,
|
|
720
|
+
updatedAt: timestamp,
|
|
721
|
+
verification: {
|
|
722
|
+
integrityVerified: true,
|
|
723
|
+
signatureVerified: payload.signatureVerified,
|
|
724
|
+
signerKeyId: payload.signer?.keyId,
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
await options.storage.set(getInstalledExtensionRecordKey(payload.manifest.id), record);
|
|
728
|
+
await options.storage.set(getInstalledExtensionModuleKey(payload.manifest.id), payload.moduleCode);
|
|
729
|
+
await writeInstalledIndex([...(await readInstalledIndex()).filter((value) => value !== payload.manifest.id), payload.manifest.id]);
|
|
730
|
+
await registerInstalledRecord(record, payload.moduleCode);
|
|
731
|
+
if (installOptions.autoActivate) {
|
|
732
|
+
await this.activate(payload.manifest.id);
|
|
733
|
+
}
|
|
734
|
+
return record;
|
|
735
|
+
},
|
|
736
|
+
async updateFromCatalogEntry(entryId, installOptions = {}) {
|
|
737
|
+
return await this.installFromCatalogEntry(entryId, installOptions);
|
|
738
|
+
},
|
|
739
|
+
async removeInstalledExtension(id) {
|
|
740
|
+
const entry = registry.get(id);
|
|
741
|
+
if (entry?.source === "bundled") {
|
|
742
|
+
throw new Error(`Cannot remove bundled extension '${id}'.`);
|
|
743
|
+
}
|
|
744
|
+
const state = states.get(id);
|
|
745
|
+
if (state) {
|
|
746
|
+
await deactivateState(state);
|
|
747
|
+
}
|
|
748
|
+
installedRegistrationDisposables.get(id)?.dispose();
|
|
749
|
+
installedRegistrationDisposables.delete(id);
|
|
750
|
+
registry.unregister(id);
|
|
751
|
+
states.delete(id);
|
|
752
|
+
await options.storage.remove(getInstalledExtensionRecordKey(id));
|
|
753
|
+
await options.storage.remove(getInstalledExtensionModuleKey(id));
|
|
754
|
+
await writeInstalledIndex((await readInstalledIndex()).filter((value) => value !== id));
|
|
755
|
+
emitSnapshotChange();
|
|
756
|
+
},
|
|
757
|
+
getConfigurationStore(extensionId) {
|
|
758
|
+
return createExtensionConfigurationStore(extensionId, options.storage);
|
|
759
|
+
},
|
|
760
|
+
getService(extensionId, token) {
|
|
761
|
+
return states.get(extensionId)?.services.get(token);
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
//# sourceMappingURL=runtime.js.map
|