@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.
Files changed (77) hide show
  1. package/README.md +44 -0
  2. package/dist/app-bridge.d.ts +77 -0
  3. package/dist/app-bridge.d.ts.map +1 -0
  4. package/dist/app-bridge.js +205 -0
  5. package/dist/app-environment-host.d.ts +524 -0
  6. package/dist/app-environment-host.d.ts.map +1 -0
  7. package/dist/app-environment-host.js +1560 -0
  8. package/dist/brain-diagnostics.d.ts +47 -0
  9. package/dist/brain-diagnostics.d.ts.map +1 -0
  10. package/dist/brain-diagnostics.js +100 -0
  11. package/dist/bridge-project.d.ts +32 -0
  12. package/dist/bridge-project.d.ts.map +1 -0
  13. package/dist/bridge-project.js +69 -0
  14. package/dist/compilation.d.ts +145 -0
  15. package/dist/compilation.d.ts.map +1 -0
  16. package/dist/compilation.js +404 -0
  17. package/dist/core-extension.d.ts +10 -0
  18. package/dist/core-extension.d.ts.map +1 -0
  19. package/dist/core-extension.js +9 -0
  20. package/dist/embedded-extension-id-gate.d.ts +38 -0
  21. package/dist/embedded-extension-id-gate.d.ts.map +1 -0
  22. package/dist/embedded-extension-id-gate.js +55 -0
  23. package/dist/embedded-extension-loader.d.ts +33 -0
  24. package/dist/embedded-extension-loader.d.ts.map +1 -0
  25. package/dist/embedded-extension-loader.js +90 -0
  26. package/dist/embedded-extension-vite-plugin.d.ts +34 -0
  27. package/dist/embedded-extension-vite-plugin.d.ts.map +1 -0
  28. package/dist/embedded-extension-vite-plugin.js +39 -0
  29. package/dist/embedded-extensions.d.ts +293 -0
  30. package/dist/embedded-extensions.d.ts.map +1 -0
  31. package/dist/embedded-extensions.js +526 -0
  32. package/dist/extension-catalog.d.ts +243 -0
  33. package/dist/extension-catalog.d.ts.map +1 -0
  34. package/dist/extension-catalog.js +408 -0
  35. package/dist/extension-install-log.d.ts +54 -0
  36. package/dist/extension-install-log.d.ts.map +1 -0
  37. package/dist/extension-install-log.js +22 -0
  38. package/dist/extension-install.d.ts +162 -0
  39. package/dist/extension-install.d.ts.map +1 -0
  40. package/dist/extension-install.js +412 -0
  41. package/dist/extension-report-presenter.d.ts +40 -0
  42. package/dist/extension-report-presenter.d.ts.map +1 -0
  43. package/dist/extension-report-presenter.js +36 -0
  44. package/dist/fetched-extension-snapshots.d.ts +66 -0
  45. package/dist/fetched-extension-snapshots.d.ts.map +1 -0
  46. package/dist/fetched-extension-snapshots.js +137 -0
  47. package/dist/folder-host-session.d.ts +85 -0
  48. package/dist/folder-host-session.d.ts.map +1 -0
  49. package/dist/folder-host-session.js +210 -0
  50. package/dist/index.d.ts +38 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +19 -0
  53. package/dist/library-offer.d.ts +60 -0
  54. package/dist/library-offer.d.ts.map +1 -0
  55. package/dist/library-offer.js +47 -0
  56. package/dist/library-uninstall-guard.d.ts +66 -0
  57. package/dist/library-uninstall-guard.d.ts.map +1 -0
  58. package/dist/library-uninstall-guard.js +103 -0
  59. package/dist/manifest-files.d.ts +53 -0
  60. package/dist/manifest-files.d.ts.map +1 -0
  61. package/dist/manifest-files.js +242 -0
  62. package/dist/node.d.ts +4 -0
  63. package/dist/node.d.ts.map +1 -0
  64. package/dist/node.js +2 -0
  65. package/dist/project-file-bridge.d.ts +10 -0
  66. package/dist/project-file-bridge.d.ts.map +1 -0
  67. package/dist/project-file-bridge.js +87 -0
  68. package/dist/user-tile-registration.d.ts +89 -0
  69. package/dist/user-tile-registration.d.ts.map +1 -0
  70. package/dist/user-tile-registration.js +100 -0
  71. package/dist/vfs-asset-url-provider.d.ts +22 -0
  72. package/dist/vfs-asset-url-provider.d.ts.map +1 -0
  73. package/dist/vfs-asset-url-provider.js +52 -0
  74. package/dist/workspace-folder-project-store.d.ts +125 -0
  75. package/dist/workspace-folder-project-store.d.ts.map +1 -0
  76. package/dist/workspace-folder-project-store.js +400 -0
  77. package/package.json +103 -0
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ export { createInMemoryProjectFileSystem, WENDOO_JSON_PATH } from "@wendoo/app-host";
2
+ export { FOLDER_HOST_MODE_FOLDER, FOLDER_HOST_MODE_GLOBAL, FOLDER_HOST_MODE_URL_PARAM, FolderSessionErrorCode, } from "@wendoo/bridge-protocol";
3
+ export { createAppBridge } from "./app-bridge.js";
4
+ export { AppEnvironmentHost } from "./app-environment-host.js";
5
+ export { collectBrainErrorDiagnostics, collectBrainTileCompileDiagnostics } from "./brain-diagnostics.js";
6
+ export { CORE_LIB_COORDINATE, CORE_LIB_REFERENCE } from "./core-extension.js";
7
+ export { findEmbeddedExtensionsMissingStableIds, formatEmbeddedExtensionIdViolations, } from "./embedded-extension-id-gate.js";
8
+ export { CatalogMoveWarningCode, createCatalogMoveVersionLookup, ExtensionResolutionCycleError, resolveProjectExtensions, } from "./embedded-extensions.js";
9
+ export { buildExtensionCatalog, buildExtensionCatalogOffers, buildExtensionCatalogShelf, deriveProjectPlatformStack, ExtensionActionResultCode, installEmbeddedExtension, installExtensionReference, isExtensionCompatible, satisfiesRange, uninstallExtension, } from "./extension-catalog.js";
10
+ export { collectExtensionFetchClosure, diffProjectDiagnostics, floatingPinsFromSnapshots, movedClosureHasMissingContent, typecheckBrainProblems, } from "./extension-install.js";
11
+ export { appendExtensionInstallLog, EXTENSION_INSTALL_LOG_APP_DATA_KEY, parseExtensionInstallLog, } from "./extension-install-log.js";
12
+ export { presentExtensionTransaction } from "./extension-report-presenter.js";
13
+ export { decodeInstalledSnapshotFiles, fetchedContentFromSnapshots, INSTALLED_EXTENSIONS_APP_DATA_KEY, installedExtensionMetadataFromSnapshots, installedSnapshotFromFetched, parseInstalledExtensionMetadata, parseInstalledExtensionSnapshots, reconstructInstalledSnapshotsFromTree, serializeInstalledExtensionSnapshots, } from "./fetched-extension-snapshots.js";
14
+ export { connectFolderHostSession, createFolderCompileDiagnosticsPublisher, FolderSessionError, } from "./folder-host-session.js";
15
+ export { addOfferedLibrary } from "./library-offer.js";
16
+ export { collectLibraryUninstallImpact, runGuardedLibraryUninstall } from "./library-uninstall-guard.js";
17
+ export { applyCompiledUserTiles, collectMetadataFromCompile } from "./user-tile-registration.js";
18
+ export { createVfsAssetUrlProvider } from "./vfs-asset-url-provider.js";
19
+ export { WORKSPACE_FOLDER_PROJECT_COLLECTION_ID, WorkspaceFolderProjectStore, WorkspaceFolderStoreError, WorkspaceFolderStoreErrorCode, } from "./workspace-folder-project-store.js";
@@ -0,0 +1,60 @@
1
+ import type { ExtensionActionResult } from "./extension-catalog.js";
2
+ import type { ExtensionInstallReport } from "./extension-install.js";
3
+ import type { ExtensionTransactionToasts } from "./extension-report-presenter.js";
4
+ /**
5
+ * What one attempt to install a reference answers: the app's own install
6
+ * outcome, narrowed to what an offer needs of it. `ok: false` is an input the
7
+ * app could not turn into an installable reference; `ok: true` carries the
8
+ * extensions-map result and, when the map changed, the transaction's report.
9
+ */
10
+ export type LibraryInstallAttempt = {
11
+ /** False: the reference did not resolve to something installable; nothing changed. */
12
+ readonly ok: false;
13
+ /** Stable code of the rejection. */
14
+ readonly code: string;
15
+ /** Human-readable failure message. */
16
+ readonly message: string;
17
+ } | {
18
+ /** True: the reference resolved and the install action ran. */
19
+ readonly ok: true;
20
+ /** The extensions-map mutation result. */
21
+ readonly action: ExtensionActionResult;
22
+ /** The install transaction's report; absent when the action changed nothing. */
23
+ readonly report?: ExtensionInstallReport;
24
+ };
25
+ /**
26
+ * The toast surface a library offer reports through: the two an extension
27
+ * transaction always has, plus the note a commit that brought new problems with
28
+ * it carries.
29
+ */
30
+ export interface LibraryOfferToasts extends ExtensionTransactionToasts {
31
+ /** The library was added, and new problems appeared with it. Names the library by display name. */
32
+ worsened(libraryName: string): void;
33
+ }
34
+ /** The app-side pieces one library offer's install runs through. */
35
+ export interface LibraryOfferInstallSurface {
36
+ /** The reference the app's approved catalog installs `coordinate` by; absent when it approves none. */
37
+ readonly approvedReference: (coordinate: string) => string | undefined;
38
+ /** Run the app's install transaction for `reference`. */
39
+ readonly install: (reference: string) => Promise<LibraryInstallAttempt>;
40
+ /** The display name the app reads `coordinate` by. */
41
+ readonly displayName: (coordinate: string) => string;
42
+ /** Where the attempt's outcome is presented. */
43
+ readonly toasts: LibraryOfferToasts;
44
+ }
45
+ /**
46
+ * Add the library at `coordinate` through the app's own install transaction and
47
+ * present what happened through `surface.toasts`: a refusal as `failed`, a
48
+ * commit as `confirmed`, and a commit that brought new problems with it as
49
+ * `worsened` after the confirmation. A coordinate the app's catalog approves no
50
+ * reference for is refused without running anything.
51
+ *
52
+ * Answers whether this attempt put the library in the project: `false` for a
53
+ * refusal, for a failure, and for a library the project already held, which runs
54
+ * no transaction and presents nothing.
55
+ *
56
+ * @param surface - The app's approved catalog, install transaction, display names, and toasts.
57
+ * @param coordinate - The `<owner>/<repo>` coordinate to add.
58
+ */
59
+ export declare function addOfferedLibrary(surface: LibraryOfferInstallSurface, coordinate: string): Promise<boolean>;
60
+ //# sourceMappingURL=library-offer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-offer.d.ts","sourceRoot":"","sources":["../src/library-offer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAGlF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IACE,sFAAsF;IACtF,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,GACD;IACE,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C,CAAC;AAEN;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,0BAA0B;IACpE,mGAAmG;IACnG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,oEAAoE;AACpE,MAAM,WAAW,0BAA0B;IACzC,uGAAuG;IACvG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvE,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACxE,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BjH"}
@@ -0,0 +1,47 @@
1
+ import { ExtensionActionResultCode } from "./extension-catalog.js";
2
+ import { presentExtensionTransaction } from "./extension-report-presenter.js";
3
+ /**
4
+ * Add the library at `coordinate` through the app's own install transaction and
5
+ * present what happened through `surface.toasts`: a refusal as `failed`, a
6
+ * commit as `confirmed`, and a commit that brought new problems with it as
7
+ * `worsened` after the confirmation. A coordinate the app's catalog approves no
8
+ * reference for is refused without running anything.
9
+ *
10
+ * Answers whether this attempt put the library in the project: `false` for a
11
+ * refusal, for a failure, and for a library the project already held, which runs
12
+ * no transaction and presents nothing.
13
+ *
14
+ * @param surface - The app's approved catalog, install transaction, display names, and toasts.
15
+ * @param coordinate - The `<owner>/<repo>` coordinate to add.
16
+ */
17
+ export async function addOfferedLibrary(surface, coordinate) {
18
+ const reference = surface.approvedReference(coordinate);
19
+ if (reference === undefined) {
20
+ surface.toasts.failed({
21
+ code: ExtensionActionResultCode.UNKNOWN_COORDINATE,
22
+ message: `No approved library is shelved at "${coordinate}".`,
23
+ });
24
+ return false;
25
+ }
26
+ const attempt = await surface.install(reference);
27
+ if (!attempt.ok) {
28
+ surface.toasts.failed({ code: attempt.code, message: attempt.message });
29
+ return false;
30
+ }
31
+ if (!attempt.action.ok) {
32
+ if (attempt.action.code === ExtensionActionResultCode.ALREADY_INSTALLED) {
33
+ return false;
34
+ }
35
+ surface.toasts.failed({ code: attempt.action.code, message: `Adding "${coordinate}" changed nothing.` });
36
+ return false;
37
+ }
38
+ const libraryName = surface.displayName(coordinate);
39
+ presentExtensionTransaction({ report: attempt.report, flavor: "install", libraryName, toasts: surface.toasts });
40
+ if (attempt.report?.committed !== true) {
41
+ return false;
42
+ }
43
+ if (attempt.report.outcome.kind === "worsened") {
44
+ surface.toasts.worsened(libraryName);
45
+ }
46
+ return true;
47
+ }
@@ -0,0 +1,66 @@
1
+ import type { FileContent } from "@wendoo/app-host";
2
+ import type { EmbeddedExtension, FetchedExtensionContentMap } from "./embedded-extensions.js";
3
+ /** A saved brain checked by the uninstall guard: its display name and persisted brain JSON. */
4
+ export interface UninstallGuardBrain {
5
+ /** The brain's display name, as listed to the user. */
6
+ readonly name: string;
7
+ /** The brain's persisted JSON (the plain object form of its stored bytes). */
8
+ readonly json: unknown;
9
+ }
10
+ /**
11
+ * What uninstalling one library takes away from a project: the namespaces
12
+ * leaving the resolved closure, the saved brains referencing them, and the
13
+ * user content files importing them.
14
+ */
15
+ export interface LibraryUninstallImpact {
16
+ /**
17
+ * The `<owner>/<repo>` namespaces that leave the resolved closure when the
18
+ * coordinate is removed: the coordinate itself plus every transitive
19
+ * dependency no remaining dependent still pulls in.
20
+ */
21
+ readonly leavingNamespaces: ReadonlySet<string>;
22
+ /** Display names of saved brains referencing a leaving namespace, in input order. */
23
+ readonly brainNames: readonly string[];
24
+ /** Paths of user content files importing a leaving namespace through `@lib/`, in input order. */
25
+ readonly filePaths: readonly string[];
26
+ }
27
+ /**
28
+ * Compute what uninstalling `coordinate` takes away from a project. The
29
+ * leaving set is the closure delta: the origins resolved today that are no
30
+ * longer resolved once the coordinate's manifest entry is removed, so a
31
+ * transitive dependency kept alive by another dependent does not count. A
32
+ * brain counts as affected when any of its referenced namespaces is leaving;
33
+ * a file counts when any of its `@lib/` import coordinates is leaving.
34
+ *
35
+ * @param options.extensions - The project's current extensions map, keyed by coordinate.
36
+ * @param options.coordinate - The `<owner>/<repo>` coordinate being uninstalled.
37
+ * @param options.embedded - The host application's bundled embedded extensions.
38
+ * @param options.fetched - Installed fetched-extension content, keyed by reference.
39
+ * @param options.brains - The project's saved brains, each with its display name and persisted JSON.
40
+ * @param options.files - User content files by path; omit for a host with no user content files.
41
+ */
42
+ export declare function collectLibraryUninstallImpact(options: {
43
+ extensions: Readonly<Record<string, string>> | undefined;
44
+ coordinate: string;
45
+ embedded: readonly EmbeddedExtension[];
46
+ fetched?: FetchedExtensionContentMap;
47
+ brains: readonly UninstallGuardBrain[];
48
+ files?: ReadonlyMap<string, FileContent>;
49
+ }): LibraryUninstallImpact;
50
+ /** Outcome of a guarded uninstall run. */
51
+ export type LibraryUninstallGuardOutcome = "removed" | "cancelled";
52
+ /**
53
+ * Run an uninstall behind the in-use guard. An empty impact uninstalls
54
+ * immediately; a non-empty impact asks `confirmRemoval` first and uninstalls
55
+ * only on an affirmative answer.
56
+ *
57
+ * @param options.impact - The computed uninstall impact.
58
+ * @param options.confirmRemoval - Asks the user to confirm removing an in-use library.
59
+ * @param options.uninstall - Performs the uninstall.
60
+ */
61
+ export declare function runGuardedLibraryUninstall(options: {
62
+ impact: Pick<LibraryUninstallImpact, "brainNames" | "filePaths">;
63
+ confirmRemoval: () => boolean | Promise<boolean>;
64
+ uninstall: () => void | Promise<void>;
65
+ }): Promise<LibraryUninstallGuardOutcome>;
66
+ //# sourceMappingURL=library-uninstall-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-uninstall-guard.d.ts","sourceRoot":"","sources":["../src/library-uninstall-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAG9F,+FAA+F;AAC/F,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACrD,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACvC,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC1C,GAAG,sBAAsB,CA2CzB;AAED,0CAA0C;AAC1C,MAAM,MAAM,4BAA4B,GAAG,SAAS,GAAG,WAAW,CAAC;AAEnE;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE;IACxD,MAAM,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,WAAW,CAAC,CAAC;IACjE,cAAc,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAOxC"}
@@ -0,0 +1,103 @@
1
+ import { fileContentText } from "@wendoo/app-host";
2
+ import { renameBrainNamespaces } from "@wendoo/core/app";
3
+ import { EXTENSION_IMPORT_PREFIX } from "@wendoo/ts-compiler";
4
+ import { resolveProjectExtensions } from "./embedded-extensions.js";
5
+ /** Collect every library namespace a persisted brain references. */
6
+ function collectBrainNamespaces(json) {
7
+ const namespaces = new Set();
8
+ renameBrainNamespaces(json, (namespace) => {
9
+ namespaces.add(namespace);
10
+ return namespace;
11
+ });
12
+ return namespaces;
13
+ }
14
+ /** The `<owner>/<repo>` coordinates a file's `@lib/` import specifiers name. */
15
+ function collectFileImportCoordinates(content) {
16
+ const coordinates = new Set();
17
+ const pattern = new RegExp(`["']${EXTENSION_IMPORT_PREFIX}([^"'\\s]+)["']`, "g");
18
+ for (const match of content.matchAll(pattern)) {
19
+ const segments = match[1].split("/");
20
+ if (segments.length >= 2 && segments[0].length > 0 && segments[1].length > 0) {
21
+ coordinates.add(`${segments[0]}/${segments[1]}`);
22
+ }
23
+ }
24
+ return coordinates;
25
+ }
26
+ /** The set of origin namespaces in the resolved closure of an extensions map. */
27
+ function resolvedNamespaces(extensions, embedded, fetched) {
28
+ const resolved = resolveProjectExtensions(extensions, { embedded, fetched });
29
+ return new Set(resolved.dependencyMounts.map((mount) => mount.namespace));
30
+ }
31
+ /**
32
+ * Compute what uninstalling `coordinate` takes away from a project. The
33
+ * leaving set is the closure delta: the origins resolved today that are no
34
+ * longer resolved once the coordinate's manifest entry is removed, so a
35
+ * transitive dependency kept alive by another dependent does not count. A
36
+ * brain counts as affected when any of its referenced namespaces is leaving;
37
+ * a file counts when any of its `@lib/` import coordinates is leaving.
38
+ *
39
+ * @param options.extensions - The project's current extensions map, keyed by coordinate.
40
+ * @param options.coordinate - The `<owner>/<repo>` coordinate being uninstalled.
41
+ * @param options.embedded - The host application's bundled embedded extensions.
42
+ * @param options.fetched - Installed fetched-extension content, keyed by reference.
43
+ * @param options.brains - The project's saved brains, each with its display name and persisted JSON.
44
+ * @param options.files - User content files by path; omit for a host with no user content files.
45
+ */
46
+ export function collectLibraryUninstallImpact(options) {
47
+ const current = options.extensions ?? {};
48
+ const next = {};
49
+ for (const [key, value] of Object.entries(current)) {
50
+ if (key !== options.coordinate) {
51
+ next[key] = value;
52
+ }
53
+ }
54
+ const remaining = resolvedNamespaces(next, options.embedded, options.fetched);
55
+ const leavingNamespaces = new Set();
56
+ for (const namespace of resolvedNamespaces(current, options.embedded, options.fetched)) {
57
+ if (!remaining.has(namespace)) {
58
+ leavingNamespaces.add(namespace);
59
+ }
60
+ }
61
+ const brainNames = [];
62
+ for (const brain of options.brains) {
63
+ const referenced = collectBrainNamespaces(brain.json);
64
+ for (const namespace of leavingNamespaces) {
65
+ if (referenced.has(namespace)) {
66
+ brainNames.push(brain.name);
67
+ break;
68
+ }
69
+ }
70
+ }
71
+ const filePaths = [];
72
+ for (const [path, content] of options.files ?? []) {
73
+ const text = fileContentText(content);
74
+ if (text === undefined) {
75
+ continue;
76
+ }
77
+ const imported = collectFileImportCoordinates(text);
78
+ for (const namespace of leavingNamespaces) {
79
+ if (imported.has(namespace)) {
80
+ filePaths.push(path);
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ return { leavingNamespaces, brainNames, filePaths };
86
+ }
87
+ /**
88
+ * Run an uninstall behind the in-use guard. An empty impact uninstalls
89
+ * immediately; a non-empty impact asks `confirmRemoval` first and uninstalls
90
+ * only on an affirmative answer.
91
+ *
92
+ * @param options.impact - The computed uninstall impact.
93
+ * @param options.confirmRemoval - Asks the user to confirm removing an in-use library.
94
+ * @param options.uninstall - Performs the uninstall.
95
+ */
96
+ export async function runGuardedLibraryUninstall(options) {
97
+ const inUse = options.impact.brainNames.length > 0 || options.impact.filePaths.length > 0;
98
+ if (inUse && !(await options.confirmRemoval())) {
99
+ return "cancelled";
100
+ }
101
+ await options.uninstall();
102
+ return "removed";
103
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Queries and edits over a project content manifest's `files` list: the list
3
+ * names what is in the build, while the project may hold more files than it
4
+ * lists. Membership tells the two apart; the only error direction is a listed
5
+ * file that is absent from the project. The edit helpers toggle one entry in
6
+ * the manifest's JSON text while leaving every other byte of the document
7
+ * untouched.
8
+ */
9
+ /**
10
+ * Normalize a manifest `files` entry or project file path for comparison by
11
+ * stripping leading `./` and `/` segments. `"./a.ts"`, `"/a.ts"`, and
12
+ * `"a.ts"` all normalize to `"a.ts"`.
13
+ */
14
+ export declare function normalizeManifestFilePath(path: string): string;
15
+ /**
16
+ * True when `path` is named by the manifest `files` list, comparing both
17
+ * sides with {@link normalizeManifestFilePath}.
18
+ */
19
+ export declare function isFileInBuild(files: readonly string[], path: string): boolean;
20
+ /**
21
+ * Return the `files` entries, as written, whose normalized path `exists`
22
+ * rejects. An empty result means every listed file is present. Paths absent
23
+ * from `files` are never reported, whatever `exists` answers for them.
24
+ *
25
+ * @param files - The manifest `files` list.
26
+ * @param exists - Presence test invoked with each entry's normalized path.
27
+ */
28
+ export declare function findMissingListedFiles(files: readonly string[], exists: (path: string) => boolean): readonly string[];
29
+ /**
30
+ * Read the `files` list from a manifest's JSON text without validating the
31
+ * rest of the document. Returns the string entries when the document is a
32
+ * JSON object whose `files` is an array holding at least one string, and
33
+ * `undefined` otherwise (including unparseable text).
34
+ */
35
+ export declare function readManifestFilesList(manifestText: string): readonly string[] | undefined;
36
+ /**
37
+ * Add `path` (normalized) to the manifest text's `files` array, preserving
38
+ * every byte of the document outside that array and matching the array's
39
+ * existing one-entry-per-line or single-line formatting. Returns the edited
40
+ * text, or `undefined` when the document has no well-formed root-level
41
+ * string-array `files`, the entry is already listed, or the edit would not
42
+ * produce valid JSON.
43
+ */
44
+ export declare function addManifestFilesEntry(manifestText: string, path: string): string | undefined;
45
+ /**
46
+ * Remove every `files` entry matching `path` (compared normalized) from the
47
+ * manifest text, preserving every byte of the document outside the `files`
48
+ * array. Returns the edited text, or `undefined` when the document has no
49
+ * well-formed root-level string-array `files`, no entry matches, or the edit
50
+ * would not produce valid JSON. Removing the last entry leaves `[]`.
51
+ */
52
+ export declare function removeManifestFilesEntry(manifestText: string, path: string): string | undefined;
53
+ //# sourceMappingURL=manifest-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-files.d.ts","sourceRoot":"","sources":["../src/manifest-files.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW9D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG7E;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,MAAM,EAAE,CAErH;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAgBzF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmB5F;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoB/F"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Queries and edits over a project content manifest's `files` list: the list
3
+ * names what is in the build, while the project may hold more files than it
4
+ * lists. Membership tells the two apart; the only error direction is a listed
5
+ * file that is absent from the project. The edit helpers toggle one entry in
6
+ * the manifest's JSON text while leaving every other byte of the document
7
+ * untouched.
8
+ */
9
+ /**
10
+ * Normalize a manifest `files` entry or project file path for comparison by
11
+ * stripping leading `./` and `/` segments. `"./a.ts"`, `"/a.ts"`, and
12
+ * `"a.ts"` all normalize to `"a.ts"`.
13
+ */
14
+ export function normalizeManifestFilePath(path) {
15
+ let result = path;
16
+ for (;;) {
17
+ if (result.startsWith("./")) {
18
+ result = result.slice(2);
19
+ }
20
+ else if (result.startsWith("/")) {
21
+ result = result.slice(1);
22
+ }
23
+ else {
24
+ return result;
25
+ }
26
+ }
27
+ }
28
+ /**
29
+ * True when `path` is named by the manifest `files` list, comparing both
30
+ * sides with {@link normalizeManifestFilePath}.
31
+ */
32
+ export function isFileInBuild(files, path) {
33
+ const target = normalizeManifestFilePath(path);
34
+ return files.some((entry) => normalizeManifestFilePath(entry) === target);
35
+ }
36
+ /**
37
+ * Return the `files` entries, as written, whose normalized path `exists`
38
+ * rejects. An empty result means every listed file is present. Paths absent
39
+ * from `files` are never reported, whatever `exists` answers for them.
40
+ *
41
+ * @param files - The manifest `files` list.
42
+ * @param exists - Presence test invoked with each entry's normalized path.
43
+ */
44
+ export function findMissingListedFiles(files, exists) {
45
+ return files.filter((entry) => !exists(normalizeManifestFilePath(entry)));
46
+ }
47
+ /**
48
+ * Read the `files` list from a manifest's JSON text without validating the
49
+ * rest of the document. Returns the string entries when the document is a
50
+ * JSON object whose `files` is an array holding at least one string, and
51
+ * `undefined` otherwise (including unparseable text).
52
+ */
53
+ export function readManifestFilesList(manifestText) {
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(manifestText);
57
+ }
58
+ catch {
59
+ return undefined;
60
+ }
61
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
62
+ return undefined;
63
+ }
64
+ const files = parsed.files;
65
+ if (!Array.isArray(files)) {
66
+ return undefined;
67
+ }
68
+ const entries = files.filter((entry) => typeof entry === "string");
69
+ return entries.length > 0 ? entries : undefined;
70
+ }
71
+ /**
72
+ * Add `path` (normalized) to the manifest text's `files` array, preserving
73
+ * every byte of the document outside that array and matching the array's
74
+ * existing one-entry-per-line or single-line formatting. Returns the edited
75
+ * text, or `undefined` when the document has no well-formed root-level
76
+ * string-array `files`, the entry is already listed, or the edit would not
77
+ * produce valid JSON.
78
+ */
79
+ export function addManifestFilesEntry(manifestText, path) {
80
+ const span = findFilesArray(manifestText);
81
+ if (span === undefined)
82
+ return undefined;
83
+ const target = normalizeManifestFilePath(path);
84
+ if (span.elements.some((element) => normalizeManifestFilePath(element.value) === target)) {
85
+ return undefined;
86
+ }
87
+ const entryText = JSON.stringify(target);
88
+ let edited;
89
+ if (span.elements.length === 0) {
90
+ edited = manifestText.slice(0, span.start + 1) + entryText + manifestText.slice(span.end);
91
+ }
92
+ else {
93
+ const first = span.elements[0];
94
+ const last = span.elements[span.elements.length - 1];
95
+ const leading = manifestText.slice(span.start + 1, first.start);
96
+ const separator = `,${leading === "" ? " " : leading}`;
97
+ edited = manifestText.slice(0, last.end) + separator + entryText + manifestText.slice(last.end);
98
+ }
99
+ return isValidJson(edited) ? edited : undefined;
100
+ }
101
+ /**
102
+ * Remove every `files` entry matching `path` (compared normalized) from the
103
+ * manifest text, preserving every byte of the document outside the `files`
104
+ * array. Returns the edited text, or `undefined` when the document has no
105
+ * well-formed root-level string-array `files`, no entry matches, or the edit
106
+ * would not produce valid JSON. Removing the last entry leaves `[]`.
107
+ */
108
+ export function removeManifestFilesEntry(manifestText, path) {
109
+ const span = findFilesArray(manifestText);
110
+ if (span === undefined)
111
+ return undefined;
112
+ const target = normalizeManifestFilePath(path);
113
+ const kept = span.elements.filter((element) => normalizeManifestFilePath(element.value) !== target);
114
+ if (kept.length === span.elements.length)
115
+ return undefined;
116
+ let content;
117
+ if (kept.length === 0) {
118
+ content = "";
119
+ }
120
+ else {
121
+ const first = span.elements[0];
122
+ const last = span.elements[span.elements.length - 1];
123
+ const leading = manifestText.slice(span.start + 1, first.start);
124
+ const trailing = manifestText.slice(last.end, span.end);
125
+ const separator = span.elements.length >= 2 ? manifestText.slice(span.elements[0].end, span.elements[1].start) : "";
126
+ content =
127
+ leading + kept.map((element) => manifestText.slice(element.start, element.end)).join(separator) + trailing;
128
+ }
129
+ const edited = manifestText.slice(0, span.start + 1) + content + manifestText.slice(span.end);
130
+ return isValidJson(edited) ? edited : undefined;
131
+ }
132
+ /**
133
+ * Locate the root-level `files` array in JSON text. Only a `files` key of the
134
+ * document's root object counts; the same key nested deeper, or the word
135
+ * inside a string value, is ignored. Returns `undefined` when the key is
136
+ * absent, its value is not an array of strings, or the text is malformed.
137
+ */
138
+ function findFilesArray(text) {
139
+ const stack = [];
140
+ let i = 0;
141
+ while (i < text.length) {
142
+ const ch = text[i];
143
+ if (ch === '"') {
144
+ const token = scanString(text, i);
145
+ if (token === undefined)
146
+ return undefined;
147
+ if (stack.length === 1 && stack[0] === "{" && token.value === "files") {
148
+ const colon = skipWhitespace(text, token.end);
149
+ if (text[colon] === ":") {
150
+ const open = skipWhitespace(text, colon + 1);
151
+ if (text[open] !== "[")
152
+ return undefined;
153
+ return scanFilesArray(text, open);
154
+ }
155
+ }
156
+ i = token.end;
157
+ continue;
158
+ }
159
+ if (ch === "{" || ch === "[") {
160
+ stack.push(ch);
161
+ }
162
+ else if (ch === "}" || ch === "]") {
163
+ stack.pop();
164
+ }
165
+ i++;
166
+ }
167
+ return undefined;
168
+ }
169
+ /** Scan the `files` array starting at its `[`; strings at element depth become elements. */
170
+ function scanFilesArray(text, open) {
171
+ const elements = [];
172
+ let depth = 1;
173
+ let i = open + 1;
174
+ while (i < text.length) {
175
+ const ch = text[i];
176
+ if (ch === '"') {
177
+ const token = scanString(text, i);
178
+ if (token === undefined)
179
+ return undefined;
180
+ if (depth !== 1)
181
+ return undefined;
182
+ elements.push(token);
183
+ i = token.end;
184
+ continue;
185
+ }
186
+ if (ch === "{" || ch === "[") {
187
+ depth++;
188
+ }
189
+ else if (ch === "}" || ch === "]") {
190
+ depth--;
191
+ if (depth === 0) {
192
+ if (ch !== "]")
193
+ return undefined;
194
+ return { start: open, end: i, elements };
195
+ }
196
+ }
197
+ else if (ch !== "," && ch !== " " && ch !== "\t" && ch !== "\n" && ch !== "\r") {
198
+ return undefined;
199
+ }
200
+ i++;
201
+ }
202
+ return undefined;
203
+ }
204
+ /** Scan one JSON string token starting at its opening quote. */
205
+ function scanString(text, start) {
206
+ let i = start + 1;
207
+ while (i < text.length) {
208
+ const ch = text[i];
209
+ if (ch === "\\") {
210
+ i += 2;
211
+ continue;
212
+ }
213
+ if (ch === '"') {
214
+ const end = i + 1;
215
+ try {
216
+ return { start, end, value: JSON.parse(text.slice(start, end)) };
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ }
222
+ i++;
223
+ }
224
+ return undefined;
225
+ }
226
+ /** Index of the first non-whitespace character at or after `from`. */
227
+ function skipWhitespace(text, from) {
228
+ let i = from;
229
+ while (i < text.length && (text[i] === " " || text[i] === "\t" || text[i] === "\n" || text[i] === "\r")) {
230
+ i++;
231
+ }
232
+ return i;
233
+ }
234
+ function isValidJson(text) {
235
+ try {
236
+ JSON.parse(text);
237
+ return true;
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ }
package/dist/node.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { buildEmbeddedExtensionFromDir, extensionSourceFiles, findMissingExtensionFiles, } from "./embedded-extension-loader.js";
2
+ export type { EmbeddedExtensionRegistration, EmbeddedExtensionsVitePlugin, } from "./embedded-extension-vite-plugin.js";
3
+ export { EMBEDDED_EXTENSIONS_MODULE_ID, embeddedExtensionsVitePlugin } from "./embedded-extension-vite-plugin.js";
4
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,2 @@
1
+ export { buildEmbeddedExtensionFromDir, extensionSourceFiles, findMissingExtensionFiles, } from "./embedded-extension-loader.js";
2
+ export { EMBEDDED_EXTENSIONS_MODULE_ID, embeddedExtensionsVitePlugin } from "./embedded-extension-vite-plugin.js";
@@ -0,0 +1,10 @@
1
+ import type { ProjectFileChange, ProjectFileSnapshot } from "@wendoo/app-host";
2
+ import type { FileSystemSnapshot } from "@wendoo/bridge-client";
3
+ import type { FileSystemNotification } from "@wendoo/bridge-protocol";
4
+ /** Convert an app-host project file snapshot into a bridge-client snapshot. */
5
+ export declare function toFileSystemSnapshot(snapshot: ProjectFileSnapshot): FileSystemSnapshot;
6
+ /** Convert an app-host project file mutation into a bridge protocol notification. */
7
+ export declare function toFileSystemNotification(change: ProjectFileChange): FileSystemNotification;
8
+ /** Convert a bridge protocol notification into an app-host project file mutation. */
9
+ export declare function toProjectFileChange(notification: FileSystemNotification): ProjectFileChange;
10
+ //# sourceMappingURL=project-file-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-file-bridge.d.ts","sourceRoot":"","sources":["../src/project-file-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAA0B,MAAM,kBAAkB,CAAC;AAEvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,kBAAkB,CAEtF;AAED,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,iBAAiB,GAAG,sBAAsB,CA+B1F;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,sBAAsB,GAAG,iBAAiB,CA+B3F"}