1import * as NodePath from "@effect/platform-node/NodePath";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, describe, it } from "@effect/vitest";
4import { vi } from "vite-plus/test";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
8
9import * as DesktopEnvironment from "./DesktopEnvironment.ts";
10import * as DesktopConfig from "./DesktopConfig.ts";
11
12const defaultInput = {
13 dirname: "/repo/apps/desktop/dist-electron",
14 homeDirectory: "/Users/alice",
15 platform: "darwin",
16 processArch: "arm64",
17 appVersion: "0.0.22",
18 appPath: "/Applications/T3 Code.app/Contents/Resources/app.asar",
19 isPackaged: false,
20 resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
21 runningUnderArm64Translation: false,
22} satisfies DesktopEnvironment.MakeDesktopEnvironmentInput;
23
24const layerEnvironment = (
25 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
26 env: Record<string, string | undefined> = {},
27) =>
28 DesktopEnvironment.layer({
29 ...defaultInput,
30 ...overrides,
31 }).pipe(
32 Layer.provide(
33 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest(env)),
34 ),
35 );
36
37const makeEnvironment = (
38 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
39 env: Record<string, string | undefined> = {},
40) => DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(layerEnvironment(overrides, env)));
41
42describe("DesktopEnvironment", () => {
43 it.effect("derives state paths and development identity inside Effect", () =>
44 Effect.gen(function* () {
45 const environment = yield* makeEnvironment(
46 {},
47 {
48 T3CODE_HOME: " /tmp/t3 ",
49 T3CODE_COMMIT_HASH: " 0123456789abcdef ",
50 T3CODE_PORT: "4949",
51 VITE_DEV_SERVER_URL: "http://localhost:5173",
52 T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH: " /remote/server.mjs ",
53 T3CODE_OTLP_TRACES_URL: " http://127.0.0.1:4318/v1/traces ",
54 T3CODE_OTLP_METRICS_URL: " http://127.0.0.1:4318/v1/metrics ",
55 T3CODE_OTLP_LOGS_URL: " http://127.0.0.1:4318/v1/logs ",
56 T3CODE_OTLP_EXPORT_INTERVAL_MS: "2500",
57 T3CODE_OTLP_HEADERS: "authorization=Basic%20abc%3D%3D,x-tenant=t3",
58 T3CODE_OTLP_PROTOCOL: "http/protobuf",
59 },
60 );
61
62 assert.equal(environment.isDevelopment, true);
63 assert.equal(environment.appDataDirectory, "/Users/alice/Library/Application Support");
64 assert.equal(environment.baseDir, "/tmp/t3");
65 assert.equal(environment.stateDir, "/tmp/t3/userdata");
66 assert.equal(environment.desktopSettingsPath, "/tmp/t3/userdata/desktop-settings.json");
67 assert.equal(environment.clientSettingsPath, "/tmp/t3/userdata/client-settings.json");
68 assert.equal(
69 environment.savedEnvironmentRegistryPath,
70 "/tmp/t3/userdata/saved-environments.json",
71 );
72 assert.equal(environment.serverSettingsPath, "/tmp/t3/userdata/settings.json");
73 assert.equal(environment.logDir, "/tmp/t3/userdata/logs");
74 assert.equal(environment.browserArtifactsDir, "/tmp/t3/userdata/browser-artifacts");
75 assert.equal(environment.rootDir, "/repo");
76 assert.equal(environment.appRoot, "/repo");
77 assert.equal(environment.serverRoot, "/repo");
78 assert.equal(environment.backendEntryPath, "/repo/apps/server/dist/bin.mjs");
79 assert.equal(environment.backendCwd, "/repo");
80 assert.equal(environment.appUserModelId, "com.t3tools.t3code.dev");
81 assert.equal(environment.linuxWmClass, "t3code-dev");
82 assert.equal(environment.linuxDesktopEntryName, "com.t3tools.T3Code.Development.desktop");
83 assert.deepEqual(
84 Option.map(environment.devServerUrl, (url) => url.href),
85 Option.some("http://localhost:5173/"),
86 );
87 assert.deepEqual(environment.devRemoteT3ServerEntryPath, Option.some("/remote/server.mjs"));
88 assert.deepEqual(environment.configuredBackendPort, Option.some(4949));
89 assert.deepEqual(environment.commitHashOverride, Option.some("0123456789abcdef"));
90 assert.deepEqual(environment.otlpTracesUrl, Option.some("http://127.0.0.1:4318/v1/traces"));
91 assert.deepEqual(environment.otlpMetricsUrl, Option.some("http://127.0.0.1:4318/v1/metrics"));
92 assert.deepEqual(environment.otlpLogsUrl, Option.some("http://127.0.0.1:4318/v1/logs"));
93 assert.equal(environment.otlpExportIntervalMs, 2500);
94 assert.deepEqual(
95 environment.otlpHeaders,
96 Option.some({
97 authorization: "Basic abc==",
98 "x-tenant": "t3",
99 }),
100 );
101 assert.equal(environment.otlpProtocol, "http/protobuf");
102 }),
103 );
104
105 it.effect("stores production state under userdata in an explicit home", () =>
106 Effect.gen(function* () {
107 const environment = yield* makeEnvironment(
108 {},
109 {
110 T3CODE_HOME: "/tmp/t3",
111 },
112 );
113
114 assert.equal(environment.isDevelopment, false);
115 assert.equal(environment.stateDir, "/tmp/t3/userdata");
116 assert.equal(environment.logDir, "/tmp/t3/userdata/logs");
117 assert.equal(environment.browserArtifactsDir, "/tmp/t3/userdata/browser-artifacts");
118 assert.equal(environment.serverSettingsPath, "/tmp/t3/userdata/settings.json");
119 assert.equal(environment.otlpProtocol, "http/json");
120 }),
121 );
122
123 it.effect("uses the packaged Windows server sidecar as the backend root", () =>
124 Effect.gen(function* () {
125 const environment = yield* makeEnvironment({
126 platform: "win32",
127 isPackaged: true,
128 appPath: "/install/resources/app.asar",
129 resourcesPath: "/install/resources",
130 });
131
132 assert.equal(environment.appRoot, "/install/resources/app.asar");
133 assert.equal(environment.serverRoot, "/install/resources/server.asar");
134 assert.equal(
135 environment.backendEntryPath,
136 "/install/resources/server.asar/apps/server/dist/bin.mjs",
137 );
138 assert.equal(
139 environment.clientAssetsDir,
140 "/install/resources/server.asar/apps/server/dist/client",
141 );
142 }),
143 );
144
145 it.effect("keeps downloaded Windows desktop and server dependencies separate", () =>
146 Effect.gen(function* () {
147 vi.stubEnv("T3CODE_DESKTOP_PAYLOAD", JSON.stringify({ directory: "/updates/release-new" }));
148 try {
149 const environment = yield* makeEnvironment({
150 platform: "win32",
151 isPackaged: true,
152 appPath: "/updates/release-new",
153 resourcesPath: "/install/resources",
154 });
155 assert.equal(environment.serverRoot, "/updates/release-new/server");
156 assert.equal(
157 environment.backendEntryPath,
158 "/updates/release-new/server/apps/server/dist/bin.mjs",
159 );
160 assert.equal(
161 environment.clientAssetsDir,
162 "/updates/release-new/server/apps/server/dist/client",
163 );
164 } finally {
165 vi.unstubAllEnvs();
166 }
167 }),
168 );
169
170 it.effect("uses the stable desktop entry as the packaged Linux portal identity", () =>
171 Effect.gen(function* () {
172 const environment = yield* makeEnvironment({
173 platform: "linux",
174 isPackaged: true,
175 appPath: "/tmp/.mount_t3code/resources/app.asar",
176 resourcesPath: "/tmp/.mount_t3code/resources",
177 });
178
179 assert.equal(environment.linuxDesktopEntryName, "com.t3tools.T3Code.desktop");
180 }),
181 );
182
183 it.effect("keeps implicit development state separate from production state", () =>
184 Effect.gen(function* () {
185 const development = yield* makeEnvironment(
186 {},
187 { VITE_DEV_SERVER_URL: "http://localhost:5173" },
188 );
189 const production = yield* makeEnvironment();
190
191 assert.equal(development.stateDir, "/Users/alice/.t3/dev");
192 assert.equal(production.stateDir, "/Users/alice/.t3/userdata");
193 }),
194 );
195
196 it.effect("uses a configured app user model id override", () =>
197 Effect.gen(function* () {
198 const environment = yield* makeEnvironment(
199 {},
200 {
201 T3CODE_DESKTOP_APP_USER_MODEL_ID: " com.t3tools.t3code.dev.local ",
202 VITE_DEV_SERVER_URL: "http://localhost:5173",
203 },
204 );
205
206 assert.equal(environment.appUserModelId, "com.t3tools.t3code.dev.local");
207 }),
208 );
209
210 it.effect("resolves picker defaults without nullish sentinels", () =>
211 Effect.gen(function* () {
212 const environment = yield* makeEnvironment();
213
214 assert.deepEqual(environment.resolvePickFolderDefaultPath(null), Option.none());
215 assert.deepEqual(
216 environment.resolvePickFolderDefaultPath({ initialPath: " " }),
217 Option.none(),
218 );
219 assert.deepEqual(
220 environment.resolvePickFolderDefaultPath({ initialPath: "~" }),
221 Option.some("/Users/alice"),
222 );
223 assert.deepEqual(
224 environment.resolvePickFolderDefaultPath({ initialPath: "~/project" }),
225 Option.some("/Users/alice/project"),
226 );
227 }),
228 );
229});