@wendoo/bridge-app 0.2.2
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 +44 -0
- package/dist/app-bridge.d.ts +77 -0
- package/dist/app-bridge.d.ts.map +1 -0
- package/dist/app-bridge.js +205 -0
- package/dist/app-environment-host.d.ts +524 -0
- package/dist/app-environment-host.d.ts.map +1 -0
- package/dist/app-environment-host.js +1560 -0
- package/dist/brain-diagnostics.d.ts +47 -0
- package/dist/brain-diagnostics.d.ts.map +1 -0
- package/dist/brain-diagnostics.js +100 -0
- package/dist/bridge-project.d.ts +32 -0
- package/dist/bridge-project.d.ts.map +1 -0
- package/dist/bridge-project.js +69 -0
- package/dist/compilation.d.ts +145 -0
- package/dist/compilation.d.ts.map +1 -0
- package/dist/compilation.js +404 -0
- package/dist/core-extension.d.ts +10 -0
- package/dist/core-extension.d.ts.map +1 -0
- package/dist/core-extension.js +9 -0
- package/dist/embedded-extension-id-gate.d.ts +38 -0
- package/dist/embedded-extension-id-gate.d.ts.map +1 -0
- package/dist/embedded-extension-id-gate.js +55 -0
- package/dist/embedded-extension-loader.d.ts +33 -0
- package/dist/embedded-extension-loader.d.ts.map +1 -0
- package/dist/embedded-extension-loader.js +90 -0
- package/dist/embedded-extension-vite-plugin.d.ts +34 -0
- package/dist/embedded-extension-vite-plugin.d.ts.map +1 -0
- package/dist/embedded-extension-vite-plugin.js +39 -0
- package/dist/embedded-extensions.d.ts +293 -0
- package/dist/embedded-extensions.d.ts.map +1 -0
- package/dist/embedded-extensions.js +526 -0
- package/dist/extension-catalog.d.ts +243 -0
- package/dist/extension-catalog.d.ts.map +1 -0
- package/dist/extension-catalog.js +408 -0
- package/dist/extension-install-log.d.ts +54 -0
- package/dist/extension-install-log.d.ts.map +1 -0
- package/dist/extension-install-log.js +22 -0
- package/dist/extension-install.d.ts +162 -0
- package/dist/extension-install.d.ts.map +1 -0
- package/dist/extension-install.js +412 -0
- package/dist/extension-report-presenter.d.ts +40 -0
- package/dist/extension-report-presenter.d.ts.map +1 -0
- package/dist/extension-report-presenter.js +36 -0
- package/dist/fetched-extension-snapshots.d.ts +66 -0
- package/dist/fetched-extension-snapshots.d.ts.map +1 -0
- package/dist/fetched-extension-snapshots.js +137 -0
- package/dist/folder-host-session.d.ts +85 -0
- package/dist/folder-host-session.d.ts.map +1 -0
- package/dist/folder-host-session.js +210 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/library-offer.d.ts +60 -0
- package/dist/library-offer.d.ts.map +1 -0
- package/dist/library-offer.js +47 -0
- package/dist/library-uninstall-guard.d.ts +66 -0
- package/dist/library-uninstall-guard.d.ts.map +1 -0
- package/dist/library-uninstall-guard.js +103 -0
- package/dist/manifest-files.d.ts +53 -0
- package/dist/manifest-files.d.ts.map +1 -0
- package/dist/manifest-files.js +242 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +2 -0
- package/dist/project-file-bridge.d.ts +10 -0
- package/dist/project-file-bridge.d.ts.map +1 -0
- package/dist/project-file-bridge.js +87 -0
- package/dist/user-tile-registration.d.ts +89 -0
- package/dist/user-tile-registration.d.ts.map +1 -0
- package/dist/user-tile-registration.js +100 -0
- package/dist/vfs-asset-url-provider.d.ts +22 -0
- package/dist/vfs-asset-url-provider.d.ts.map +1 -0
- package/dist/vfs-asset-url-provider.js +52 -0
- package/dist/workspace-folder-project-store.d.ts +125 -0
- package/dist/workspace-folder-project-store.d.ts.map +1 -0
- package/dist/workspace-folder-project-store.js +400 -0
- package/package.json +103 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { type ExtensionCatalogDocument, type ExtensionCatalogDocumentEntry, type ExtensionTarget } from "@wendoo/app-host";
|
|
2
|
+
export { satisfiesRange } from "@wendoo/app-host";
|
|
3
|
+
import type { EmbeddedExtension, FetchedExtensionContentMap } from "./embedded-extensions.js";
|
|
4
|
+
/**
|
|
5
|
+
* One layer of a project's platform stack: a resolved layer library's
|
|
6
|
+
* `<owner>/<repo>` coordinate and the semantic version its bundled content
|
|
7
|
+
* declares. Compatibility filtering matches an extension's declared targets
|
|
8
|
+
* against these entries.
|
|
9
|
+
*/
|
|
10
|
+
export interface PlatformStackLayer {
|
|
11
|
+
/** The layer library's `<owner>/<repo>` coordinate. */
|
|
12
|
+
readonly coordinate: string;
|
|
13
|
+
/** The layer library's declared semantic version, or `0.0.0` when it declares none. */
|
|
14
|
+
readonly version: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One entry of the extension catalog: an embedded extension surfaced for a
|
|
18
|
+
* project, annotated with its manifest identity and its lifecycle state
|
|
19
|
+
* relative to the project.
|
|
20
|
+
*/
|
|
21
|
+
export interface ExtensionCatalogEntry {
|
|
22
|
+
/** The extension's `<owner>/<repo>` coordinate: its identity and manifest key. */
|
|
23
|
+
readonly coordinate: string;
|
|
24
|
+
/** Display name read from the extension's content manifest; the coordinate when it declares none. */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** Semantic version read from the extension's content manifest, or `0.0.0` when it declares none. */
|
|
27
|
+
readonly version: string;
|
|
28
|
+
/**
|
|
29
|
+
* Free-form description: the extension's content manifest's when it carries
|
|
30
|
+
* one, else the host catalog document's entry description; absent when
|
|
31
|
+
* neither declares one.
|
|
32
|
+
*/
|
|
33
|
+
readonly description?: string;
|
|
34
|
+
/** Thumbnail URL or data URI read from the extension's content manifest; absent when it declares none. */
|
|
35
|
+
readonly thumbnailUrl?: string;
|
|
36
|
+
/** True when the extension is in the project's resolved extension set. */
|
|
37
|
+
readonly installed: boolean;
|
|
38
|
+
/** GitHub repository URL `https://github.com/<coordinate>`; set only for remote (`gh:`) entries, whose coordinate is their repository. */
|
|
39
|
+
readonly repoUrl?: string;
|
|
40
|
+
/** True for a fetched (`gh:`) dependency with installed content, which carries an on-request update affordance. */
|
|
41
|
+
readonly updatable?: boolean;
|
|
42
|
+
/** Present when a declared `gh:` dependency has no installed snapshot content. */
|
|
43
|
+
readonly broken?: {
|
|
44
|
+
/** Stable failure code of the most recent recorded install attempt; absent when none is recorded. */
|
|
45
|
+
readonly code?: string;
|
|
46
|
+
/** Human-readable description of the broken state. */
|
|
47
|
+
readonly message: string;
|
|
48
|
+
};
|
|
49
|
+
/** Present when the installed content's manifest identity differs from the coordinate its reference names. */
|
|
50
|
+
readonly identityMismatch?: {
|
|
51
|
+
/** The `<owner>/<repo>` identity the installed content's manifest declares. */
|
|
52
|
+
readonly declaredIdentity: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** The last recorded fetch failure per reference, keyed by the reference string as written. */
|
|
56
|
+
export type ExtensionFetchFailures = ReadonlyMap<string, {
|
|
57
|
+
readonly code: string;
|
|
58
|
+
readonly message: string;
|
|
59
|
+
}>;
|
|
60
|
+
/** Stable identifiers for the outcome of an install or uninstall action. */
|
|
61
|
+
export declare const ExtensionActionResultCode: {
|
|
62
|
+
/** The extension reference was added to the project's extensions map. */
|
|
63
|
+
readonly INSTALLED: "EXTENSION_INSTALLED";
|
|
64
|
+
/** The extension was already in the project's extensions map; nothing changed. */
|
|
65
|
+
readonly ALREADY_INSTALLED: "EXTENSION_ALREADY_INSTALLED";
|
|
66
|
+
/** The extension reference was removed from the project's extensions map. */
|
|
67
|
+
readonly UNINSTALLED: "EXTENSION_UNINSTALLED";
|
|
68
|
+
/** The extension was not in the project's extensions map; nothing changed. */
|
|
69
|
+
readonly NOT_INSTALLED: "EXTENSION_NOT_INSTALLED";
|
|
70
|
+
/** The coordinate is a required layer library and cannot be uninstalled; nothing changed. */
|
|
71
|
+
readonly LOCKED: "EXTENSION_LOCKED";
|
|
72
|
+
/** The coordinate names no bundled extension in the embed record; nothing changed. */
|
|
73
|
+
readonly UNKNOWN_COORDINATE: "EXTENSION_UNKNOWN_COORDINATE";
|
|
74
|
+
/** The reference string is not a well-formed remote extension reference; nothing changed. */
|
|
75
|
+
readonly INVALID_REFERENCE: "EXTENSION_INVALID_REFERENCE";
|
|
76
|
+
/** Another installed extension depends on the coordinate; it cannot be uninstalled while depended upon. Nothing changed. */
|
|
77
|
+
readonly REQUIRED_BY_DEPENDENT: "EXTENSION_REQUIRED_BY_DEPENDENT";
|
|
78
|
+
};
|
|
79
|
+
/** Union of all {@link ExtensionActionResultCode} values. */
|
|
80
|
+
export type ExtensionActionResultCode = (typeof ExtensionActionResultCode)[keyof typeof ExtensionActionResultCode];
|
|
81
|
+
/** Outcome of an install or uninstall action against a project's extensions map. */
|
|
82
|
+
export interface ExtensionActionResult {
|
|
83
|
+
/** True when the action changed the extensions map. */
|
|
84
|
+
readonly ok: boolean;
|
|
85
|
+
/** Stable code describing what happened. */
|
|
86
|
+
readonly code: ExtensionActionResultCode;
|
|
87
|
+
/** The extensions map after the action; unchanged from the input when `ok` is false. */
|
|
88
|
+
readonly extensions: Readonly<Record<string, string>>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Derive a project's platform stack: the resolved layer libraries the project
|
|
92
|
+
* sits on, each with its declared version. The stack is the intersection of the
|
|
93
|
+
* project's resolved extension closure with the declared platform-layer
|
|
94
|
+
* coordinates; a microbit-sim project resolves `core`, `wodal`, and
|
|
95
|
+
* `microbit-v2`, an apps/ecosim project resolves `core` and `sim`.
|
|
96
|
+
*
|
|
97
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
98
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
99
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
100
|
+
*/
|
|
101
|
+
export declare function deriveProjectPlatformStack(extensions: Readonly<Record<string, string>> | undefined, embedRecord: readonly EmbeddedExtension[], layerCoordinates: ReadonlySet<string>): PlatformStackLayer[];
|
|
102
|
+
/**
|
|
103
|
+
* Report whether an extension declaring the given compatibility targets is
|
|
104
|
+
* compatible with a platform stack. Compatible when one of the extension's
|
|
105
|
+
* target packages is in the stack and the stack's version for that package
|
|
106
|
+
* satisfies the target's `packageVersion` semver range. An extension with no
|
|
107
|
+
* targets, or an empty targets map, is compatible with no stack.
|
|
108
|
+
*
|
|
109
|
+
* @param targets - The extension's declared compatibility targets, keyed by target package coordinate.
|
|
110
|
+
* @param stack - The project's platform stack.
|
|
111
|
+
*/
|
|
112
|
+
export declare function isExtensionCompatible(targets: Readonly<Record<string, ExtensionTarget>> | undefined, stack: readonly PlatformStackLayer[]): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Build the entry cards for a project's extension browser: the project's direct
|
|
115
|
+
* dependencies that a user manages, across both transports. An embedded entry
|
|
116
|
+
* is listed when the map references its coordinate directly through an
|
|
117
|
+
* `embedded:` reference and the coordinate is not a platform layer; a remote
|
|
118
|
+
* (`gh:`) entry is listed for each `gh:` reference the map names. Nothing else
|
|
119
|
+
* lists: a platform layer library, a non-referenced bundled add-on, and a
|
|
120
|
+
* transitively-resolved sub-dependency are not entry cards (a bundled add-on is
|
|
121
|
+
* surfaced through the catalog offers instead). A remote dependency's card
|
|
122
|
+
* reads its name and version from its installed snapshot content and is marked
|
|
123
|
+
* `updatable`; a remote reference with no content available is listed under its
|
|
124
|
+
* coordinate as `broken`, carrying the last recorded fetch failure when one is
|
|
125
|
+
* known, so it can be retried or removed. A remote dependency whose installed
|
|
126
|
+
* manifest declares a different identity than its coordinate carries an
|
|
127
|
+
* `identityMismatch` annotation.
|
|
128
|
+
*
|
|
129
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
130
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
131
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers; these are never entry cards.
|
|
132
|
+
* @param fetched - Installed fetched-extension content, keyed by reference.
|
|
133
|
+
* @param fetchFailures - The last recorded fetch failure per reference.
|
|
134
|
+
* @param catalog - The host's catalog document; its entry descriptions back-fill a manifest that declares none.
|
|
135
|
+
*/
|
|
136
|
+
export declare function buildExtensionCatalog(extensions: Readonly<Record<string, string>> | undefined, embedRecord: readonly EmbeddedExtension[], layerCoordinates: ReadonlySet<string>, fetched?: FetchedExtensionContentMap, fetchFailures?: ExtensionFetchFailures, catalog?: ExtensionCatalogDocument): ExtensionCatalogEntry[];
|
|
137
|
+
/**
|
|
138
|
+
* One catalog document entry offered to a project: the entry's display
|
|
139
|
+
* metadata. Only libraries the project has not yet installed are offered.
|
|
140
|
+
*/
|
|
141
|
+
export interface ExtensionCatalogOffer {
|
|
142
|
+
/** The extension's `<owner>/<repo>` coordinate. */
|
|
143
|
+
readonly coordinate: string;
|
|
144
|
+
/** Display name from the catalog entry. */
|
|
145
|
+
readonly name: string;
|
|
146
|
+
/** Published version from the catalog entry. */
|
|
147
|
+
readonly version: string;
|
|
148
|
+
/** Description from the catalog entry. */
|
|
149
|
+
readonly description: string;
|
|
150
|
+
/** Thumbnail URL or data URI from the catalog entry; absent when it declares none. */
|
|
151
|
+
readonly thumbnailUrl?: string;
|
|
152
|
+
/** The pinned `gh:` reference an install of this offer writes. */
|
|
153
|
+
readonly ref: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Adapt a validated extension catalog document into per-project offers,
|
|
157
|
+
* compatibility-filtered against the project's platform stack: one offer per
|
|
158
|
+
* compatible entry the project has not already installed, rendered from the
|
|
159
|
+
* entry's display metadata alone. An entry whose coordinate the project's
|
|
160
|
+
* extensions map already carries is dropped, since it is represented instead by
|
|
161
|
+
* its manageable entry card. The stack is derived from the project's
|
|
162
|
+
* extensions, the embed record, and the layer coordinates.
|
|
163
|
+
*
|
|
164
|
+
* An offer's compatibility is judged by its reference transport. An
|
|
165
|
+
* `embedded:` offer is compatible when at least one target coordinate its
|
|
166
|
+
* embed-record manifest declares is a layer in the stack; its declared version
|
|
167
|
+
* range is ignored, since an embedded library versions with the host. A `gh:`
|
|
168
|
+
* offer is compatible when its catalog-entry targets both name a stack layer
|
|
169
|
+
* and satisfy that layer's version through {@link isExtensionCompatible}; a
|
|
170
|
+
* `gh:` offer that declares no targets is excluded, since it cannot be
|
|
171
|
+
* verified compatible.
|
|
172
|
+
*
|
|
173
|
+
* @param document - The validated catalog document.
|
|
174
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
175
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
176
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
177
|
+
*/
|
|
178
|
+
export declare function buildExtensionCatalogOffers(document: ExtensionCatalogDocument, extensions: Readonly<Record<string, string>> | undefined, embedRecord: readonly EmbeddedExtension[], layerCoordinates: ReadonlySet<string>): ExtensionCatalogOffer[];
|
|
179
|
+
/**
|
|
180
|
+
* One approved catalog entry as the library shelf lists it: the entry's own
|
|
181
|
+
* curated metadata, annotated with whether the project already holds it.
|
|
182
|
+
*/
|
|
183
|
+
export type ExtensionCatalogShelfEntry = Pick<ExtensionCatalogDocumentEntry, "coordinate" | "name" | "version" | "description"> & {
|
|
184
|
+
/** True when the project's extensions map already carries the entry's coordinate. */
|
|
185
|
+
readonly installed: boolean;
|
|
186
|
+
/** GitHub repository URL `https://github.com/<coordinate>`; set only for entries a remote (`gh:`) reference installs. */
|
|
187
|
+
readonly sourceUrl?: string;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Adapt a validated extension catalog document into the shelf a project can be
|
|
191
|
+
* told about: every entry the project already holds, marked installed, plus
|
|
192
|
+
* every offer {@link buildExtensionCatalogOffers} finds compatible with the
|
|
193
|
+
* project's platform stack, marked not installed. Entries run in document
|
|
194
|
+
* order, and an entry that is neither installed nor compatible is left out. An
|
|
195
|
+
* entry a remote (`gh:`) reference installs carries the repository URL it is
|
|
196
|
+
* published at.
|
|
197
|
+
*
|
|
198
|
+
* @param document - The validated catalog document.
|
|
199
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
200
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
201
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
202
|
+
*/
|
|
203
|
+
export declare function buildExtensionCatalogShelf(document: ExtensionCatalogDocument, extensions: Readonly<Record<string, string>> | undefined, embedRecord: readonly EmbeddedExtension[], layerCoordinates: ReadonlySet<string>): ExtensionCatalogShelfEntry[];
|
|
204
|
+
/**
|
|
205
|
+
* Add a remote extension to a project's extensions map from a `gh:` reference
|
|
206
|
+
* string, keyed by the reference's `<owner>/<repo>` coordinate. Rejects a
|
|
207
|
+
* string that is not a well-formed `gh:` reference, and reports an
|
|
208
|
+
* already-present coordinate as a no-op.
|
|
209
|
+
*
|
|
210
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
211
|
+
* @param reference - The `gh:<owner>/<repo>@<pin>` or `gh:<owner>/<repo>#<branch>` reference to add.
|
|
212
|
+
*/
|
|
213
|
+
export declare function installExtensionReference(extensions: Readonly<Record<string, string>> | undefined, reference: string): ExtensionActionResult;
|
|
214
|
+
/**
|
|
215
|
+
* Install an embedded extension into a project's extensions map by adding an
|
|
216
|
+
* `embedded:<owner>/<repo>` reference keyed by its coordinate. The existing
|
|
217
|
+
* resolution and materialization pipeline includes it thereafter. Rejects a
|
|
218
|
+
* coordinate that names no bundled extension, and reports an already-present
|
|
219
|
+
* coordinate as a no-op.
|
|
220
|
+
*
|
|
221
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
222
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
223
|
+
* @param coordinate - The `<owner>/<repo>` coordinate to install.
|
|
224
|
+
*/
|
|
225
|
+
export declare function installEmbeddedExtension(extensions: Readonly<Record<string, string>> | undefined, embedRecord: readonly EmbeddedExtension[], coordinate: string): ExtensionActionResult;
|
|
226
|
+
/**
|
|
227
|
+
* Uninstall an extension from a project's extensions map by removing the entry
|
|
228
|
+
* keyed by its coordinate, regardless of the entry's transport. The existing
|
|
229
|
+
* regenerate-on-load pipeline de-materializes it thereafter. Rejects a required
|
|
230
|
+
* platform layer library, reports an absent coordinate as a no-op, and blocks
|
|
231
|
+
* removing a coordinate that another still-installed extension depends on:
|
|
232
|
+
* after the removal that dependent would still pull the coordinate back into
|
|
233
|
+
* the resolved closure, so removing its explicit entry is refused with
|
|
234
|
+
* {@link ExtensionActionResultCode.REQUIRED_BY_DEPENDENT}.
|
|
235
|
+
*
|
|
236
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
237
|
+
* @param coordinate - The `<owner>/<repo>` coordinate to uninstall.
|
|
238
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers; these are not uninstallable.
|
|
239
|
+
* @param embedRecord - The host application's bundled embedded extensions, used to resolve dependents.
|
|
240
|
+
* @param fetched - Installed fetched-extension content, keyed by reference, used to resolve dependents.
|
|
241
|
+
*/
|
|
242
|
+
export declare function uninstallExtension(extensions: Readonly<Record<string, string>> | undefined, coordinate: string, layerCoordinates: ReadonlySet<string>, embedRecord: readonly EmbeddedExtension[], fetched?: FetchedExtensionContentMap): ExtensionActionResult;
|
|
243
|
+
//# sourceMappingURL=extension-catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-catalog.d.ts","sourceRoot":"","sources":["../src/extension-catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { fileContentText, LOWEST_CONTENT_VERSION, parseExtensionReference, parseProjectContentManifest, satisfiesRange, WENDOO_JSON_PATH, } from "@wendoo/app-host";
|
|
2
|
+
export { satisfiesRange } from "@wendoo/app-host";
|
|
3
|
+
import { resolveProjectExtensions } from "./embedded-extensions.js";
|
|
4
|
+
/** Stable identifiers for the outcome of an install or uninstall action. */
|
|
5
|
+
export const ExtensionActionResultCode = {
|
|
6
|
+
/** The extension reference was added to the project's extensions map. */
|
|
7
|
+
INSTALLED: "EXTENSION_INSTALLED",
|
|
8
|
+
/** The extension was already in the project's extensions map; nothing changed. */
|
|
9
|
+
ALREADY_INSTALLED: "EXTENSION_ALREADY_INSTALLED",
|
|
10
|
+
/** The extension reference was removed from the project's extensions map. */
|
|
11
|
+
UNINSTALLED: "EXTENSION_UNINSTALLED",
|
|
12
|
+
/** The extension was not in the project's extensions map; nothing changed. */
|
|
13
|
+
NOT_INSTALLED: "EXTENSION_NOT_INSTALLED",
|
|
14
|
+
/** The coordinate is a required layer library and cannot be uninstalled; nothing changed. */
|
|
15
|
+
LOCKED: "EXTENSION_LOCKED",
|
|
16
|
+
/** The coordinate names no bundled extension in the embed record; nothing changed. */
|
|
17
|
+
UNKNOWN_COORDINATE: "EXTENSION_UNKNOWN_COORDINATE",
|
|
18
|
+
/** The reference string is not a well-formed remote extension reference; nothing changed. */
|
|
19
|
+
INVALID_REFERENCE: "EXTENSION_INVALID_REFERENCE",
|
|
20
|
+
/** Another installed extension depends on the coordinate; it cannot be uninstalled while depended upon. Nothing changed. */
|
|
21
|
+
REQUIRED_BY_DEPENDENT: "EXTENSION_REQUIRED_BY_DEPENDENT",
|
|
22
|
+
};
|
|
23
|
+
/** Read an embedded extension's manifest identity from its bundled `wendoo.json`. */
|
|
24
|
+
function readEmbeddedManifest(extension) {
|
|
25
|
+
const manifestFile = extension.files.find((file) => file.path === WENDOO_JSON_PATH || file.path === `/${WENDOO_JSON_PATH}`);
|
|
26
|
+
if (manifestFile === undefined) {
|
|
27
|
+
return { name: extension.canonicalOrigin, version: LOWEST_CONTENT_VERSION };
|
|
28
|
+
}
|
|
29
|
+
const parsed = parseProjectContentManifest(manifestFile.content);
|
|
30
|
+
if (!parsed.ok) {
|
|
31
|
+
return { name: extension.canonicalOrigin, version: LOWEST_CONTENT_VERSION };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
name: parsed.manifest.name,
|
|
35
|
+
version: parsed.manifest.version,
|
|
36
|
+
...(parsed.manifest.description !== undefined ? { description: parsed.manifest.description } : {}),
|
|
37
|
+
...(parsed.manifest.thumbnailUrl !== undefined ? { thumbnailUrl: parsed.manifest.thumbnailUrl } : {}),
|
|
38
|
+
...(parsed.manifest.targets !== undefined ? { targets: parsed.manifest.targets } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The `<owner>/<repo>` coordinates a project's extensions map references directly through embedded references. */
|
|
42
|
+
function directEmbeddedCoordinates(extensions, byCoordinate) {
|
|
43
|
+
const direct = new Set();
|
|
44
|
+
for (const reference of Object.values(extensions ?? {})) {
|
|
45
|
+
const parsed = parseExtensionReference(reference);
|
|
46
|
+
if (parsed?.transport === "embedded" && byCoordinate.has(parsed.coordinate)) {
|
|
47
|
+
direct.add(parsed.coordinate);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return direct;
|
|
51
|
+
}
|
|
52
|
+
/** The coordinates of every origin in a project's resolved extension closure. */
|
|
53
|
+
function resolvedOrigins(extensions, embedRecord, fetched) {
|
|
54
|
+
const resolved = resolveProjectExtensions(extensions, { embedded: embedRecord, fetched });
|
|
55
|
+
return new Set(resolved.dependencyMounts.map((mount) => mount.namespace));
|
|
56
|
+
}
|
|
57
|
+
/** Read a fetched extension's manifest identity from its snapshot content. */
|
|
58
|
+
function readFetchedManifest(files) {
|
|
59
|
+
const entry = files.get(`/${WENDOO_JSON_PATH}`) ?? files.get(WENDOO_JSON_PATH);
|
|
60
|
+
const manifestContent = entry === undefined ? undefined : fileContentText(entry);
|
|
61
|
+
if (manifestContent === undefined) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const parsed = parseProjectContentManifest(manifestContent);
|
|
65
|
+
if (!parsed.ok) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: parsed.manifest.name,
|
|
70
|
+
version: parsed.manifest.version,
|
|
71
|
+
...(parsed.manifest.description !== undefined ? { description: parsed.manifest.description } : {}),
|
|
72
|
+
...(parsed.manifest.thumbnailUrl !== undefined ? { thumbnailUrl: parsed.manifest.thumbnailUrl } : {}),
|
|
73
|
+
...(parsed.manifest.targets !== undefined ? { targets: parsed.manifest.targets } : {}),
|
|
74
|
+
...(parsed.manifest.identity !== undefined ? { identity: parsed.manifest.identity } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Derive a project's platform stack: the resolved layer libraries the project
|
|
79
|
+
* sits on, each with its declared version. The stack is the intersection of the
|
|
80
|
+
* project's resolved extension closure with the declared platform-layer
|
|
81
|
+
* coordinates; a microbit-sim project resolves `core`, `wodal`, and
|
|
82
|
+
* `microbit-v2`, an apps/ecosim project resolves `core` and `sim`.
|
|
83
|
+
*
|
|
84
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
85
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
86
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
87
|
+
*/
|
|
88
|
+
export function deriveProjectPlatformStack(extensions, embedRecord, layerCoordinates) {
|
|
89
|
+
const byCoordinate = new Map(embedRecord.map((extension) => [extension.canonicalOrigin, extension]));
|
|
90
|
+
const stack = [];
|
|
91
|
+
for (const origin of resolvedOrigins(extensions, embedRecord)) {
|
|
92
|
+
if (!layerCoordinates.has(origin)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const extension = byCoordinate.get(origin);
|
|
96
|
+
if (extension === undefined) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
stack.push({ coordinate: origin, version: readEmbeddedManifest(extension).version });
|
|
100
|
+
}
|
|
101
|
+
return stack;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Report whether an extension declaring the given compatibility targets is
|
|
105
|
+
* compatible with a platform stack. Compatible when one of the extension's
|
|
106
|
+
* target packages is in the stack and the stack's version for that package
|
|
107
|
+
* satisfies the target's `packageVersion` semver range. An extension with no
|
|
108
|
+
* targets, or an empty targets map, is compatible with no stack.
|
|
109
|
+
*
|
|
110
|
+
* @param targets - The extension's declared compatibility targets, keyed by target package coordinate.
|
|
111
|
+
* @param stack - The project's platform stack.
|
|
112
|
+
*/
|
|
113
|
+
export function isExtensionCompatible(targets, stack) {
|
|
114
|
+
if (targets === undefined) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
for (const [coordinate, target] of Object.entries(targets)) {
|
|
118
|
+
const layer = stack.find((entry) => entry.coordinate === coordinate);
|
|
119
|
+
if (layer !== undefined && satisfiesRange(layer.version, target.packageVersion)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the entry cards for a project's extension browser: the project's direct
|
|
127
|
+
* dependencies that a user manages, across both transports. An embedded entry
|
|
128
|
+
* is listed when the map references its coordinate directly through an
|
|
129
|
+
* `embedded:` reference and the coordinate is not a platform layer; a remote
|
|
130
|
+
* (`gh:`) entry is listed for each `gh:` reference the map names. Nothing else
|
|
131
|
+
* lists: a platform layer library, a non-referenced bundled add-on, and a
|
|
132
|
+
* transitively-resolved sub-dependency are not entry cards (a bundled add-on is
|
|
133
|
+
* surfaced through the catalog offers instead). A remote dependency's card
|
|
134
|
+
* reads its name and version from its installed snapshot content and is marked
|
|
135
|
+
* `updatable`; a remote reference with no content available is listed under its
|
|
136
|
+
* coordinate as `broken`, carrying the last recorded fetch failure when one is
|
|
137
|
+
* known, so it can be retried or removed. A remote dependency whose installed
|
|
138
|
+
* manifest declares a different identity than its coordinate carries an
|
|
139
|
+
* `identityMismatch` annotation.
|
|
140
|
+
*
|
|
141
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
142
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
143
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers; these are never entry cards.
|
|
144
|
+
* @param fetched - Installed fetched-extension content, keyed by reference.
|
|
145
|
+
* @param fetchFailures - The last recorded fetch failure per reference.
|
|
146
|
+
* @param catalog - The host's catalog document; its entry descriptions back-fill a manifest that declares none.
|
|
147
|
+
*/
|
|
148
|
+
export function buildExtensionCatalog(extensions, embedRecord, layerCoordinates, fetched, fetchFailures, catalog) {
|
|
149
|
+
const byCoordinate = new Map(embedRecord.map((extension) => [extension.canonicalOrigin, extension]));
|
|
150
|
+
const installed = resolvedOrigins(extensions, embedRecord, fetched);
|
|
151
|
+
const direct = directEmbeddedCoordinates(extensions, byCoordinate);
|
|
152
|
+
const description = (coordinate, manifest) => {
|
|
153
|
+
return manifest?.description ?? catalog?.entries.find((entry) => entry.coordinate === coordinate)?.description;
|
|
154
|
+
};
|
|
155
|
+
const entries = [];
|
|
156
|
+
for (const extension of embedRecord) {
|
|
157
|
+
const coordinate = extension.canonicalOrigin;
|
|
158
|
+
if (!direct.has(coordinate) || layerCoordinates.has(coordinate)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const manifest = readEmbeddedManifest(extension);
|
|
162
|
+
const described = description(coordinate, manifest);
|
|
163
|
+
entries.push({
|
|
164
|
+
coordinate,
|
|
165
|
+
name: manifest.name,
|
|
166
|
+
version: manifest.version,
|
|
167
|
+
...(described !== undefined ? { description: described } : {}),
|
|
168
|
+
...(manifest.thumbnailUrl !== undefined ? { thumbnailUrl: manifest.thumbnailUrl } : {}),
|
|
169
|
+
installed: installed.has(coordinate),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
for (const [coordinate, reference] of Object.entries(extensions ?? {})) {
|
|
173
|
+
const parsed = parseExtensionReference(reference);
|
|
174
|
+
if (parsed?.transport !== "gh") {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const content = fetched?.get(reference);
|
|
178
|
+
const manifest = content !== undefined ? readFetchedManifest(content) : undefined;
|
|
179
|
+
const failure = fetchFailures?.get(reference);
|
|
180
|
+
const broken = content === undefined
|
|
181
|
+
? {
|
|
182
|
+
...(failure !== undefined ? { code: failure.code } : {}),
|
|
183
|
+
message: failure?.message ?? `No content is installed for "${reference}".`,
|
|
184
|
+
}
|
|
185
|
+
: undefined;
|
|
186
|
+
const identityMismatch = manifest?.identity !== undefined && manifest.identity !== coordinate
|
|
187
|
+
? { declaredIdentity: manifest.identity }
|
|
188
|
+
: undefined;
|
|
189
|
+
const described = description(coordinate, manifest);
|
|
190
|
+
entries.push({
|
|
191
|
+
coordinate,
|
|
192
|
+
name: manifest?.name ?? coordinate,
|
|
193
|
+
version: manifest?.version ?? LOWEST_CONTENT_VERSION,
|
|
194
|
+
...(described !== undefined ? { description: described } : {}),
|
|
195
|
+
...(manifest?.thumbnailUrl !== undefined ? { thumbnailUrl: manifest.thumbnailUrl } : {}),
|
|
196
|
+
installed: installed.has(coordinate),
|
|
197
|
+
repoUrl: `https://github.com/${coordinate}`,
|
|
198
|
+
...(content !== undefined ? { updatable: true } : {}),
|
|
199
|
+
...(broken !== undefined ? { broken } : {}),
|
|
200
|
+
...(identityMismatch !== undefined ? { identityMismatch } : {}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return entries;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Report whether an offer's declared target coordinates place it on a platform
|
|
207
|
+
* stack, ignoring version ranges. True when at least one declared target
|
|
208
|
+
* coordinate is a layer in the stack. An offer with no targets is on no stack.
|
|
209
|
+
* Used for embedded offers, which version with the host and so declare only the
|
|
210
|
+
* target coordinate they belong to.
|
|
211
|
+
*
|
|
212
|
+
* @param targets - The offer's declared compatibility targets, keyed by target package coordinate.
|
|
213
|
+
* @param stack - The project's platform stack.
|
|
214
|
+
*/
|
|
215
|
+
function targetsCoordinateInStack(targets, stack) {
|
|
216
|
+
if (targets === undefined) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
for (const coordinate of Object.keys(targets)) {
|
|
220
|
+
if (stack.some((layer) => layer.coordinate === coordinate)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Adapt a validated extension catalog document into per-project offers,
|
|
228
|
+
* compatibility-filtered against the project's platform stack: one offer per
|
|
229
|
+
* compatible entry the project has not already installed, rendered from the
|
|
230
|
+
* entry's display metadata alone. An entry whose coordinate the project's
|
|
231
|
+
* extensions map already carries is dropped, since it is represented instead by
|
|
232
|
+
* its manageable entry card. The stack is derived from the project's
|
|
233
|
+
* extensions, the embed record, and the layer coordinates.
|
|
234
|
+
*
|
|
235
|
+
* An offer's compatibility is judged by its reference transport. An
|
|
236
|
+
* `embedded:` offer is compatible when at least one target coordinate its
|
|
237
|
+
* embed-record manifest declares is a layer in the stack; its declared version
|
|
238
|
+
* range is ignored, since an embedded library versions with the host. A `gh:`
|
|
239
|
+
* offer is compatible when its catalog-entry targets both name a stack layer
|
|
240
|
+
* and satisfy that layer's version through {@link isExtensionCompatible}; a
|
|
241
|
+
* `gh:` offer that declares no targets is excluded, since it cannot be
|
|
242
|
+
* verified compatible.
|
|
243
|
+
*
|
|
244
|
+
* @param document - The validated catalog document.
|
|
245
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
246
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
247
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
248
|
+
*/
|
|
249
|
+
export function buildExtensionCatalogOffers(document, extensions, embedRecord, layerCoordinates) {
|
|
250
|
+
const current = extensions ?? {};
|
|
251
|
+
const stack = deriveProjectPlatformStack(extensions, embedRecord, layerCoordinates);
|
|
252
|
+
const byCoordinate = new Map(embedRecord.map((extension) => [extension.canonicalOrigin, extension]));
|
|
253
|
+
const offers = [];
|
|
254
|
+
for (const entry of document.entries) {
|
|
255
|
+
if (entry.coordinate in current) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const parsed = parseExtensionReference(entry.ref);
|
|
259
|
+
let compatible;
|
|
260
|
+
if (parsed?.transport === "embedded") {
|
|
261
|
+
const embedded = byCoordinate.get(entry.coordinate);
|
|
262
|
+
const targets = embedded !== undefined ? readEmbeddedManifest(embedded).targets : undefined;
|
|
263
|
+
compatible = targetsCoordinateInStack(targets, stack);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
compatible = isExtensionCompatible(entry.targets, stack);
|
|
267
|
+
}
|
|
268
|
+
if (!compatible) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
offers.push({
|
|
272
|
+
coordinate: entry.coordinate,
|
|
273
|
+
name: entry.name,
|
|
274
|
+
version: entry.version,
|
|
275
|
+
description: entry.description,
|
|
276
|
+
...(entry.thumbnail !== undefined ? { thumbnailUrl: entry.thumbnail } : {}),
|
|
277
|
+
ref: entry.ref,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return offers;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Adapt a validated extension catalog document into the shelf a project can be
|
|
284
|
+
* told about: every entry the project already holds, marked installed, plus
|
|
285
|
+
* every offer {@link buildExtensionCatalogOffers} finds compatible with the
|
|
286
|
+
* project's platform stack, marked not installed. Entries run in document
|
|
287
|
+
* order, and an entry that is neither installed nor compatible is left out. An
|
|
288
|
+
* entry a remote (`gh:`) reference installs carries the repository URL it is
|
|
289
|
+
* published at.
|
|
290
|
+
*
|
|
291
|
+
* @param document - The validated catalog document.
|
|
292
|
+
* @param extensions - The project's extensions map, keyed by coordinate.
|
|
293
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
294
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers.
|
|
295
|
+
*/
|
|
296
|
+
export function buildExtensionCatalogShelf(document, extensions, embedRecord, layerCoordinates) {
|
|
297
|
+
const current = extensions ?? {};
|
|
298
|
+
const offered = new Map(buildExtensionCatalogOffers(document, extensions, embedRecord, layerCoordinates).map((offer) => [
|
|
299
|
+
offer.coordinate,
|
|
300
|
+
offer,
|
|
301
|
+
]));
|
|
302
|
+
const shelf = [];
|
|
303
|
+
for (const entry of document.entries) {
|
|
304
|
+
const installed = entry.coordinate in current;
|
|
305
|
+
const listed = installed ? entry : offered.get(entry.coordinate);
|
|
306
|
+
if (listed === undefined) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const remote = parseExtensionReference(listed.ref)?.transport === "gh";
|
|
310
|
+
shelf.push({
|
|
311
|
+
coordinate: listed.coordinate,
|
|
312
|
+
name: listed.name,
|
|
313
|
+
version: listed.version,
|
|
314
|
+
description: listed.description,
|
|
315
|
+
installed,
|
|
316
|
+
...(remote ? { sourceUrl: `https://github.com/${listed.coordinate}` } : {}),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return shelf;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Add a remote extension to a project's extensions map from a `gh:` reference
|
|
323
|
+
* string, keyed by the reference's `<owner>/<repo>` coordinate. Rejects a
|
|
324
|
+
* string that is not a well-formed `gh:` reference, and reports an
|
|
325
|
+
* already-present coordinate as a no-op.
|
|
326
|
+
*
|
|
327
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
328
|
+
* @param reference - The `gh:<owner>/<repo>@<pin>` or `gh:<owner>/<repo>#<branch>` reference to add.
|
|
329
|
+
*/
|
|
330
|
+
export function installExtensionReference(extensions, reference) {
|
|
331
|
+
const current = extensions ?? {};
|
|
332
|
+
const parsed = parseExtensionReference(reference.trim());
|
|
333
|
+
if (parsed?.transport !== "gh") {
|
|
334
|
+
return { ok: false, code: ExtensionActionResultCode.INVALID_REFERENCE, extensions: current };
|
|
335
|
+
}
|
|
336
|
+
const coordinate = `${parsed.owner}/${parsed.repo}`;
|
|
337
|
+
if (coordinate in current) {
|
|
338
|
+
return { ok: false, code: ExtensionActionResultCode.ALREADY_INSTALLED, extensions: current };
|
|
339
|
+
}
|
|
340
|
+
const next = {};
|
|
341
|
+
for (const [key, value] of Object.entries(current)) {
|
|
342
|
+
next[key] = value;
|
|
343
|
+
}
|
|
344
|
+
next[coordinate] = reference.trim();
|
|
345
|
+
return { ok: true, code: ExtensionActionResultCode.INSTALLED, extensions: next };
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Install an embedded extension into a project's extensions map by adding an
|
|
349
|
+
* `embedded:<owner>/<repo>` reference keyed by its coordinate. The existing
|
|
350
|
+
* resolution and materialization pipeline includes it thereafter. Rejects a
|
|
351
|
+
* coordinate that names no bundled extension, and reports an already-present
|
|
352
|
+
* coordinate as a no-op.
|
|
353
|
+
*
|
|
354
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
355
|
+
* @param embedRecord - The host application's bundled embedded extensions.
|
|
356
|
+
* @param coordinate - The `<owner>/<repo>` coordinate to install.
|
|
357
|
+
*/
|
|
358
|
+
export function installEmbeddedExtension(extensions, embedRecord, coordinate) {
|
|
359
|
+
const current = extensions ?? {};
|
|
360
|
+
const known = embedRecord.some((extension) => extension.canonicalOrigin === coordinate);
|
|
361
|
+
if (!known) {
|
|
362
|
+
return { ok: false, code: ExtensionActionResultCode.UNKNOWN_COORDINATE, extensions: current };
|
|
363
|
+
}
|
|
364
|
+
if (coordinate in current) {
|
|
365
|
+
return { ok: false, code: ExtensionActionResultCode.ALREADY_INSTALLED, extensions: current };
|
|
366
|
+
}
|
|
367
|
+
const next = {};
|
|
368
|
+
for (const [key, value] of Object.entries(current)) {
|
|
369
|
+
next[key] = value;
|
|
370
|
+
}
|
|
371
|
+
next[coordinate] = `embedded:${coordinate}`;
|
|
372
|
+
return { ok: true, code: ExtensionActionResultCode.INSTALLED, extensions: next };
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Uninstall an extension from a project's extensions map by removing the entry
|
|
376
|
+
* keyed by its coordinate, regardless of the entry's transport. The existing
|
|
377
|
+
* regenerate-on-load pipeline de-materializes it thereafter. Rejects a required
|
|
378
|
+
* platform layer library, reports an absent coordinate as a no-op, and blocks
|
|
379
|
+
* removing a coordinate that another still-installed extension depends on:
|
|
380
|
+
* after the removal that dependent would still pull the coordinate back into
|
|
381
|
+
* the resolved closure, so removing its explicit entry is refused with
|
|
382
|
+
* {@link ExtensionActionResultCode.REQUIRED_BY_DEPENDENT}.
|
|
383
|
+
*
|
|
384
|
+
* @param extensions - The project's current extensions map, keyed by coordinate.
|
|
385
|
+
* @param coordinate - The `<owner>/<repo>` coordinate to uninstall.
|
|
386
|
+
* @param layerCoordinates - The coordinates the host declares as platform layers; these are not uninstallable.
|
|
387
|
+
* @param embedRecord - The host application's bundled embedded extensions, used to resolve dependents.
|
|
388
|
+
* @param fetched - Installed fetched-extension content, keyed by reference, used to resolve dependents.
|
|
389
|
+
*/
|
|
390
|
+
export function uninstallExtension(extensions, coordinate, layerCoordinates, embedRecord, fetched) {
|
|
391
|
+
const current = extensions ?? {};
|
|
392
|
+
if (layerCoordinates.has(coordinate)) {
|
|
393
|
+
return { ok: false, code: ExtensionActionResultCode.LOCKED, extensions: current };
|
|
394
|
+
}
|
|
395
|
+
if (!(coordinate in current)) {
|
|
396
|
+
return { ok: false, code: ExtensionActionResultCode.NOT_INSTALLED, extensions: current };
|
|
397
|
+
}
|
|
398
|
+
const next = {};
|
|
399
|
+
for (const [key, value] of Object.entries(current)) {
|
|
400
|
+
if (key !== coordinate) {
|
|
401
|
+
next[key] = value;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (resolvedOrigins(next, embedRecord, fetched).has(coordinate)) {
|
|
405
|
+
return { ok: false, code: ExtensionActionResultCode.REQUIRED_BY_DEPENDENT, extensions: current };
|
|
406
|
+
}
|
|
407
|
+
return { ok: true, code: ExtensionActionResultCode.UNINSTALLED, extensions: next };
|
|
408
|
+
}
|