1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Path from "effect/Path";
7import * as PlatformError from "effect/PlatformError";
8import * as Ref from "effect/Ref";
9
10import * as DesktopConfig from "../app/DesktopConfig.ts";
11import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
12import * as DesktopWslServerTree from "./DesktopWslServerTree.ts";
13
14// Electron's asar-aware fs accepts a plain server.asar directory under Node for extraction checks.
15
16const layerEnvironment = (input: {
17 readonly baseDir: string;
18 readonly resourcesPath: string;
19 readonly appVersion?: string;
20 readonly isPackaged?: boolean;
21}) =>
22 DesktopEnvironment.layer({
23 dirname: "/repo/apps/desktop/src",
24 homeDirectory: input.baseDir,
25 platform: "win32",
26 processArch: "x64",
27 appVersion: input.appVersion ?? "1.2.3",
28 appPath: "/repo",
29 isPackaged: input.isPackaged ?? true,
30 resourcesPath: input.resourcesPath,
31 runningUnderArm64Translation: false,
32 }).pipe(
33 Layer.provide(
34 Layer.mergeAll(
35 NodeServices.layer,
36 DesktopConfig.layerTest({
37 T3CODE_HOME: input.baseDir,
38 T3CODE_MODE: "desktop",
39 }),
40 ),
41 ),
42 );
43
44const withTempDir = <A, E, R>(run: (tempDir: string) => Effect.Effect<A, E, R>) =>
45 Effect.gen(function* () {
46 const fileSystem = yield* FileSystem.FileSystem;
47 const tempDir = yield* fileSystem.makeTempDirectoryScoped({
48 prefix: "t3-wsl-server-tree-test-",
49 });
50 return yield* run(tempDir);
51 });
52
53const ensureWith = (input: {
54 readonly baseDir: string;
55 readonly resourcesPath: string;
56 readonly appVersion?: string;
57 readonly isPackaged?: boolean;
58}) =>
59 Effect.gen(function* () {
60 const tree = yield* DesktopWslServerTree.DesktopWslServerTree;
61 return yield* tree.ensure;
62 }).pipe(
63 Effect.provide(DesktopWslServerTree.layer.pipe(Layer.provideMerge(layerEnvironment(input)))),
64 );
65
66describe("DesktopWslServerTree", () => {
67 it.effect("bounds entry work across an eight-way nested tree", () =>
68 Effect.gen(function* () {
69 const active = yield* Ref.make(0);
70 const maxActive = yield* Ref.make(0);
71 const visited = yield* Ref.make(0);
72
73 yield* DesktopWslServerTree.forEachBoundedTree([{ depth: 0, id: "root" }], (node) =>
74 Effect.acquireUseRelease(
75 Effect.gen(function* () {
76 const current = yield* Ref.updateAndGet(active, (count) => count + 1);
77 yield* Ref.update(maxActive, (maximum) => Math.max(maximum, current));
78 yield* Ref.update(visited, (count) => count + 1);
79 }),
80 () =>
81 Effect.gen(function* () {
82 yield* Effect.yieldNow;
83 if (node.depth === 4) return [];
84 return Array.from({ length: 8 }, (_, index) => ({
85 depth: node.depth + 1,
86 id: `${node.id}.${String(index)}`,
87 }));
88 }),
89 () => Ref.update(active, (count) => count - 1),
90 ),
91 );
92
93 assert.equal(yield* Ref.get(active), 0);
94 assert.equal(yield* Ref.get(maxActive), 8);
95 assert.equal(yield* Ref.get(visited), 4_681);
96 }),
97 );
98
99 it.effect("extracts an archive root into a version-keyed state directory", () =>
100 withTempDir((tempDir) =>
101 Effect.gen(function* () {
102 const fileSystem = yield* FileSystem.FileSystem;
103 const path = yield* Path.Path;
104 const serverRoot = path.join(tempDir, "resources", "server.asar");
105 yield* fileSystem.makeDirectory(path.join(serverRoot, "apps/server/dist"), {
106 recursive: true,
107 });
108 yield* fileSystem.writeFileString(
109 path.join(serverRoot, "apps/server/dist/bin.mjs"),
110 "server-entry",
111 );
112 yield* fileSystem.makeDirectory(path.join(serverRoot, "node_modules/effect"), {
113 recursive: true,
114 });
115 yield* fileSystem.writeFileString(
116 path.join(serverRoot, "node_modules/effect/package.json"),
117 "{}",
118 );
119
120 const result = yield* ensureWith({
121 baseDir: tempDir,
122 resourcesPath: path.join(tempDir, "resources"),
123 });
124
125 assert.isTrue(result.ok);
126 const root = result.ok ? result.root : "";
127 assert.include(root, path.join("wsl-server-tree", "1.2.3"));
128 const entry = yield* fileSystem.readFileString(path.join(root, "apps/server/dist/bin.mjs"));
129 assert.equal(entry, "server-entry");
130 const dep = yield* fileSystem.exists(path.join(root, "node_modules/effect/package.json"));
131 assert.isTrue(dep);
132 const marker = yield* fileSystem.readFileString(
133 path.join(root, "t3code-wsl-server-tree.json"),
134 );
135 assert.include(marker, '"version":"1.2.3"');
136 }),
137 ).pipe(Effect.provide(NodeServices.layer)),
138 );
139
140 it.effect("serializes concurrent extraction callers and publishes one complete tree", () =>
141 withTempDir((tempDir) =>
142 Effect.gen(function* () {
143 const fileSystem = yield* FileSystem.FileSystem;
144 const path = yield* Path.Path;
145 const resourcesPath = path.join(tempDir, "resources");
146 const serverRoot = path.join(resourcesPath, "server.asar");
147 yield* fileSystem.makeDirectory(path.join(serverRoot, "apps/server/dist"), {
148 recursive: true,
149 });
150 yield* fileSystem.writeFileString(
151 path.join(serverRoot, "apps/server/dist/bin.mjs"),
152 "server-entry",
153 );
154
155 const results = yield* Effect.gen(function* () {
156 const tree = yield* DesktopWslServerTree.DesktopWslServerTree;
157 return yield* Effect.all([tree.ensure, tree.ensure], { concurrency: "unbounded" });
158 }).pipe(
159 Effect.provide(
160 DesktopWslServerTree.layer.pipe(
161 Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })),
162 ),
163 ),
164 );
165
166 assert.isTrue(results.every((result) => result.ok));
167 const roots = results.flatMap((result) => (result.ok ? [result.root] : []));
168 assert.lengthOf(new Set(roots), 1);
169 assert.equal(
170 yield* fileSystem.readFileString(path.join(roots[0] ?? "", "apps/server/dist/bin.mjs")),
171 "server-entry",
172 );
173 }),
174 ).pipe(Effect.provide(NodeServices.layer)),
175 );
176
177 it.effect("reuses a completed extraction instead of copying again", () =>
178 withTempDir((tempDir) =>
179 Effect.gen(function* () {
180 const fileSystem = yield* FileSystem.FileSystem;
181 const path = yield* Path.Path;
182 const serverRoot = path.join(tempDir, "resources", "server.asar");
183 yield* fileSystem.makeDirectory(path.join(serverRoot, "apps/server/dist"), {
184 recursive: true,
185 });
186 yield* fileSystem.writeFileString(path.join(serverRoot, "apps/server/dist/bin.mjs"), "v1");
187
188 const first = yield* ensureWith({
189 baseDir: tempDir,
190 resourcesPath: path.join(tempDir, "resources"),
191 });
192 assert.isTrue(first.ok);
193
194 yield* fileSystem.writeFileString(
195 path.join(serverRoot, "apps/server/dist/bin.mjs"),
196 "v2-should-not-appear",
197 );
198 const second = yield* ensureWith({
199 baseDir: tempDir,
200 resourcesPath: path.join(tempDir, "resources"),
201 });
202 assert.isTrue(second.ok);
203 const root = second.ok ? second.root : "";
204 const entry = yield* fileSystem.readFileString(path.join(root, "apps/server/dist/bin.mjs"));
205 assert.equal(entry, "v1");
206 }),
207 ).pipe(Effect.provide(NodeServices.layer)),
208 );
209
210 it.effect("sweeps stale version directories and leftover partials after extraction", () =>
211 withTempDir((tempDir) =>
212 Effect.gen(function* () {
213 const fileSystem = yield* FileSystem.FileSystem;
214 const path = yield* Path.Path;
215 const serverRoot = path.join(tempDir, "resources", "server.asar");
216 yield* fileSystem.makeDirectory(path.join(serverRoot, "apps/server/dist"), {
217 recursive: true,
218 });
219 yield* fileSystem.writeFileString(path.join(serverRoot, "apps/server/dist/bin.mjs"), "x");
220
221 const treeRoot = path.join(tempDir, "userdata", "wsl-server-tree");
222 yield* fileSystem.makeDirectory(path.join(treeRoot, "1.0.0"), { recursive: true });
223 yield* fileSystem.makeDirectory(path.join(treeRoot, "1.2.3.partial"), { recursive: true });
224
225 const result = yield* ensureWith({
226 baseDir: tempDir,
227 resourcesPath: path.join(tempDir, "resources"),
228 });
229 assert.isTrue(result.ok);
230 assert.isFalse(yield* fileSystem.exists(path.join(treeRoot, "1.0.0")));
231 assert.isFalse(yield* fileSystem.exists(path.join(treeRoot, "1.2.3.partial")));
232 assert.isTrue(yield* fileSystem.exists(path.join(treeRoot, "1.2.3")));
233 }),
234 ).pipe(Effect.provide(NodeServices.layer)),
235 );
236
237 it.effect("re-extracts when the app version changes", () =>
238 withTempDir((tempDir) =>
239 Effect.gen(function* () {
240 const fileSystem = yield* FileSystem.FileSystem;
241 const path = yield* Path.Path;
242 const serverRoot = path.join(tempDir, "resources", "server.asar");
243 yield* fileSystem.makeDirectory(path.join(serverRoot, "apps/server/dist"), {
244 recursive: true,
245 });
246 yield* fileSystem.writeFileString(path.join(serverRoot, "apps/server/dist/bin.mjs"), "old");
247
248 const first = yield* ensureWith({
249 baseDir: tempDir,
250 resourcesPath: path.join(tempDir, "resources"),
251 appVersion: "1.2.3",
252 });
253 assert.isTrue(first.ok);
254
255 yield* fileSystem.writeFileString(path.join(serverRoot, "apps/server/dist/bin.mjs"), "new");
256 const second = yield* ensureWith({
257 baseDir: tempDir,
258 resourcesPath: path.join(tempDir, "resources"),
259 appVersion: "1.2.4",
260 });
261 assert.isTrue(second.ok);
262 const root = second.ok ? second.root : "";
263 assert.include(root, "1.2.4");
264 const entry = yield* fileSystem.readFileString(path.join(root, "apps/server/dist/bin.mjs"));
265 assert.equal(entry, "new");
266 const treeRoot = path.dirname(root);
267 assert.isFalse(yield* fileSystem.exists(path.join(treeRoot, "1.2.3")));
268 }),
269 ).pipe(Effect.provide(NodeServices.layer)),
270 );
271
272 it.effect("re-extracts after legacy cleanup partially deletes the completed tree", () =>
273 withTempDir((tempDir) =>
274 Effect.gen(function* () {
275 const fileSystem = yield* FileSystem.FileSystem;
276 const path = yield* Path.Path;
277 const resourcesPath = path.join(tempDir, "resources");
278 const serverRoot = path.join(resourcesPath, "server.asar");
279 const sourceEntryPath = path.join(serverRoot, "apps/server/dist/bin.mjs");
280 yield* fileSystem.makeDirectory(path.dirname(sourceEntryPath), { recursive: true });
281 yield* fileSystem.writeFileString(sourceEntryPath, "fresh-server-entry");
282
283 const initial = yield* ensureWith({ baseDir: tempDir, resourcesPath });
284 assert.isTrue(initial.ok);
285 const versionDir = initial.ok ? initial.root : "";
286 const treeRoot = path.dirname(versionDir);
287 const extractedEntryPath = path.join(versionDir, "apps/server/dist/bin.mjs");
288 let cleanupFailed = false;
289 const layerPartialCleanupFileSystem = Layer.effect(
290 FileSystem.FileSystem,
291 Effect.gen(function* () {
292 const realFileSystem = yield* FileSystem.FileSystem;
293 return {
294 ...realFileSystem,
295 remove: (target, options) =>
296 String(target) === treeRoot && options?.recursive === true && !cleanupFailed
297 ? Effect.gen(function* () {
298 cleanupFailed = true;
299 yield* realFileSystem.remove(extractedEntryPath);
300 return yield* PlatformError.systemError({
301 _tag: "PermissionDenied",
302 module: "FileSystem",
303 method: "remove",
304 pathOrDescriptor: treeRoot,
305 description: "simulated partial legacy cleanup",
306 });
307 })
308 : realFileSystem.remove(target, options),
309 } satisfies FileSystem.FileSystem;
310 }),
311 ).pipe(Layer.provide(NodeServices.layer));
312
313 const result = yield* Effect.gen(function* () {
314 const tree = yield* DesktopWslServerTree.DesktopWslServerTree;
315 yield* tree.cleanupLegacy;
316 return yield* tree.ensure;
317 }).pipe(
318 Effect.provide(
319 DesktopWslServerTree.layer.pipe(
320 Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })),
321 Layer.provideMerge(layerPartialCleanupFileSystem),
322 ),
323 ),
324 );
325
326 assert.isTrue(cleanupFailed);
327 assert.isTrue(result.ok);
328 assert.equal(yield* fileSystem.readFileString(extractedEntryPath), "fresh-server-entry");
329 }),
330 ).pipe(Effect.provide(NodeServices.layer)),
331 );
332
333 it.effect("reports a retryable failure when the archive cannot be read", () =>
334 withTempDir((tempDir) =>
335 Effect.gen(function* () {
336 const fileSystem = yield* FileSystem.FileSystem;
337 const path = yield* Path.Path;
338 const result = yield* ensureWith({
339 baseDir: tempDir,
340 resourcesPath: path.join(tempDir, "resources"),
341 });
342 assert.isFalse(result.ok);
343 if (!result.ok) {
344 assert.include(result.reason, "could not be extracted");
345 assert.isFalse(result.fatal);
346 }
347 const treeRoot = path.join(tempDir, "userdata", "wsl-server-tree");
348 const leftovers = yield* fileSystem
349 .readDirectory(treeRoot)
350 .pipe(Effect.orElseSucceed(() => []));
351 assert.deepStrictEqual(leftovers, []);
352 }),
353 ).pipe(Effect.provide(NodeServices.layer)),
354 );
355});