@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
@@ -0,0 +1,54 @@
1
+ import type { ExtensionResolutionWarning } from "./embedded-extensions.js";
2
+ /** App-data key holding a project's extension install log. */
3
+ export declare const EXTENSION_INSTALL_LOG_APP_DATA_KEY = "extension-install-log";
4
+ /**
5
+ * One entry of a project's install log. The log is a record: nothing in it
6
+ * requires action, and where an application surfaces it is the application's
7
+ * choice.
8
+ */
9
+ export type ExtensionInstallLogEvent = {
10
+ /** An origin entered the project's resolved closure. */
11
+ readonly kind: "install";
12
+ /** Unix epoch milliseconds. */
13
+ readonly at: number;
14
+ /** The origin's `<owner>/<repo>` coordinate. */
15
+ readonly origin: string;
16
+ /** The winning reference the origin resolved through. */
17
+ readonly reference: string;
18
+ /** The immutable routing specifier a fetched origin was installed at; absent for other transports. */
19
+ readonly specifier?: string;
20
+ } | {
21
+ /** An origin left the project's resolved closure. */
22
+ readonly kind: "remove";
23
+ /** Unix epoch milliseconds. */
24
+ readonly at: number;
25
+ /** The origin's `<owner>/<repo>` coordinate. */
26
+ readonly origin: string;
27
+ } | {
28
+ /** An install transaction was refused because a reference's fetch failed; the project was left unchanged. */
29
+ readonly kind: "fetch-refusal";
30
+ /** Unix epoch milliseconds. */
31
+ readonly at: number;
32
+ /** The reference whose fetch failed, as written. */
33
+ readonly reference: string;
34
+ /** Stable failure code of the fetch attempt. */
35
+ readonly code: string;
36
+ /** Human-readable failure message. */
37
+ readonly message: string;
38
+ } | {
39
+ /** Resolution selected between conflicting requesters (take-newer or reference tie-break). */
40
+ readonly kind: "resolution-warning";
41
+ /** Unix epoch milliseconds. */
42
+ readonly at: number;
43
+ /** The structured resolution warning. */
44
+ readonly warning: ExtensionResolutionWarning;
45
+ };
46
+ /**
47
+ * Parse a project's install log from its app-data text. Returns an empty log
48
+ * when the text is absent or malformed. Entries with event kinds no longer
49
+ * produced (historical logs) parse and round-trip unchanged.
50
+ */
51
+ export declare function parseExtensionInstallLog(raw: string | undefined): readonly ExtensionInstallLogEvent[];
52
+ /** Append events to a log's app-data text, returning the updated text. */
53
+ export declare function appendExtensionInstallLog(raw: string | undefined, events: readonly ExtensionInstallLogEvent[]): string;
54
+ //# sourceMappingURL=extension-install-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-install-log.d.ts","sourceRoot":"","sources":["../src/extension-install-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAE3E,8DAA8D;AAC9D,eAAO,MAAM,kCAAkC,0BAA0B,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAChC;IACE,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sGAAsG;IACtG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,GACD;IACE,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE,6GAA6G;IAC7G,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,GACD;IACE,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAE,0BAA0B,CAAC;CAC9C,CAAC;AAEN;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,wBAAwB,EAAE,CAQrG;AAED,0EAA0E;AAC1E,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,MAAM,EAAE,SAAS,wBAAwB,EAAE,GAC1C,MAAM,CAER"}
@@ -0,0 +1,22 @@
1
+ /** App-data key holding a project's extension install log. */
2
+ export const EXTENSION_INSTALL_LOG_APP_DATA_KEY = "extension-install-log";
3
+ /**
4
+ * Parse a project's install log from its app-data text. Returns an empty log
5
+ * when the text is absent or malformed. Entries with event kinds no longer
6
+ * produced (historical logs) parse and round-trip unchanged.
7
+ */
8
+ export function parseExtensionInstallLog(raw) {
9
+ if (raw === undefined)
10
+ return [];
11
+ try {
12
+ const parsed = JSON.parse(raw);
13
+ return Array.isArray(parsed) ? parsed : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ /** Append events to a log's app-data text, returning the updated text. */
20
+ export function appendExtensionInstallLog(raw, events) {
21
+ return JSON.stringify([...parseExtensionInstallLog(raw), ...events]);
22
+ }
@@ -0,0 +1,162 @@
1
+ import type { AppHostErrorCode, ExtensionCatalogMoves, ExtensionFetchError, ExtensionFetchResult, ExtensionVersionListResult } from "@wendoo/app-host";
2
+ import type { IBrainDef } from "@wendoo/core/app";
3
+ import type { WorkspaceDiagnosticEntry } from "@wendoo/ts-compiler";
4
+ import type { EmbeddedExtension, ExtensionResolutionWarning } from "./embedded-extensions.js";
5
+ import type { InstalledExtensionSnapshot, InstalledExtensionSnapshots } from "./fetched-extension-snapshots.js";
6
+ /**
7
+ * The diagnostic state of a project at one extension resolution: per-file
8
+ * compiler diagnostics keyed by workspace path, and per-brain typecheck
9
+ * problems keyed by brain key. Two states diff into an
10
+ * {@link ExtensionInstallOutcome} regardless of what mutation produced them.
11
+ */
12
+ export interface ProjectDiagnosticsState {
13
+ /** Compiler diagnostics keyed by workspace path (host files and `.libraries/` files alike). */
14
+ readonly files: ReadonlyMap<string, readonly WorkspaceDiagnosticEntry[]>;
15
+ /** Brain typecheck problem renderings keyed by brain key. */
16
+ readonly brains: ReadonlyMap<string, readonly string[]>;
17
+ }
18
+ /** How an install transaction's end state compares to its baseline. */
19
+ export type ExtensionInstallOutcomeKind = "improved" | "unchanged" | "worsened";
20
+ /** One problem named by an install outcome. */
21
+ export interface ExtensionInstallProblem {
22
+ /** Workspace path of the file, or the brain key, the problem is located at. */
23
+ readonly location: string;
24
+ /** Rendering of the problem. */
25
+ readonly description: string;
26
+ }
27
+ /** The outcome of one install transaction: the diagnostic difference against its baseline. */
28
+ export interface ExtensionInstallOutcome {
29
+ /** `worsened` when new problems appeared; `improved` when none appeared and some resolved; `unchanged` otherwise. */
30
+ readonly kind: ExtensionInstallOutcomeKind;
31
+ /** Problems present after the transaction that were absent before. */
32
+ readonly newProblems: readonly ExtensionInstallProblem[];
33
+ /** Problems present before the transaction that are gone after. */
34
+ readonly resolvedProblems: readonly ExtensionInstallProblem[];
35
+ }
36
+ /**
37
+ * Diff two diagnostic states into an install outcome. Problems are compared as
38
+ * per-location multisets of severity, code, and message; a problem that only
39
+ * moved within its file is unchanged.
40
+ */
41
+ export declare function diffProjectDiagnostics(before: ProjectDiagnosticsState, after: ProjectDiagnosticsState): ExtensionInstallOutcome;
42
+ /**
43
+ * Re-typecheck a brain against the current catalogs and collect its problems,
44
+ * one rendering per diagnostic. Every rule is marked dirty first, so the
45
+ * typecheck recomputes each rule's result.
46
+ */
47
+ export declare function typecheckBrainProblems(brain: IBrainDef): readonly string[];
48
+ /**
49
+ * A mechanics failure that refuses an install transaction outright: no
50
+ * coherent state exists to commit, and the project is left unchanged.
51
+ */
52
+ export type ExtensionInstallRefusal = {
53
+ /** A dependency's content could not be fetched. */
54
+ readonly kind: "fetch";
55
+ /** The failure, carrying its {@link ExtensionFetchError.code} and the reference it was for. */
56
+ readonly error: ExtensionFetchError;
57
+ } | {
58
+ /** The extension graph contains a dependency cycle. */
59
+ readonly kind: "cycle";
60
+ /** The origins on the cycle, in traversal order, with the first origin repeated at the end. */
61
+ readonly cycle: readonly string[];
62
+ /** Human-readable refusal message. */
63
+ readonly message: string;
64
+ } | {
65
+ /** The project's stored extension records could not be read, so no write may replace them. */
66
+ readonly kind: "store";
67
+ /** Stable code of the read failure. */
68
+ readonly code: AppHostErrorCode;
69
+ /** Human-readable refusal message. */
70
+ readonly message: string;
71
+ };
72
+ /** The result of one install transaction. */
73
+ export type ExtensionInstallReport = {
74
+ /** True: the transaction committed (improved, unchanged, and worsened outcomes all commit). */
75
+ readonly committed: true;
76
+ /** The diagnostic difference against the pre-install baseline. */
77
+ readonly outcome: ExtensionInstallOutcome;
78
+ /** Non-fatal resolution conflicts encountered, in encounter order. */
79
+ readonly warnings: readonly ExtensionResolutionWarning[];
80
+ } | {
81
+ /** False: a mechanics failure refused the transaction; nothing changed. */
82
+ readonly committed: false;
83
+ /** The refusal. */
84
+ readonly refusal: ExtensionInstallRefusal;
85
+ };
86
+ /** Result of {@link collectExtensionFetchClosure}. */
87
+ export type ExtensionFetchClosureResult = {
88
+ /** True when content is available for every reachable, non-skipped `gh:` reference. */
89
+ readonly ok: true;
90
+ /** Snapshot record per reachable `gh:` reference: reused stored records and fresh fetches. */
91
+ readonly snapshotsByReference: ReadonlyMap<string, InstalledExtensionSnapshot>;
92
+ /** Non-fatal catalog-move findings: edges skipped this walk, retried on the next load. */
93
+ readonly warnings: readonly ExtensionResolutionWarning[];
94
+ /**
95
+ * Pinned `gh:` references for coordinates whose moves resolved through a
96
+ * floating destination during this walk, keyed by lowercased coordinate.
97
+ */
98
+ readonly floatingPins: ReadonlyMap<string, string>;
99
+ } | {
100
+ /** False when a fetch of a reference as written failed; the transaction must refuse. */
101
+ readonly ok: false;
102
+ /** The failure. */
103
+ readonly error: ExtensionFetchError;
104
+ };
105
+ /**
106
+ * The stored snapshot records' pinned `gh:` references, keyed by lowercased
107
+ * coordinate. A floating catalog-move destination resolves through this map
108
+ * before any network lookup, so an already-migrated coordinate keeps its pin.
109
+ *
110
+ * @param stored - The project's stored snapshot records, keyed by coordinate.
111
+ */
112
+ export declare function floatingPinsFromSnapshots(stored: InstalledExtensionSnapshots): Map<string, string>;
113
+ /**
114
+ * Walk every reference reachable from an extensions map and ensure content is
115
+ * available for each reachable `gh:` reference: a usable stored record whose
116
+ * reference matches is reused, and anything else is fetched. Catalog moves
117
+ * apply at every dependency edge through the apply/fetch/re-apply loop of the
118
+ * shared walk; a moved edge that cannot complete this walk (fetch failure,
119
+ * unresolvable floating destination, undeterminable version) surfaces a
120
+ * stable-coded warning and is skipped, while a fetch failure for a reference
121
+ * as written fails the whole walk. A dependency set thus fetches as one unit
122
+ * before one resolution.
123
+ *
124
+ * @param options.extensions - The extensions map to walk; root references are walked as written.
125
+ * @param options.embedded - The host application's bundled embedded extensions.
126
+ * @param options.stored - The project's stored snapshot records, reused by matching reference when usable.
127
+ * @param options.refetch - References fetched fresh even when a stored record matches.
128
+ * @param options.moves - Curated catalog moves applied at every dependency edge.
129
+ * @param options.fetchSnapshot - Fetches a snapshot for a `gh:` reference.
130
+ * @param options.listVersions - Lists a repository's published versions, resolving floating move destinations.
131
+ */
132
+ export declare function collectExtensionFetchClosure(options: {
133
+ extensions: Readonly<Record<string, string>>;
134
+ embedded: readonly EmbeddedExtension[];
135
+ stored: InstalledExtensionSnapshots;
136
+ refetch?: ReadonlySet<string>;
137
+ moves?: ExtensionCatalogMoves;
138
+ fetchSnapshot: (reference: string) => Promise<ExtensionFetchResult>;
139
+ listVersions?: (owner: string, repo: string) => Promise<ExtensionVersionListResult>;
140
+ }): Promise<ExtensionFetchClosureResult>;
141
+ /**
142
+ * Report whether the moved closure of an extensions map reaches a `gh:`
143
+ * reference whose content is not already available -- neither bundled as an
144
+ * embedded extension nor held in the stored snapshot records. Runs the shared
145
+ * closure walk in dry-run mode: moves apply at every edge with the versions
146
+ * the stored content determines, a floating destination with no stored pin
147
+ * counts as missing content, and nothing is fetched. It answers whether a
148
+ * load must run a fetch transaction to heal a moved dependency whose content
149
+ * the project never persisted, or persisted in an unusable record.
150
+ *
151
+ * @param options.extensions - The extensions map to walk, with any top-level moves already applied.
152
+ * @param options.embedded - The host application's bundled embedded extensions.
153
+ * @param options.stored - The project's stored snapshot records, matched by reference.
154
+ * @param options.moves - Curated catalog moves applied at every dependency edge.
155
+ */
156
+ export declare function movedClosureHasMissingContent(options: {
157
+ extensions: Readonly<Record<string, string>>;
158
+ embedded: readonly EmbeddedExtension[];
159
+ stored: InstalledExtensionSnapshots;
160
+ moves?: ExtensionCatalogMoves;
161
+ }): Promise<boolean>;
162
+ //# sourceMappingURL=extension-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-install.d.ts","sourceRoot":"","sources":["../src/extension-install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,412 @@
1
+ import { applyCatalogMove, fileContentText, highestListedRelease, parseCatalogMoveReference, parseExtensionReference, parseProjectContentManifest, WENDOO_JSON_PATH, } from "@wendoo/app-host";
2
+ import { BrainRuleDef } from "@wendoo/core/brain/model";
3
+ import { CatalogMoveWarningCode, createCatalogMoveVersionLookup } from "./embedded-extensions.js";
4
+ import { decodeInstalledSnapshotFiles, installedSnapshotFromFetched } from "./fetched-extension-snapshots.js";
5
+ /** Render a compiler diagnostic as a location-independent problem key. */
6
+ function diagnosticProblem(entry) {
7
+ return `${entry.severity} ${entry.code}: ${entry.message}`;
8
+ }
9
+ /** Count problem occurrences per location. */
10
+ function problemCounts(state) {
11
+ const counts = new Map();
12
+ const add = (location, problem) => {
13
+ let atLocation = counts.get(location);
14
+ if (!atLocation) {
15
+ atLocation = new Map();
16
+ counts.set(location, atLocation);
17
+ }
18
+ atLocation.set(problem, (atLocation.get(problem) ?? 0) + 1);
19
+ };
20
+ for (const [path, entries] of state.files) {
21
+ for (const entry of entries) {
22
+ add(path, diagnosticProblem(entry));
23
+ }
24
+ }
25
+ for (const [key, problems] of state.brains) {
26
+ for (const problem of problems) {
27
+ add(key, problem);
28
+ }
29
+ }
30
+ return counts;
31
+ }
32
+ /** Problems whose per-location count grew from `from` to `to`, one entry per additional occurrence. */
33
+ function grownProblems(from, to) {
34
+ const grown = [];
35
+ for (const [location, toCounts] of to) {
36
+ const fromCounts = from.get(location);
37
+ for (const [problem, count] of toCounts) {
38
+ const before = fromCounts?.get(problem) ?? 0;
39
+ for (let i = before; i < count; i++) {
40
+ grown.push({ location, description: problem });
41
+ }
42
+ }
43
+ }
44
+ return grown;
45
+ }
46
+ /**
47
+ * Diff two diagnostic states into an install outcome. Problems are compared as
48
+ * per-location multisets of severity, code, and message; a problem that only
49
+ * moved within its file is unchanged.
50
+ */
51
+ export function diffProjectDiagnostics(before, after) {
52
+ const beforeCounts = problemCounts(before);
53
+ const afterCounts = problemCounts(after);
54
+ const newProblems = grownProblems(beforeCounts, afterCounts);
55
+ const resolvedProblems = grownProblems(afterCounts, beforeCounts);
56
+ const kind = newProblems.length > 0 ? "worsened" : resolvedProblems.length > 0 ? "improved" : "unchanged";
57
+ return { kind, newProblems, resolvedProblems };
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Brain typecheck problems
61
+ // ---------------------------------------------------------------------------
62
+ function collectRuleProblems(rule, problems) {
63
+ if (rule instanceof BrainRuleDef) {
64
+ const result = rule.when().typecheckResult();
65
+ if (result) {
66
+ const path = rule.getLocationPath();
67
+ result.whenParseResult.diags.forEach((diag) => {
68
+ problems.push(`${path} when parse ${diag.code}: ${diag.message}`);
69
+ });
70
+ result.doParseResult.diags.forEach((diag) => {
71
+ problems.push(`${path} do parse ${diag.code}: ${diag.message}`);
72
+ });
73
+ result.typeInfo.diags.forEach((diag) => {
74
+ problems.push(`${path} type ${diag.code}: ${diag.message}`);
75
+ });
76
+ }
77
+ }
78
+ rule.children().forEach((child) => {
79
+ collectRuleProblems(child, problems);
80
+ });
81
+ }
82
+ /**
83
+ * Re-typecheck a brain against the current catalogs and collect its problems,
84
+ * one rendering per diagnostic. Every rule is marked dirty first, so the
85
+ * typecheck recomputes each rule's result.
86
+ */
87
+ export function typecheckBrainProblems(brain) {
88
+ brain.pages().forEach((page) => {
89
+ page.children().forEach((rule) => {
90
+ rule.markDirty();
91
+ });
92
+ });
93
+ brain.typecheck();
94
+ const problems = [];
95
+ brain.pages().forEach((page) => {
96
+ page.children().forEach((rule) => {
97
+ collectRuleProblems(rule, problems);
98
+ });
99
+ });
100
+ return problems;
101
+ }
102
+ /** Read the extensions map an origin's own content manifest declares. */
103
+ function ownExtensions(files) {
104
+ const manifestContent = manifestTextOf(files);
105
+ if (manifestContent === undefined)
106
+ return {};
107
+ const parsed = parseProjectContentManifest(manifestContent);
108
+ return parsed.ok ? parsed.manifest.extensions : {};
109
+ }
110
+ /**
111
+ * Report whether a stored snapshot record holds usable content: its decoded
112
+ * files carry a parseable `wendoo.json`. Every fetched snapshot is written
113
+ * with one, so a record without it is corrupt and its content must be treated
114
+ * as missing.
115
+ */
116
+ function isUsableSnapshotRecord(record) {
117
+ const manifestContent = manifestTextOf(decodeInstalledSnapshotFiles(record));
118
+ if (manifestContent === undefined)
119
+ return false;
120
+ return parseProjectContentManifest(manifestContent).ok;
121
+ }
122
+ /** The text of an origin's own `wendoo.json`, at either path spelling. */
123
+ function manifestTextOf(files) {
124
+ const entry = files.get(`/${WENDOO_JSON_PATH}`) ?? files.get(WENDOO_JSON_PATH);
125
+ return entry === undefined ? undefined : fileContentText(entry);
126
+ }
127
+ /** Build the leading-slash file map of an embedded extension. */
128
+ function embeddedContent(extension) {
129
+ const files = new Map();
130
+ for (const file of extension.files) {
131
+ files.set(file.path.startsWith("/") ? file.path : `/${file.path}`, file.content);
132
+ }
133
+ return files;
134
+ }
135
+ /**
136
+ * The stored snapshot records' pinned `gh:` references, keyed by lowercased
137
+ * coordinate. A floating catalog-move destination resolves through this map
138
+ * before any network lookup, so an already-migrated coordinate keeps its pin.
139
+ *
140
+ * @param stored - The project's stored snapshot records, keyed by coordinate.
141
+ */
142
+ export function floatingPinsFromSnapshots(stored) {
143
+ const pins = new Map();
144
+ for (const [coordinate, record] of Object.entries(stored)) {
145
+ const parsed = parseExtensionReference(record.reference);
146
+ if (parsed?.transport === "gh" && parsed.routing.kind === "pin") {
147
+ pins.set(coordinate.toLowerCase(), record.reference);
148
+ }
149
+ }
150
+ return pins;
151
+ }
152
+ /**
153
+ * Walk every reference reachable from an extensions map -- through embedded
154
+ * extensions' own manifests and through fetched snapshots' manifests alike --
155
+ * applying catalog moves at every dependency edge. The walk iterates
156
+ * apply/fetch/re-apply: content fetched during a pass can determine versions
157
+ * that let a `packageVersion` selector capture on the next pass, until no edge
158
+ * rewrite changes. Root references are walked as written.
159
+ *
160
+ * A stored record is reused only when it is usable (its files carry a
161
+ * parseable `wendoo.json`); an unusable record counts as missing content in
162
+ * both modes.
163
+ *
164
+ * In fetch mode (a `fetchSnapshot` callback present), content missing for a
165
+ * reference as written fails the walk; content missing for a move-rewritten
166
+ * edge, an unresolvable floating destination, and a permanently undeterminable
167
+ * version each surface a stable-coded `catalog-move-failed` warning, skipping
168
+ * that edge for this walk. In dry-run mode nothing is fetched and the walk
169
+ * reports whether any reachable content is missing.
170
+ */
171
+ async function walkExtensionClosure(options) {
172
+ const moves = options.moves ?? {};
173
+ const dryRun = options.fetchSnapshot === undefined;
174
+ const embeddedByCoordinate = new Map(options.embedded.map((extension) => [extension.canonicalOrigin, extension]));
175
+ const storedByReference = new Map();
176
+ for (const record of Object.values(options.stored)) {
177
+ if (options.refetch?.has(record.reference))
178
+ continue;
179
+ // An unusable record is treated as missing content: a dry run reports it
180
+ // and a fetch walk refetches and replaces it.
181
+ if (!isUsableSnapshotRecord(record))
182
+ continue;
183
+ storedByReference.set(record.reference, record);
184
+ }
185
+ const fetchedRecords = new Map();
186
+ const floatingPins = floatingPinsFromSnapshots(options.stored);
187
+ const unresolvableFloating = new Set();
188
+ const versionLookup = createCatalogMoveVersionLookup({
189
+ embedded: options.embedded,
190
+ contentForReference: (reference) => {
191
+ const record = fetchedRecords.get(reference) ?? storedByReference.get(reference);
192
+ return record !== undefined ? decodeInstalledSnapshotFiles(record) : undefined;
193
+ },
194
+ });
195
+ const warnings = [];
196
+ const warned = new Set();
197
+ const warn = (reference, code, message) => {
198
+ const key = `${reference} ${code}`;
199
+ if (warned.has(key))
200
+ return;
201
+ warned.add(key);
202
+ warnings.push({
203
+ kind: "catalog-move-failed",
204
+ origin: parseCatalogMoveReference(reference)?.coordinate ?? reference,
205
+ reference,
206
+ code,
207
+ message,
208
+ });
209
+ };
210
+ /** Resolve a floating reference to a pinned one: a known pin, else the highest stable published version. */
211
+ const resolveFloating = async (coordinate) => {
212
+ const key = coordinate.toLowerCase();
213
+ const known = floatingPins.get(key);
214
+ if (known !== undefined) {
215
+ return known;
216
+ }
217
+ if (dryRun || options.listVersions === undefined || unresolvableFloating.has(key)) {
218
+ return undefined;
219
+ }
220
+ const slash = coordinate.indexOf("/");
221
+ const listed = await options.listVersions(coordinate.slice(0, slash), coordinate.slice(slash + 1));
222
+ const version = listed.ok ? highestListedRelease(listed.versions) : undefined;
223
+ if (version === undefined) {
224
+ unresolvableFloating.add(key);
225
+ return undefined;
226
+ }
227
+ const pin = `gh:${coordinate}@${version}`;
228
+ floatingPins.set(key, pin);
229
+ return pin;
230
+ };
231
+ /**
232
+ * Redirect one declared edge through the moves with the versions known so
233
+ * far. A floating result resolves to its pin, and the moves re-apply from
234
+ * the pin, until the reference is concrete and uncaptured. Returns the walk
235
+ * item, or `undefined` when the edge is skipped this walk. `pendingRefs`
236
+ * collects declared references whose version-range evaluation is still
237
+ * pending, and `outputs` records the edge's final reference for the fixpoint
238
+ * comparison.
239
+ */
240
+ const redirectEdge = async (declaredReference, pendingRefs, outputs) => {
241
+ let reference = declaredReference;
242
+ let movedByCatalog = false;
243
+ const seenPins = new Set();
244
+ while (true) {
245
+ const applied = applyCatalogMove(reference, moves, versionLookup);
246
+ if (!applied.ok) {
247
+ warn(declaredReference, applied.code, applied.message);
248
+ outputs.set(declaredReference, declaredReference);
249
+ return { reference: declaredReference, movedByCatalog: false };
250
+ }
251
+ if (applied.moved) {
252
+ reference = applied.reference;
253
+ movedByCatalog = true;
254
+ }
255
+ const parts = parseCatalogMoveReference(reference);
256
+ if (!parts?.floating) {
257
+ // Pending only counts at the edge's final concrete reference: a range
258
+ // blocked at an intermediate floating hop is resolved by the pin
259
+ // substitution and re-application.
260
+ if (applied.pendingVersion) {
261
+ pendingRefs.add(declaredReference);
262
+ }
263
+ outputs.set(declaredReference, reference);
264
+ return { reference, movedByCatalog };
265
+ }
266
+ const pin = await resolveFloating(parts.coordinate);
267
+ if (pin === undefined) {
268
+ outputs.set(declaredReference, reference);
269
+ if (dryRun) {
270
+ // A floating destination with no stored pin needs a fetch
271
+ // transaction to resolve and persist one.
272
+ missingContent = true;
273
+ }
274
+ else {
275
+ warn(declaredReference, CatalogMoveWarningCode.FLOATING_UNRESOLVED, `Catalog move for "${declaredReference}" resolves to the floating "${reference}" and no stable ` +
276
+ "published version could be pinned; the move is skipped this load.");
277
+ }
278
+ return undefined;
279
+ }
280
+ if (seenPins.has(pin)) {
281
+ warn(declaredReference, CatalogMoveWarningCode.CYCLE, `Catalog moves for "${declaredReference}" revisited the pinned "${pin}".`);
282
+ outputs.set(declaredReference, declaredReference);
283
+ return { reference: declaredReference, movedByCatalog: false };
284
+ }
285
+ seenPins.add(pin);
286
+ reference = pin;
287
+ movedByCatalog = true;
288
+ }
289
+ };
290
+ let previousOutputs;
291
+ let missingContent = false;
292
+ while (true) {
293
+ const outputs = new Map();
294
+ const pendingRefs = new Set();
295
+ const snapshotsByReference = new Map();
296
+ missingContent = false;
297
+ const visited = new Set();
298
+ const queue = Object.values(options.extensions).map((reference) => ({
299
+ reference,
300
+ movedByCatalog: false,
301
+ }));
302
+ while (queue.length > 0) {
303
+ const item = queue.shift();
304
+ if (visited.has(item.reference))
305
+ continue;
306
+ visited.add(item.reference);
307
+ const parsed = parseExtensionReference(item.reference);
308
+ if (parsed === undefined)
309
+ continue;
310
+ let files;
311
+ if (parsed.transport === "embedded") {
312
+ const extension = embeddedByCoordinate.get(parsed.coordinate);
313
+ if (extension === undefined)
314
+ continue;
315
+ files = embeddedContent(extension);
316
+ }
317
+ else if (parsed.transport === "gh") {
318
+ let record = fetchedRecords.get(item.reference) ?? storedByReference.get(item.reference);
319
+ if (record === undefined) {
320
+ if (dryRun) {
321
+ missingContent = true;
322
+ continue;
323
+ }
324
+ const fetched = await options.fetchSnapshot(item.reference);
325
+ if (!fetched.ok) {
326
+ if (item.movedByCatalog) {
327
+ warn(item.reference, CatalogMoveWarningCode.FETCH_FAILED, `Moved content "${item.reference}" could not be fetched (${fetched.error.code}); ` +
328
+ "the move is skipped this load.");
329
+ continue;
330
+ }
331
+ return { ok: false, error: fetched.error };
332
+ }
333
+ record = installedSnapshotFromFetched(fetched.snapshot);
334
+ fetchedRecords.set(item.reference, record);
335
+ }
336
+ snapshotsByReference.set(item.reference, record);
337
+ files = decodeInstalledSnapshotFiles(record);
338
+ }
339
+ if (files === undefined)
340
+ continue;
341
+ for (const declaredReference of Object.values(ownExtensions(files))) {
342
+ const child = await redirectEdge(declaredReference, pendingRefs, outputs);
343
+ if (child !== undefined) {
344
+ queue.push(child);
345
+ }
346
+ }
347
+ }
348
+ const stable = previousOutputs !== undefined &&
349
+ outputs.size === previousOutputs.size &&
350
+ [...outputs].every(([key, value]) => previousOutputs?.get(key) === value);
351
+ if (stable) {
352
+ if (!dryRun) {
353
+ for (const declaredReference of pendingRefs) {
354
+ warn(declaredReference, CatalogMoveWarningCode.VERSION_UNKNOWN, `The version of "${declaredReference}" could not be determined, so its version-scoped catalog move ` +
355
+ "cannot be evaluated; the move is skipped this load.");
356
+ }
357
+ }
358
+ return { ok: true, snapshotsByReference, warnings, floatingPins, missingContent };
359
+ }
360
+ previousOutputs = outputs;
361
+ }
362
+ }
363
+ /**
364
+ * Walk every reference reachable from an extensions map and ensure content is
365
+ * available for each reachable `gh:` reference: a usable stored record whose
366
+ * reference matches is reused, and anything else is fetched. Catalog moves
367
+ * apply at every dependency edge through the apply/fetch/re-apply loop of the
368
+ * shared walk; a moved edge that cannot complete this walk (fetch failure,
369
+ * unresolvable floating destination, undeterminable version) surfaces a
370
+ * stable-coded warning and is skipped, while a fetch failure for a reference
371
+ * as written fails the whole walk. A dependency set thus fetches as one unit
372
+ * before one resolution.
373
+ *
374
+ * @param options.extensions - The extensions map to walk; root references are walked as written.
375
+ * @param options.embedded - The host application's bundled embedded extensions.
376
+ * @param options.stored - The project's stored snapshot records, reused by matching reference when usable.
377
+ * @param options.refetch - References fetched fresh even when a stored record matches.
378
+ * @param options.moves - Curated catalog moves applied at every dependency edge.
379
+ * @param options.fetchSnapshot - Fetches a snapshot for a `gh:` reference.
380
+ * @param options.listVersions - Lists a repository's published versions, resolving floating move destinations.
381
+ */
382
+ export async function collectExtensionFetchClosure(options) {
383
+ const outcome = await walkExtensionClosure(options);
384
+ if (!outcome.ok) {
385
+ return outcome;
386
+ }
387
+ return {
388
+ ok: true,
389
+ snapshotsByReference: outcome.snapshotsByReference,
390
+ warnings: outcome.warnings,
391
+ floatingPins: outcome.floatingPins,
392
+ };
393
+ }
394
+ /**
395
+ * Report whether the moved closure of an extensions map reaches a `gh:`
396
+ * reference whose content is not already available -- neither bundled as an
397
+ * embedded extension nor held in the stored snapshot records. Runs the shared
398
+ * closure walk in dry-run mode: moves apply at every edge with the versions
399
+ * the stored content determines, a floating destination with no stored pin
400
+ * counts as missing content, and nothing is fetched. It answers whether a
401
+ * load must run a fetch transaction to heal a moved dependency whose content
402
+ * the project never persisted, or persisted in an unusable record.
403
+ *
404
+ * @param options.extensions - The extensions map to walk, with any top-level moves already applied.
405
+ * @param options.embedded - The host application's bundled embedded extensions.
406
+ * @param options.stored - The project's stored snapshot records, matched by reference.
407
+ * @param options.moves - Curated catalog moves applied at every dependency edge.
408
+ */
409
+ export async function movedClosureHasMissingContent(options) {
410
+ const outcome = await walkExtensionClosure(options);
411
+ return outcome.ok ? outcome.missingContent : true;
412
+ }