@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,400 @@
|
|
|
1
|
+
import { AppHostErrorCode, appHostError, applyProjectFileChangeToSnapshot, fileContentEquals, fileContentToWire, parseProjectContentManifest, serializeProjectContentManifest, WENDOO_JSON_PATH, } from "@wendoo/app-host";
|
|
2
|
+
import { INSTALLED_EXTENSIONS_APP_DATA_KEY, serializeInstalledExtensionSnapshots, } from "./fetched-extension-snapshots.js";
|
|
3
|
+
import { toFileSystemNotification, toProjectFileChange } from "./project-file-bridge.js";
|
|
4
|
+
/** Project collection id of the single collection a workspace-folder store exposes. */
|
|
5
|
+
export const WORKSPACE_FOLDER_PROJECT_COLLECTION_ID = "workspace-folder";
|
|
6
|
+
/** Stable identifiers for workspace-folder store errors. */
|
|
7
|
+
export const WorkspaceFolderStoreErrorCode = {
|
|
8
|
+
/** The host-provided `wendoo.json` failed content-manifest validation. */
|
|
9
|
+
INVALID_MANIFEST: "WORKSPACE_FOLDER_INVALID_MANIFEST",
|
|
10
|
+
/** A multi-project or collection-management operation was invoked; a workspace folder holds exactly one project. */
|
|
11
|
+
UNSUPPORTED_OPERATION: "WORKSPACE_FOLDER_UNSUPPORTED_OPERATION",
|
|
12
|
+
};
|
|
13
|
+
/** Error thrown by {@link WorkspaceFolderProjectStore}. */
|
|
14
|
+
export class WorkspaceFolderStoreError extends Error {
|
|
15
|
+
/** Stable machine-readable error code. */
|
|
16
|
+
code;
|
|
17
|
+
constructor(code, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "WorkspaceFolderStoreError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** App-data key whose entries are stored as the manifest's `brains` chunk. */
|
|
24
|
+
const BRAINS_APP_DATA_KEY = "brains";
|
|
25
|
+
/** App-chunk map key inside the manifest's pass-through section. */
|
|
26
|
+
const APP_CHUNKS_EXTRA_KEY = "app";
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function parseRecord(raw) {
|
|
31
|
+
if (!raw) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A {@link ProjectStore} over a host-owned workspace folder: the folder is the
|
|
44
|
+
* single project of the store's single collection. File writes are
|
|
45
|
+
* change-granular; the project manifest and its app data are stored inside the
|
|
46
|
+
* folder's `wendoo.json` (brains and app chunks in the pass-through
|
|
47
|
+
* section, unknown fields preserved). Multi-project and collection-management
|
|
48
|
+
* operations throw {@link WorkspaceFolderStoreError}.
|
|
49
|
+
*/
|
|
50
|
+
export class WorkspaceFolderProjectStore {
|
|
51
|
+
keyPrefix;
|
|
52
|
+
rpc;
|
|
53
|
+
projectId;
|
|
54
|
+
appName;
|
|
55
|
+
appDataCodec;
|
|
56
|
+
collection;
|
|
57
|
+
base;
|
|
58
|
+
manifest;
|
|
59
|
+
appData = new Map();
|
|
60
|
+
chunkAppDataKeys = new Set();
|
|
61
|
+
lastManifestContent;
|
|
62
|
+
mirror = new Map();
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.keyPrefix = options.appName;
|
|
65
|
+
this.rpc = options.rpc;
|
|
66
|
+
this.projectId = options.projectId;
|
|
67
|
+
this.appName = options.appName;
|
|
68
|
+
this.appDataCodec = options.appDataCodec;
|
|
69
|
+
this.lastManifestContent = options.manifestContent;
|
|
70
|
+
const parsed = parseProjectContentManifest(options.manifestContent);
|
|
71
|
+
if (!parsed.ok) {
|
|
72
|
+
throw new WorkspaceFolderStoreError(WorkspaceFolderStoreErrorCode.INVALID_MANIFEST, `wendoo.json is not a valid project manifest: ${parsed.errors[0]?.message ?? "unknown error"}`);
|
|
73
|
+
}
|
|
74
|
+
this.base = parsed.manifest;
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
this.collection = {
|
|
77
|
+
projectCollectionId: WORKSPACE_FOLDER_PROJECT_COLLECTION_ID,
|
|
78
|
+
name: parsed.manifest.name,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
};
|
|
82
|
+
this.manifest = {
|
|
83
|
+
id: this.projectId,
|
|
84
|
+
projectCollectionId: WORKSPACE_FOLDER_PROJECT_COLLECTION_ID,
|
|
85
|
+
name: parsed.manifest.name,
|
|
86
|
+
version: parsed.manifest.version,
|
|
87
|
+
description: parsed.manifest.description ?? "",
|
|
88
|
+
...(parsed.manifest.thumbnailUrl !== undefined ? { thumbnailUrl: parsed.manifest.thumbnailUrl } : {}),
|
|
89
|
+
...(Object.keys(parsed.manifest.extensions).length > 0 ? { extensions: parsed.manifest.extensions } : {}),
|
|
90
|
+
...(parsed.manifest.targets !== undefined ? { targets: parsed.manifest.targets } : {}),
|
|
91
|
+
createdAt: now,
|
|
92
|
+
updatedAt: now,
|
|
93
|
+
};
|
|
94
|
+
this.seedAppDataFromBase();
|
|
95
|
+
if (options.installedExtensionSnapshots && Object.keys(options.installedExtensionSnapshots).length > 0) {
|
|
96
|
+
this.appData.set(INSTALLED_EXTENSIONS_APP_DATA_KEY, serializeInstalledExtensionSnapshots(options.installedExtensionSnapshots));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Absorb a change observed on disk outside the app: a `wendoo.json`
|
|
101
|
+
* write replaces the store's manifest and app-data state; any other change
|
|
102
|
+
* updates the store's view of the folder. Returns the change in project
|
|
103
|
+
* file form for the caller to apply to the live project file system.
|
|
104
|
+
*/
|
|
105
|
+
absorbExternalChange(change) {
|
|
106
|
+
if (change.action === "write" && change.path === WENDOO_JSON_PATH) {
|
|
107
|
+
this.lastManifestContent = change.content;
|
|
108
|
+
const parsed = parseProjectContentManifest(change.content);
|
|
109
|
+
if (parsed.ok) {
|
|
110
|
+
this.base = parsed.manifest;
|
|
111
|
+
this.manifest = {
|
|
112
|
+
id: this.manifest.id,
|
|
113
|
+
projectCollectionId: this.manifest.projectCollectionId,
|
|
114
|
+
name: parsed.manifest.name,
|
|
115
|
+
version: parsed.manifest.version,
|
|
116
|
+
description: parsed.manifest.description ?? "",
|
|
117
|
+
...(parsed.manifest.thumbnailUrl !== undefined ? { thumbnailUrl: parsed.manifest.thumbnailUrl } : {}),
|
|
118
|
+
...(Object.keys(parsed.manifest.extensions).length > 0 ? { extensions: parsed.manifest.extensions } : {}),
|
|
119
|
+
...(parsed.manifest.targets !== undefined ? { targets: parsed.manifest.targets } : {}),
|
|
120
|
+
createdAt: this.manifest.createdAt,
|
|
121
|
+
updatedAt: Date.now(),
|
|
122
|
+
};
|
|
123
|
+
this.seedAppDataFromBase();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
applyProjectFileChangeToSnapshot(this.mirror, toProjectFileChange(change));
|
|
128
|
+
}
|
|
129
|
+
return toProjectFileChange(change);
|
|
130
|
+
}
|
|
131
|
+
// -- Project collections (single fixed collection) --
|
|
132
|
+
async listProjectCollections() {
|
|
133
|
+
return [this.collection];
|
|
134
|
+
}
|
|
135
|
+
async getProjectCollection(projectCollectionId) {
|
|
136
|
+
return projectCollectionId === this.collection.projectCollectionId ? this.collection : undefined;
|
|
137
|
+
}
|
|
138
|
+
async createProjectCollection() {
|
|
139
|
+
throw this.unsupported("createProjectCollection");
|
|
140
|
+
}
|
|
141
|
+
async updateProjectCollection() {
|
|
142
|
+
throw this.unsupported("updateProjectCollection");
|
|
143
|
+
}
|
|
144
|
+
async deleteProjectCollection() {
|
|
145
|
+
throw this.unsupported("deleteProjectCollection");
|
|
146
|
+
}
|
|
147
|
+
async ensureDefaultProjectCollection() {
|
|
148
|
+
return this.collection;
|
|
149
|
+
}
|
|
150
|
+
// -- Projects (single fixed project) --
|
|
151
|
+
async listProjects(projectCollectionId) {
|
|
152
|
+
return projectCollectionId === this.collection.projectCollectionId ? [{ ...this.manifest }] : [];
|
|
153
|
+
}
|
|
154
|
+
async countProjectsByCollection() {
|
|
155
|
+
return new Map([[this.collection.projectCollectionId, 1]]);
|
|
156
|
+
}
|
|
157
|
+
async getProject(id) {
|
|
158
|
+
return id === this.projectId ? { ...this.manifest } : undefined;
|
|
159
|
+
}
|
|
160
|
+
async createProject() {
|
|
161
|
+
throw this.unsupported("createProject");
|
|
162
|
+
}
|
|
163
|
+
async deleteProject() {
|
|
164
|
+
throw this.unsupported("deleteProject");
|
|
165
|
+
}
|
|
166
|
+
async updateProject(id, updates) {
|
|
167
|
+
this.requireProject(id);
|
|
168
|
+
this.manifest = { ...this.manifest, ...updates, updatedAt: Date.now() };
|
|
169
|
+
await this.writeManifestIfChanged();
|
|
170
|
+
}
|
|
171
|
+
async duplicateProject() {
|
|
172
|
+
throw this.unsupported("duplicateProject");
|
|
173
|
+
}
|
|
174
|
+
async copyProjectToCollection() {
|
|
175
|
+
throw this.unsupported("copyProjectToCollection");
|
|
176
|
+
}
|
|
177
|
+
// -- Project files --
|
|
178
|
+
async loadProjectFiles(id) {
|
|
179
|
+
this.requireProject(id);
|
|
180
|
+
const snapshot = await this.rpc.loadFiles();
|
|
181
|
+
snapshot.delete(WENDOO_JSON_PATH);
|
|
182
|
+
this.mirror = new Map(snapshot);
|
|
183
|
+
return new Map(snapshot);
|
|
184
|
+
}
|
|
185
|
+
async saveProjectFiles(id, snapshot) {
|
|
186
|
+
this.requireProject(id);
|
|
187
|
+
await this.writeSnapshotDiff(snapshot);
|
|
188
|
+
}
|
|
189
|
+
async applyProjectFileChanges(id, changes) {
|
|
190
|
+
this.requireProject(id);
|
|
191
|
+
for (const change of changes) {
|
|
192
|
+
if (touchesManifestFile(change)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (change.action === "import") {
|
|
196
|
+
const target = new Map(change.entries);
|
|
197
|
+
await this.writeSnapshotDiff(target);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
await this.rpc.sendChange(toFileSystemNotification(change));
|
|
201
|
+
applyProjectFileChangeToSnapshot(this.mirror, change);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// -- App data (carried in the manifest's pass-through section) --
|
|
205
|
+
async loadAppData(id, key) {
|
|
206
|
+
this.requireProject(id);
|
|
207
|
+
return this.appData.get(key);
|
|
208
|
+
}
|
|
209
|
+
async saveAppData(id, key, data) {
|
|
210
|
+
this.requireProject(id);
|
|
211
|
+
this.appData.set(key, data);
|
|
212
|
+
await this.writeManifestIfChanged();
|
|
213
|
+
}
|
|
214
|
+
async deleteAppData(id, key) {
|
|
215
|
+
this.requireProject(id);
|
|
216
|
+
this.appData.delete(key);
|
|
217
|
+
await this.writeManifestIfChanged();
|
|
218
|
+
}
|
|
219
|
+
// -- Tab session (fixed: the single folder project is always active) --
|
|
220
|
+
getProjectSession() {
|
|
221
|
+
return {
|
|
222
|
+
projectCollectionId: this.collection.projectCollectionId,
|
|
223
|
+
activeProjectId: this.projectId,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
setProjectSession() {
|
|
227
|
+
// The folder project is the session; there is nothing to persist.
|
|
228
|
+
}
|
|
229
|
+
getLastOpenedProject() {
|
|
230
|
+
return {
|
|
231
|
+
projectCollectionId: this.collection.projectCollectionId,
|
|
232
|
+
projectId: this.projectId,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** Ignores the write; this store's last opened project is always its folder project. */
|
|
236
|
+
setLastOpenedProject() { }
|
|
237
|
+
// -- Internals --
|
|
238
|
+
unsupported(operation) {
|
|
239
|
+
return new WorkspaceFolderStoreError(WorkspaceFolderStoreErrorCode.UNSUPPORTED_OPERATION, `${operation} is not available for a workspace-folder project`);
|
|
240
|
+
}
|
|
241
|
+
requireProject(id) {
|
|
242
|
+
if (id !== this.projectId) {
|
|
243
|
+
throw appHostError(AppHostErrorCode.PROJECT_NOT_FOUND, `Project not found: ${id}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
seedAppDataFromBase() {
|
|
247
|
+
for (const key of this.chunkAppDataKeys) {
|
|
248
|
+
this.appData.delete(key);
|
|
249
|
+
}
|
|
250
|
+
const brains = this.base.extras?.brains;
|
|
251
|
+
if (isRecord(brains) && Object.keys(brains).length > 0) {
|
|
252
|
+
this.appData.set(BRAINS_APP_DATA_KEY, JSON.stringify(brains));
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
this.appData.delete(BRAINS_APP_DATA_KEY);
|
|
256
|
+
}
|
|
257
|
+
if (!this.appDataCodec) {
|
|
258
|
+
this.chunkAppDataKeys = new Set();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const appChunks = this.base.extras?.[APP_CHUNKS_EXTRA_KEY];
|
|
262
|
+
const ownChunk = isRecord(appChunks) ? appChunks[this.appName] : undefined;
|
|
263
|
+
const entries = ownChunk !== undefined ? this.appDataCodec.appDataFromChunk(ownChunk) : {};
|
|
264
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
265
|
+
this.appData.set(key, value);
|
|
266
|
+
}
|
|
267
|
+
this.chunkAppDataKeys = new Set(Object.keys(entries));
|
|
268
|
+
}
|
|
269
|
+
composeManifestContent() {
|
|
270
|
+
const extras = { ...(this.base.extras ?? {}) };
|
|
271
|
+
const brains = parseRecord(this.appData.get(BRAINS_APP_DATA_KEY));
|
|
272
|
+
if (brains && Object.keys(brains).length > 0) {
|
|
273
|
+
extras.brains = brains;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
delete extras.brains;
|
|
277
|
+
}
|
|
278
|
+
const appChunks = isRecord(extras[APP_CHUNKS_EXTRA_KEY])
|
|
279
|
+
? { ...extras[APP_CHUNKS_EXTRA_KEY] }
|
|
280
|
+
: {};
|
|
281
|
+
const ownChunk = this.appDataCodec?.chunkFromAppData(this.appData);
|
|
282
|
+
if (ownChunk !== undefined) {
|
|
283
|
+
appChunks[this.appName] = ownChunk;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
delete appChunks[this.appName];
|
|
287
|
+
}
|
|
288
|
+
if (Object.keys(appChunks).length > 0) {
|
|
289
|
+
extras[APP_CHUNKS_EXTRA_KEY] = appChunks;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
delete extras[APP_CHUNKS_EXTRA_KEY];
|
|
293
|
+
}
|
|
294
|
+
const content = {
|
|
295
|
+
name: this.manifest.name,
|
|
296
|
+
version: this.manifest.version,
|
|
297
|
+
...(this.base.identity !== undefined ? { identity: this.base.identity } : {}),
|
|
298
|
+
description: this.manifest.description,
|
|
299
|
+
...(this.manifest.thumbnailUrl !== undefined ? { thumbnailUrl: this.manifest.thumbnailUrl } : {}),
|
|
300
|
+
extensions: this.manifest.extensions ?? {},
|
|
301
|
+
...(this.base.files !== undefined ? { files: this.base.files } : {}),
|
|
302
|
+
...(this.base.ambient !== undefined ? { ambient: this.base.ambient } : {}),
|
|
303
|
+
...(this.base.targets !== undefined ? { targets: this.base.targets } : {}),
|
|
304
|
+
...(Object.keys(extras).length > 0 ? { extras } : {}),
|
|
305
|
+
};
|
|
306
|
+
return serializeProjectContentManifest(content);
|
|
307
|
+
}
|
|
308
|
+
async writeManifestIfChanged() {
|
|
309
|
+
const content = this.composeManifestContent();
|
|
310
|
+
if (content === this.lastManifestContent) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
await this.rpc.writeManifest(content);
|
|
314
|
+
this.lastManifestContent = content;
|
|
315
|
+
}
|
|
316
|
+
async writeSnapshotDiff(target) {
|
|
317
|
+
const changes = diffSnapshotChanges(this.mirror, target);
|
|
318
|
+
for (const change of changes) {
|
|
319
|
+
await this.rpc.sendChange(change);
|
|
320
|
+
}
|
|
321
|
+
const next = new Map();
|
|
322
|
+
for (const [path, entry] of target) {
|
|
323
|
+
if (path !== WENDOO_JSON_PATH) {
|
|
324
|
+
next.set(path, entry);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.mirror = next;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function touchesManifestFile(change) {
|
|
331
|
+
switch (change.action) {
|
|
332
|
+
case "write":
|
|
333
|
+
case "delete":
|
|
334
|
+
case "mkdir":
|
|
335
|
+
case "rmdir":
|
|
336
|
+
return change.path === WENDOO_JSON_PATH;
|
|
337
|
+
case "rename":
|
|
338
|
+
return change.oldPath === WENDOO_JSON_PATH || change.newPath === WENDOO_JSON_PATH;
|
|
339
|
+
case "import":
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Compute the change-granular writes that bring `mirror` (the store's view of
|
|
345
|
+
* the folder) to `target`. The `wendoo.json` entry is ignored on both
|
|
346
|
+
* sides; removals under a removed directory are covered by its `rmdir`.
|
|
347
|
+
*/
|
|
348
|
+
function diffSnapshotChanges(mirror, target) {
|
|
349
|
+
const changes = [];
|
|
350
|
+
for (const [path, entry] of target) {
|
|
351
|
+
if (path === WENDOO_JSON_PATH) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const current = mirror.get(path);
|
|
355
|
+
if (entry.kind === "directory") {
|
|
356
|
+
if (!current) {
|
|
357
|
+
changes.push({ action: "mkdir", path });
|
|
358
|
+
}
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (!current || current.kind !== "file" || !fileContentEquals(current.content, entry.content)) {
|
|
362
|
+
changes.push({
|
|
363
|
+
action: "write",
|
|
364
|
+
path,
|
|
365
|
+
...fileContentToWire(entry.content),
|
|
366
|
+
newEtag: entry.etag,
|
|
367
|
+
...(current?.kind === "file" ? { expectedEtag: current.etag } : {}),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const removedPaths = [...mirror.keys()].filter((path) => path !== WENDOO_JSON_PATH && !target.has(path)).sort();
|
|
372
|
+
const removedDirectories = [];
|
|
373
|
+
for (const path of removedPaths) {
|
|
374
|
+
if (removedDirectories.some((directory) => path.startsWith(`${directory}/`))) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const entry = mirror.get(path);
|
|
378
|
+
if (entry?.kind === "directory") {
|
|
379
|
+
// A directory absent from the target as an entry may still parent
|
|
380
|
+
// target files; only an empty subtree is removed.
|
|
381
|
+
const prefix = `${path}/`;
|
|
382
|
+
let hasTargetDescendant = false;
|
|
383
|
+
for (const targetPath of target.keys()) {
|
|
384
|
+
if (targetPath.startsWith(prefix)) {
|
|
385
|
+
hasTargetDescendant = true;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (hasTargetDescendant) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
changes.push({ action: "rmdir", path });
|
|
393
|
+
removedDirectories.push(path);
|
|
394
|
+
}
|
|
395
|
+
else if (entry) {
|
|
396
|
+
changes.push({ action: "delete", path, expectedEtag: entry.etag });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return changes;
|
|
400
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wendoo/bridge-app",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "App-side client for the Wendoo bridge",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "wendoo-lang",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/wendoo-lang/wendoo-lang.git",
|
|
11
|
+
"directory": "packages/bridge-app"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/wendoo-lang/wendoo-lang/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/wendoo-lang/wendoo-lang/tree/main/packages/bridge-app#readme",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"wendoo",
|
|
19
|
+
"visual-programming",
|
|
20
|
+
"tile-based",
|
|
21
|
+
"typescript",
|
|
22
|
+
"vscode",
|
|
23
|
+
"language",
|
|
24
|
+
"education",
|
|
25
|
+
"stem",
|
|
26
|
+
"coding",
|
|
27
|
+
"game-development",
|
|
28
|
+
"creative-coding",
|
|
29
|
+
"kodu",
|
|
30
|
+
"project-spark"
|
|
31
|
+
],
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"main": "dist/index.js",
|
|
43
|
+
"types": "dist/index.d.ts",
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"browser": "./dist/index.js",
|
|
48
|
+
"import": "./dist/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./compilation": {
|
|
51
|
+
"types": "./dist/compilation.d.ts",
|
|
52
|
+
"browser": "./dist/compilation.js",
|
|
53
|
+
"import": "./dist/compilation.js"
|
|
54
|
+
},
|
|
55
|
+
"./manifest-files": {
|
|
56
|
+
"types": "./dist/manifest-files.d.ts",
|
|
57
|
+
"browser": "./dist/manifest-files.js",
|
|
58
|
+
"import": "./dist/manifest-files.js"
|
|
59
|
+
},
|
|
60
|
+
"./node": {
|
|
61
|
+
"types": "./dist/node.d.ts",
|
|
62
|
+
"import": "./dist/node.js"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"wendooBuild": {
|
|
66
|
+
"script": "build:prod",
|
|
67
|
+
"outputs": [
|
|
68
|
+
"dist"
|
|
69
|
+
],
|
|
70
|
+
"postProcessed": false
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsc --build",
|
|
74
|
+
"build:prod": "tsc --build tsconfig.prod.json",
|
|
75
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo tsconfig.prod.tsbuildinfo",
|
|
76
|
+
"watch": "tsc --build --watch",
|
|
77
|
+
"lint:only": "biome lint ./src",
|
|
78
|
+
"check:only": "biome check ./src",
|
|
79
|
+
"format:only": "biome format ./src",
|
|
80
|
+
"lint": "biome lint ./src --write",
|
|
81
|
+
"check": "biome check ./src --write",
|
|
82
|
+
"pretest": "npm run build:prod",
|
|
83
|
+
"test": "tsx --tsconfig tsconfig.spec.json --test $(find src -name '*.spec.ts')",
|
|
84
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.spec.json",
|
|
85
|
+
"format": "biome format ./src --write",
|
|
86
|
+
"release:patch": "node ../../scripts/release.js patch",
|
|
87
|
+
"release:minor": "node ../../scripts/release.js minor",
|
|
88
|
+
"release:major": "node ../../scripts/release.js major"
|
|
89
|
+
},
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"@wendoo/app-host": "^0.2.8",
|
|
92
|
+
"@wendoo/bridge-client": "^0.2.7",
|
|
93
|
+
"@wendoo/bridge-protocol": "^0.2.10",
|
|
94
|
+
"@wendoo/core": "^0.2.19",
|
|
95
|
+
"@wendoo/ts-compiler": "^0.2.3"
|
|
96
|
+
},
|
|
97
|
+
"devDependencies": {
|
|
98
|
+
"@biomejs/biome": "2.3.15",
|
|
99
|
+
"@types/node": "^25.3.0",
|
|
100
|
+
"tsx": "^4.21.0",
|
|
101
|
+
"typescript": "~5.9.3"
|
|
102
|
+
}
|
|
103
|
+
}
|