1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeFS from "node:fs";
3import * as NodeOS from "node:os";
4
5import { assert, expect, it } from "@effect/vitest";
6import * as ConfigProvider from "effect/ConfigProvider";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Layer from "effect/Layer";
10import * as Option from "effect/Option";
11import * as Path from "effect/Path";
12import * as Redacted from "effect/Redacted";
13import * as Schema from "effect/Schema";
14
15import {
16 DesktopBackendBootstrap,
17 type DesktopBackendBootstrap as DesktopBackendBootstrapValue,
18} from "@t3tools/contracts";
19import * as NetService from "@t3tools/shared/Net";
20import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
21import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
22import * as NodeServices from "@effect/platform-node/NodeServices";
23import { deriveServerPaths } from "../config.ts";
24import { resolveServerConfig } from "./config.ts";
25
26const deriveExplicitServerPaths = (baseDir: string, devUrl: URL | undefined) =>
27 deriveServerPaths(baseDir, devUrl, { baseDirIsExplicit: true });
28
29const minimalDesktopFlags = (baseDir: string) => ({
30 mode: Option.some("desktop" as const),
31 port: Option.some(4888),
32 host: Option.none<string>(),
33 baseDir: Option.some(baseDir),
34 cwd: Option.none<string>(),
35 devUrl: Option.none<URL>(),
36 noBrowser: Option.none<boolean>(),
37 bootstrapFd: Option.none<number>(),
38 autoBootstrapProjectFromCwd: Option.none<boolean>(),
39 logWebSocketEvents: Option.none<boolean>(),
40 tailscaleServeEnabled: Option.none<boolean>(),
41 tailscaleServePort: Option.none<number>(),
42});
43
44const encodeDesktopBootstrap = Schema.encodeEffect(Schema.fromJsonString(DesktopBackendBootstrap));
45const encodeUnknownJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
46
47const makeDesktopBootstrap = (
48 overrides: Partial<DesktopBackendBootstrapValue> = {},
49): DesktopBackendBootstrapValue => ({
50 mode: "desktop",
51 noBrowser: true,
52 port: 4888,
53 t3Home: "/tmp/t3-bootstrap-home",
54 host: "127.0.0.1",
55 desktopBootstrapToken: "desktop-bootstrap-token",
56 tailscaleServeEnabled: false,
57 tailscaleServePort: 443,
58 ...overrides,
59});
60
61it.layer(NodeServices.layer)("cli config resolution", (it) => {
62 const defaultObservabilityConfig = {
63 traceMinLevel: "Info",
64 traceTimingEnabled: true,
65 traceBatchWindowMs: 1_000,
66 traceMaxBytes: 10 * 1024 * 1024,
67 traceMaxFiles: 10,
68 otlpTracesUrl: undefined,
69 otlpMetricsUrl: undefined,
70 otlpLogsUrl: undefined,
71 otlpTracesExport: DEFAULT_SIGNAL_EXPORT,
72 otlpMetricsExport: DEFAULT_SIGNAL_EXPORT,
73 otlpLogsExport: DEFAULT_SIGNAL_EXPORT,
74 otelEnvironment: OtelEnvironment.none,
75 devAllowedOrigins: [],
76 } as const;
77
78 const openBootstrapFd = Effect.fn(function* (payload: DesktopBackendBootstrapValue) {
79 const fs = yield* FileSystem.FileSystem;
80 const filePath = yield* fs.makeTempFileScoped({ prefix: "t3-bootstrap-", suffix: ".ndjson" });
81 const encoded = yield* encodeDesktopBootstrap(payload);
82 yield* fs.writeFileString(filePath, `${encoded}\n`);
83 return yield* Effect.acquireRelease(
84 Effect.sync(() => NodeFS.openSync(filePath, "r")),
85 // Without a /proc or /dev/fd path to reopen, the reader consumes the fd
86 // itself (autoClose), so on Windows it is already closed here.
87 (fd) =>
88 Effect.sync(() => {
89 try {
90 NodeFS.closeSync(fd);
91 } catch (error) {
92 if ((error as NodeJS.ErrnoException).code !== "EBADF") throw error;
93 }
94 }),
95 );
96 });
97
98 it.effect("keeps stale records and supervised startup out of the manual launch preflight", () =>
99 Effect.gen(function* () {
100 const fs = yield* FileSystem.FileSystem;
101 const path = yield* Path.Path;
102 const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-preflight-" });
103 for (const [name, pid, mode, rejectRunningServer] of [
104 ["stale", 2_147_483_647, "web", true],
105 ["desktop", process.pid, "desktop", true],
106 ["serve", process.pid, "web", false],
107 ] as const) {
108 const baseDir = path.join(root, name);
109 const stateDir = path.join(baseDir, "userdata");
110 yield* fs.makeDirectory(stateDir, { recursive: true });
111 yield* fs.writeFileString(
112 path.join(stateDir, "server-runtime.json"),
113 yield* encodeUnknownJson({
114 version: 1,
115 pid,
116 port: 3773,
117 origin: "http://127.0.0.1:3773",
118 startedAt: "2026-10-01T00:00:00.000Z",
119 }),
120 );
121 const cwd = path.join(root, `${name}-project`);
122 const config = yield* resolveServerConfig(
123 {
124 ...minimalWebFlags(baseDir),
125 mode: Option.some(mode),
126 port: Option.some(8788),
127 cwd: Option.some(cwd),
128 },
129 Option.none(),
130 { rejectRunningServer },
131 ).pipe(
132 Effect.provide(
133 Layer.merge(
134 NetService.layer,
135 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
136 ),
137 ),
138 );
139 expect(config.cwd).toBe(cwd);
140 expect(yield* fs.exists(cwd)).toBe(true);
141 }
142 }),
143 );
144
145 it.effect("enables a trimmed reusable auth token only for web dev mode", () =>
146 Effect.gen(function* () {
147 const baseDir = yield* FileSystem.FileSystem.pipe(
148 Effect.flatMap((fs) => fs.makeTempDirectoryScoped({ prefix: "t3-cli-dev-auth-" })),
149 );
150 const flags = {
151 mode: Option.some("web" as const),
152 port: Option.some(8788),
153 host: Option.none<string>(),
154 baseDir: Option.some(baseDir),
155 cwd: Option.none<string>(),
156 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
157 noBrowser: Option.none<boolean>(),
158 bootstrapFd: Option.none<number>(),
159 autoBootstrapProjectFromCwd: Option.none<boolean>(),
160 logWebSocketEvents: Option.none<boolean>(),
161 tailscaleServeEnabled: Option.none<boolean>(),
162 tailscaleServePort: Option.none<number>(),
163 };
164 const layerConfig = ConfigProvider.layer(
165 ConfigProvider.fromEnv({
166 env: {
167 T3CODE_DEV_AUTH_TOKEN: " reusable-dev-auth-token-that-is-long-enough ",
168 },
169 }),
170 );
171 const web = yield* resolveServerConfig(flags, Option.none()).pipe(
172 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
173 );
174 const desktop = yield* resolveServerConfig(
175 { ...flags, mode: Option.some("desktop" as const) },
176 Option.none(),
177 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
178
179 expect(web.devAuthToken).toBeDefined();
180 if (web.devAuthToken === undefined) {
181 return yield* Effect.die("Expected reusable dev auth token.");
182 }
183 expect(Redacted.value(web.devAuthToken)).toBe("reusable-dev-auth-token-that-is-long-enough");
184 expect(desktop.devAuthToken).toBeUndefined();
185 }),
186 );
187
188 it.effect("does not expose an invalid reusable auth token", () =>
189 Effect.gen(function* () {
190 const secret = "short-secret";
191 const baseDir = yield* FileSystem.FileSystem.pipe(
192 Effect.flatMap((fs) => fs.makeTempDirectoryScoped({ prefix: "t3-cli-dev-auth-invalid-" })),
193 );
194 const flags = {
195 mode: Option.some("web" as const),
196 port: Option.some(8788),
197 host: Option.none<string>(),
198 baseDir: Option.some(baseDir),
199 cwd: Option.none<string>(),
200 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
201 noBrowser: Option.none<boolean>(),
202 bootstrapFd: Option.none<number>(),
203 autoBootstrapProjectFromCwd: Option.none<boolean>(),
204 logWebSocketEvents: Option.none<boolean>(),
205 tailscaleServeEnabled: Option.none<boolean>(),
206 tailscaleServePort: Option.none<number>(),
207 };
208 const layerConfig = ConfigProvider.layer(
209 ConfigProvider.fromEnv({ env: { T3CODE_DEV_AUTH_TOKEN: secret } }),
210 );
211 const error = yield* resolveServerConfig(flags, Option.none()).pipe(
212 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
213 Effect.flip,
214 );
215 const desktop = yield* resolveServerConfig(
216 { ...flags, mode: Option.some("desktop" as const) },
217 Option.none(),
218 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
219 const staticWeb = yield* resolveServerConfig(
220 { ...flags, devUrl: Option.none() },
221 Option.none(),
222 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
223
224 expect(String(error)).not.toContain(secret);
225 const serialized = yield* encodeUnknownJson(error);
226 expect(serialized).not.toContain(secret);
227 expect(desktop.devAuthToken).toBeUndefined();
228 expect(staticWeb.devAuthToken).toBeUndefined();
229 }),
230 );
231
232 it.effect("falls back to effect/config values when flags are omitted", () =>
233 Effect.gen(function* () {
234 const { join } = yield* Path.Path;
235 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-base");
236 const derivedPaths = yield* deriveExplicitServerPaths(
237 baseDir,
238 new URL("http://127.0.0.1:5173"),
239 );
240 const resolved = yield* resolveServerConfig(
241 {
242 mode: Option.none(),
243 port: Option.none(),
244 host: Option.none(),
245 baseDir: Option.none(),
246 cwd: Option.none(),
247 devUrl: Option.none(),
248 noBrowser: Option.none(),
249 bootstrapFd: Option.none(),
250 autoBootstrapProjectFromCwd: Option.none(),
251 logWebSocketEvents: Option.none(),
252 tailscaleServeEnabled: Option.none(),
253 tailscaleServePort: Option.none(),
254 },
255 Option.none(),
256 ).pipe(
257 Effect.provide(
258 Layer.mergeAll(
259 ConfigProvider.layer(
260 ConfigProvider.fromEnv({
261 env: {
262 T3CODE_LOG_LEVEL: "Warn",
263 T3CODE_MODE: "desktop",
264 T3CODE_PORT: "4001",
265 T3CODE_HOST: "0.0.0.0",
266 T3CODE_HOME: baseDir,
267 VITE_DEV_SERVER_URL: "http://127.0.0.1:5173",
268 T3CODE_DEV_ALLOWED_ORIGINS:
269 "https://host.example.ts.net, https://phone.example.ts.net ",
270 T3CODE_NO_BROWSER: "true",
271 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false",
272 T3CODE_LOG_WS_EVENTS: "true",
273 },
274 }),
275 ),
276 NetService.layer,
277 ),
278 ),
279 );
280
281 expect(resolved).toEqual({
282 logLevel: "Warn",
283 ...defaultObservabilityConfig,
284 mode: "desktop",
285 port: 4001,
286 cwd: process.cwd(),
287 baseDir,
288 ...derivedPaths,
289 host: "0.0.0.0",
290 staticDir: undefined,
291 devUrl: new URL("http://127.0.0.1:5173"),
292 devAllowedOrigins: ["https://host.example.ts.net", "https://phone.example.ts.net"],
293 noBrowser: true,
294 startupPresentation: "browser",
295 desktopBootstrapToken: undefined,
296 autoBootstrapProjectFromCwd: false,
297 logWebSocketEvents: true,
298 tailscaleServeEnabled: false,
299 tailscaleServePort: 443,
300 });
301 assert.equal(resolved.stateDir, join(baseDir, "userdata"));
302 }),
303 );
304
305 it.effect("uses CLI flags when provided", () =>
306 Effect.gen(function* () {
307 const { join } = yield* Path.Path;
308 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-flags-base");
309 const derivedPaths = yield* deriveExplicitServerPaths(
310 baseDir,
311 new URL("http://127.0.0.1:4173"),
312 );
313 const resolved = yield* resolveServerConfig(
314 {
315 mode: Option.some("web"),
316 port: Option.some(8788),
317 host: Option.some("127.0.0.1"),
318 baseDir: Option.some(baseDir),
319 cwd: Option.none(),
320 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
321 noBrowser: Option.some(true),
322 bootstrapFd: Option.none(),
323 autoBootstrapProjectFromCwd: Option.some(true),
324 logWebSocketEvents: Option.some(true),
325 tailscaleServeEnabled: Option.some(true),
326 tailscaleServePort: Option.some(8443),
327 },
328 Option.some("Debug"),
329 ).pipe(
330 Effect.provide(
331 Layer.mergeAll(
332 ConfigProvider.layer(
333 ConfigProvider.fromEnv({
334 env: {
335 T3CODE_LOG_LEVEL: "Warn",
336 T3CODE_MODE: "desktop",
337 T3CODE_PORT: "4001",
338 T3CODE_HOST: "0.0.0.0",
339 T3CODE_HOME: join(NodeOS.tmpdir(), "ignored-base"),
340 VITE_DEV_SERVER_URL: "http://127.0.0.1:5173",
341 T3CODE_NO_BROWSER: "false",
342 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false",
343 T3CODE_LOG_WS_EVENTS: "false",
344 },
345 }),
346 ),
347 NetService.layer,
348 ),
349 ),
350 );
351
352 expect(resolved).toEqual({
353 logLevel: "Debug",
354 ...defaultObservabilityConfig,
355 mode: "web",
356 port: 8788,
357 cwd: process.cwd(),
358 baseDir,
359 ...derivedPaths,
360 host: "127.0.0.1",
361 staticDir: undefined,
362 devUrl: new URL("http://127.0.0.1:4173"),
363 noBrowser: true,
364 startupPresentation: "browser",
365 desktopBootstrapToken: undefined,
366 autoBootstrapProjectFromCwd: true,
367 logWebSocketEvents: true,
368 tailscaleServeEnabled: true,
369 tailscaleServePort: 8443,
370 });
371 assert.equal(resolved.dbPath, join(baseDir, "userdata", "statev2.sqlite"));
372 }),
373 );
374
375 it.effect("preserves explicit false CLI boolean flags over env and bootstrap values", () =>
376 Effect.gen(function* () {
377 const { join } = yield* Path.Path;
378 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-false-flags");
379 const fd = yield* openBootstrapFd(
380 makeDesktopBootstrap({
381 noBrowser: true,
382 tailscaleServeEnabled: false,
383 tailscaleServePort: 443,
384 }),
385 );
386 const derivedPaths = yield* deriveExplicitServerPaths(
387 baseDir,
388 new URL("http://127.0.0.1:4173"),
389 );
390
391 const resolved = yield* resolveServerConfig(
392 {
393 mode: Option.some("web"),
394 port: Option.some(8788),
395 host: Option.some("127.0.0.1"),
396 baseDir: Option.some(baseDir),
397 cwd: Option.none(),
398 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
399 noBrowser: Option.some(false),
400 bootstrapFd: Option.none(),
401 autoBootstrapProjectFromCwd: Option.some(false),
402 logWebSocketEvents: Option.some(false),
403 tailscaleServeEnabled: Option.none(),
404 tailscaleServePort: Option.none(),
405 },
406 Option.none(),
407 ).pipe(
408 Effect.provide(
409 Layer.mergeAll(
410 ConfigProvider.layer(
411 ConfigProvider.fromEnv({
412 env: {
413 T3CODE_BOOTSTRAP_FD: String(fd),
414 T3CODE_NO_BROWSER: "true",
415 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
416 T3CODE_LOG_WS_EVENTS: "true",
417 },
418 }),
419 ),
420 NetService.layer,
421 ),
422 ),
423 );
424
425 expect(resolved).toEqual({
426 logLevel: "Info",
427 ...defaultObservabilityConfig,
428 mode: "web",
429 port: 8788,
430 cwd: process.cwd(),
431 baseDir,
432 ...derivedPaths,
433 host: "127.0.0.1",
434 staticDir: undefined,
435 devUrl: new URL("http://127.0.0.1:4173"),
436 noBrowser: false,
437 startupPresentation: "browser",
438 desktopBootstrapToken: "desktop-bootstrap-token",
439 autoBootstrapProjectFromCwd: false,
440 logWebSocketEvents: false,
441 tailscaleServeEnabled: false,
442 tailscaleServePort: 443,
443 });
444 }),
445 );
446
447 it.effect("uses bootstrap envelope values as fallbacks when flags and env are absent", () =>
448 Effect.gen(function* () {
449 const { join, resolve } = yield* Path.Path;
450 // The resolver absolutises the configured home, so the expectation must
451 // carry the host's drive on Windows.
452 const baseDir = resolve("/tmp/t3-bootstrap-home");
453 const fd = yield* openBootstrapFd(
454 makeDesktopBootstrap({
455 port: 4888,
456 host: "127.0.0.2",
457 t3Home: "/tmp/t3-bootstrap-home",
458 noBrowser: true,
459 desktopBootstrapToken: "desktop-token",
460 desktopTelemetryFd: 4,
461 desktopTelemetryControlFd: 5,
462 tailscaleServeEnabled: false,
463 tailscaleServePort: 443,
464 otlpTracesUrl: "http://localhost:4318/v1/traces",
465 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
466 otlpLogsUrl: "http://localhost:4318/v1/logs",
467 }),
468 );
469 const derivedPaths = yield* deriveServerPaths(baseDir, undefined);
470
471 const resolved = yield* resolveServerConfig(
472 {
473 mode: Option.none(),
474 port: Option.none(),
475 host: Option.none(),
476 baseDir: Option.none(),
477 cwd: Option.none(),
478 devUrl: Option.none(),
479 noBrowser: Option.none(),
480 bootstrapFd: Option.none(),
481 autoBootstrapProjectFromCwd: Option.none(),
482 logWebSocketEvents: Option.none(),
483 tailscaleServeEnabled: Option.none(),
484 tailscaleServePort: Option.none(),
485 },
486 Option.none(),
487 ).pipe(
488 Effect.provide(
489 Layer.mergeAll(
490 ConfigProvider.layer(
491 ConfigProvider.fromEnv({
492 env: {
493 T3CODE_BOOTSTRAP_FD: String(fd),
494 },
495 }),
496 ),
497 NetService.layer,
498 ),
499 ),
500 );
501
502 expect(resolved).toEqual({
503 logLevel: "Info",
504 ...defaultObservabilityConfig,
505 otlpTracesUrl: "http://localhost:4318/v1/traces",
506 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
507 otlpLogsUrl: "http://localhost:4318/v1/logs",
508 mode: "desktop",
509 port: 4888,
510 cwd: process.cwd(),
511 baseDir,
512 ...derivedPaths,
513 host: "127.0.0.2",
514 staticDir: resolved.staticDir,
515 devUrl: undefined,
516 noBrowser: true,
517 startupPresentation: "browser",
518 desktopBootstrapToken: "desktop-token",
519 desktopTelemetryFd: 4,
520 desktopTelemetryControlFd: 5,
521 resourceMonitorPath: undefined,
522 autoBootstrapProjectFromCwd: false,
523 logWebSocketEvents: false,
524 tailscaleServeEnabled: false,
525 tailscaleServePort: 443,
526 });
527 assert.equal(join(baseDir, "userdata"), resolved.stateDir);
528 assert.equal(resolved.desktopTelemetryFd, 4);
529 assert.equal(resolved.desktopTelemetryControlFd, 5);
530 }),
531 );
532
533 it.effect("carries the desktop's shell environment handoff only when the envelope sets it", () =>
534 Effect.gen(function* () {
535 const fs = yield* FileSystem.FileSystem;
536 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-shell-env-" });
537 const resolveWith = Effect.fn(function* (overrides: Partial<DesktopBackendBootstrapValue>) {
538 const fd = yield* openBootstrapFd(makeDesktopBootstrap(overrides));
539 return yield* resolveServerConfig(
540 {
541 ...minimalDesktopFlags(baseDir),
542 bootstrapFd: Option.some(fd),
543 },
544 Option.none(),
545 ).pipe(
546 Effect.provide(
547 Layer.mergeAll(
548 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
549 NetService.layer,
550 ),
551 ),
552 );
553 });
554
555 assert.equal((yield* resolveWith({})).shellEnvironmentPrepared, undefined);
556 assert.equal(
557 (yield* resolveWith({ shellEnvironmentPrepared: true })).shellEnvironmentPrepared,
558 true,
559 );
560 }),
561 );
562
563 it.effect("creates derived runtime directories during config resolution", () =>
564 Effect.gen(function* () {
565 const fs = yield* FileSystem.FileSystem;
566 const path = yield* Path.Path;
567 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-dirs-" });
568 const customCwd = path.join(baseDir, "nested", "project");
569
570 const resolved = yield* resolveServerConfig(
571 {
572 mode: Option.some("desktop"),
573 port: Option.some(4888),
574 host: Option.none(),
575 baseDir: Option.some(baseDir),
576 cwd: Option.some(customCwd),
577 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
578 noBrowser: Option.none(),
579 bootstrapFd: Option.none(),
580 autoBootstrapProjectFromCwd: Option.none(),
581 logWebSocketEvents: Option.none(),
582 tailscaleServeEnabled: Option.none(),
583 tailscaleServePort: Option.none(),
584 },
585 Option.none(),
586 ).pipe(
587 Effect.provide(
588 Layer.mergeAll(
589 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
590 NetService.layer,
591 ),
592 ),
593 );
594
595 for (const directory of [
596 customCwd,
597 resolved.stateDir,
598 resolved.logsDir,
599 resolved.providerLogsDir,
600 resolved.terminalLogsDir,
601 resolved.attachmentsDir,
602 resolved.worktreesDir,
603 path.dirname(resolved.serverTracePath),
604 ]) {
605 expect(yield* fs.exists(directory)).toBe(true);
606 }
607 expect(resolved.cwd).toBe(path.resolve(customCwd));
608 }),
609 );
610
611 it.effect("applies flag then env precedence over bootstrap envelope values", () =>
612 Effect.gen(function* () {
613 const { join } = yield* Path.Path;
614 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-wins");
615 const fd = yield* openBootstrapFd(
616 makeDesktopBootstrap({
617 port: 4888,
618 host: "127.0.0.2",
619 t3Home: "/tmp/t3-bootstrap-home",
620 noBrowser: false,
621 desktopBootstrapToken: "desktop-token",
622 tailscaleServeEnabled: false,
623 tailscaleServePort: 443,
624 }),
625 );
626 const derivedPaths = yield* deriveExplicitServerPaths(
627 baseDir,
628 new URL("http://127.0.0.1:4173"),
629 );
630
631 const resolved = yield* resolveServerConfig(
632 {
633 mode: Option.none(),
634 port: Option.some(8788),
635 host: Option.some("127.0.0.1"),
636 baseDir: Option.none(),
637 cwd: Option.none(),
638 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
639 noBrowser: Option.none(),
640 bootstrapFd: Option.none(),
641 autoBootstrapProjectFromCwd: Option.none(),
642 logWebSocketEvents: Option.none(),
643 tailscaleServeEnabled: Option.none(),
644 tailscaleServePort: Option.none(),
645 },
646 Option.some("Debug"),
647 ).pipe(
648 Effect.provide(
649 Layer.mergeAll(
650 ConfigProvider.layer(
651 ConfigProvider.fromEnv({
652 env: {
653 T3CODE_MODE: "web",
654 T3CODE_BOOTSTRAP_FD: String(fd),
655 T3CODE_HOME: baseDir,
656 T3CODE_NO_BROWSER: "true",
657 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
658 T3CODE_LOG_WS_EVENTS: "true",
659 },
660 }),
661 ),
662 NetService.layer,
663 ),
664 ),
665 );
666
667 expect(resolved).toEqual({
668 logLevel: "Debug",
669 ...defaultObservabilityConfig,
670 mode: "web",
671 port: 8788,
672 cwd: process.cwd(),
673 baseDir,
674 ...derivedPaths,
675 host: "127.0.0.1",
676 staticDir: undefined,
677 devUrl: new URL("http://127.0.0.1:4173"),
678 noBrowser: true,
679 startupPresentation: "browser",
680 desktopBootstrapToken: "desktop-token",
681 autoBootstrapProjectFromCwd: true,
682 logWebSocketEvents: true,
683 tailscaleServeEnabled: false,
684 tailscaleServePort: 443,
685 });
686 }),
687 );
688
689 it.effect("falls back to persisted observability settings when env vars are absent", () =>
690 Effect.gen(function* () {
691 const fs = yield* FileSystem.FileSystem;
692 const path = yield* Path.Path;
693 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-settings-" });
694 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
695 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
696 yield* fs.writeFileString(
697 derivedPaths.settingsPath,
698 `${JSON.stringify({
699 observability: {
700 otlpTracesUrl: "http://localhost:4318/v1/traces",
701 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
702 otlpLogsUrl: "http://localhost:4318/v1/logs",
703 },
704 })}\n`,
705 );
706
707 const resolved = yield* resolveServerConfig(
708 {
709 mode: Option.some("desktop"),
710 port: Option.some(4888),
711 host: Option.none(),
712 baseDir: Option.some(baseDir),
713 cwd: Option.none(),
714 devUrl: Option.none(),
715 noBrowser: Option.none(),
716 bootstrapFd: Option.none(),
717 autoBootstrapProjectFromCwd: Option.none(),
718 logWebSocketEvents: Option.none(),
719 tailscaleServeEnabled: Option.none(),
720 tailscaleServePort: Option.none(),
721 },
722 Option.none(),
723 ).pipe(
724 Effect.provide(
725 Layer.mergeAll(
726 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
727 NetService.layer,
728 ),
729 ),
730 );
731
732 expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
733 expect(resolved.otlpMetricsUrl).toBe("http://localhost:4318/v1/metrics");
734 expect(resolved.otlpLogsUrl).toBe("http://localhost:4318/v1/logs");
735 expect(resolved).toEqual({
736 logLevel: "Info",
737 ...defaultObservabilityConfig,
738 otlpTracesUrl: "http://localhost:4318/v1/traces",
739 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
740 otlpLogsUrl: "http://localhost:4318/v1/logs",
741 mode: "desktop",
742 port: 4888,
743 cwd: process.cwd(),
744 baseDir,
745 ...derivedPaths,
746 host: "127.0.0.1",
747 staticDir: resolved.staticDir,
748 devUrl: undefined,
749 noBrowser: true,
750 startupPresentation: "browser",
751 desktopBootstrapToken: undefined,
752 autoBootstrapProjectFromCwd: false,
753 logWebSocketEvents: false,
754 tailscaleServeEnabled: false,
755 tailscaleServePort: 443,
756 });
757 }),
758 );
759
760 it.effect("zeroes an endpoint stored in Settings when the SDK is disabled", () =>
761 Effect.gen(function* () {
762 const fs = yield* FileSystem.FileSystem;
763 const path = yield* Path.Path;
764 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-otel-off-" });
765 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
766 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
767 yield* fs.writeFileString(
768 derivedPaths.settingsPath,
769 `${JSON.stringify({
770 observability: {
771 otlpTracesUrl: "http://localhost:4318/v1/traces",
772 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
773 otlpLogsUrl: "http://localhost:4318/v1/logs",
774 },
775 })}\n`,
776 );
777
778 const resolved = yield* resolveServerConfig(
779 {
780 mode: Option.some("desktop"),
781 port: Option.some(4888),
782 host: Option.none(),
783 baseDir: Option.some(baseDir),
784 cwd: Option.none(),
785 devUrl: Option.none(),
786 noBrowser: Option.none(),
787 bootstrapFd: Option.none(),
788 autoBootstrapProjectFromCwd: Option.none(),
789 logWebSocketEvents: Option.none(),
790 tailscaleServeEnabled: Option.none(),
791 tailscaleServePort: Option.none(),
792 },
793 Option.none(),
794 ).pipe(
795 Effect.provide(
796 Layer.mergeAll(
797 ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
798 NetService.layer,
799 ),
800 ),
801 );
802
803 // The switch beats every source, including an endpoint stored in Settings.
804 expect(resolved.otlpTracesUrl).toBeUndefined();
805 expect(resolved.otlpMetricsUrl).toBeUndefined();
806 expect(resolved.otlpLogsUrl).toBeUndefined();
807 expect(resolved.otelEnvironment.disabled).toBe(true);
808 }),
809 );
810
811 it.effect("lets T3CODE_OTEL_SDK_DISABLED=false override an ambient OTEL_SDK_DISABLED=true", () =>
812 Effect.gen(function* () {
813 const fs = yield* FileSystem.FileSystem;
814 const path = yield* Path.Path;
815 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-otel-on-" });
816 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
817 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
818 yield* fs.writeFileString(
819 derivedPaths.settingsPath,
820 `${JSON.stringify({
821 observability: {
822 otlpTracesUrl: "http://localhost:4318/v1/traces",
823 },
824 })}\n`,
825 );
826
827 const resolved = yield* resolveServerConfig(
828 {
829 mode: Option.some("desktop"),
830 port: Option.some(4888),
831 host: Option.none(),
832 baseDir: Option.some(baseDir),
833 cwd: Option.none(),
834 devUrl: Option.none(),
835 noBrowser: Option.none(),
836 bootstrapFd: Option.none(),
837 autoBootstrapProjectFromCwd: Option.none(),
838 logWebSocketEvents: Option.none(),
839 tailscaleServeEnabled: Option.none(),
840 tailscaleServePort: Option.none(),
841 },
842 Option.none(),
843 ).pipe(
844 Effect.provide(
845 Layer.mergeAll(
846 ConfigProvider.layer(
847 ConfigProvider.fromEnv({
848 env: { T3CODE_OTEL_SDK_DISABLED: "false", OTEL_SDK_DISABLED: "true" },
849 }),
850 ),
851 NetService.layer,
852 ),
853 ),
854 );
855
856 expect(resolved.otelEnvironment.disabled).toBe(false);
857 expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
858 }),
859 );
860
861 it.effect("forces noBrowser and disables auto-bootstrap for headless startup presentation", () =>
862 Effect.gen(function* () {
863 const { join } = yield* Path.Path;
864 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-headless-base");
865 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
866
867 const resolved = yield* resolveServerConfig(
868 {
869 mode: Option.some("web"),
870 port: Option.some(3773),
871 host: Option.none(),
872 baseDir: Option.some(baseDir),
873 cwd: Option.none(),
874 devUrl: Option.none(),
875 noBrowser: Option.none(),
876 bootstrapFd: Option.none(),
877 autoBootstrapProjectFromCwd: Option.none(),
878 logWebSocketEvents: Option.none(),
879 tailscaleServeEnabled: Option.none(),
880 tailscaleServePort: Option.none(),
881 },
882 Option.none(),
883 {
884 startupPresentation: "headless",
885 },
886 ).pipe(
887 Effect.provide(
888 Layer.mergeAll(
889 ConfigProvider.layer(
890 ConfigProvider.fromEnv({
891 env: {
892 T3CODE_NO_BROWSER: "false",
893 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
894 },
895 }),
896 ),
897 NetService.layer,
898 ),
899 ),
900 );
901
902 expect(resolved).toEqual({
903 logLevel: "Info",
904 ...defaultObservabilityConfig,
905 mode: "web",
906 port: 3773,
907 cwd: process.cwd(),
908 baseDir,
909 ...derivedPaths,
910 host: undefined,
911 staticDir: resolved.staticDir,
912 devUrl: undefined,
913 noBrowser: true,
914 startupPresentation: "headless",
915 desktopBootstrapToken: undefined,
916 autoBootstrapProjectFromCwd: false,
917 logWebSocketEvents: false,
918 tailscaleServeEnabled: false,
919 tailscaleServePort: 443,
920 });
921 }),
922 );
923
924 it.effect("decodes percent-encoded OTLP headers from env", () =>
925 Effect.gen(function* () {
926 const { join } = yield* Path.Path;
927 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-headers-base");
928
929 const resolved = yield* resolveServerConfig(
930 {
931 mode: Option.some("web"),
932 port: Option.some(3773),
933 host: Option.none(),
934 baseDir: Option.some(baseDir),
935 cwd: Option.none(),
936 devUrl: Option.none(),
937 noBrowser: Option.none(),
938 bootstrapFd: Option.none(),
939 autoBootstrapProjectFromCwd: Option.none(),
940 logWebSocketEvents: Option.none(),
941 tailscaleServeEnabled: Option.none(),
942 tailscaleServePort: Option.none(),
943 },
944 Option.none(),
945 ).pipe(
946 Effect.provide(
947 Layer.mergeAll(
948 ConfigProvider.layer(
949 ConfigProvider.fromEnv({
950 env: {
951 T3CODE_OTLP_HEADERS: "authorization=Basic%20abc%3D%3D,x-tenant=t3",
952 },
953 }),
954 ),
955 NetService.layer,
956 ),
957 ),
958 );
959
960 expect(resolved.otlpTracesExport.headers).toEqual({
961 authorization: "Basic abc==",
962 "x-tenant": "t3",
963 });
964 }),
965 );
966
967 it.effect("keeps whitespace-separated pairs and literal equals signs in OTLP headers", () =>
968 Effect.gen(function* () {
969 const { join } = yield* Path.Path;
970 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-headers-loose-base");
971
972 const resolved = yield* resolveServerConfig(
973 {
974 mode: Option.some("web"),
975 port: Option.some(3773),
976 host: Option.none(),
977 baseDir: Option.some(baseDir),
978 cwd: Option.none(),
979 devUrl: Option.none(),
980 noBrowser: Option.none(),
981 bootstrapFd: Option.none(),
982 autoBootstrapProjectFromCwd: Option.none(),
983 logWebSocketEvents: Option.none(),
984 tailscaleServeEnabled: Option.none(),
985 tailscaleServePort: Option.none(),
986 },
987 Option.none(),
988 ).pipe(
989 Effect.provide(
990 Layer.mergeAll(
991 ConfigProvider.layer(
992 ConfigProvider.fromEnv({
993 env: {
994 T3CODE_OTLP_HEADERS: "authorization=Bearer abc==, x-tenant=t3",
995 T3CODE_OTLP_TRACES_URL: "http://collector.internal:4318",
996 },
997 }),
998 ),
999 NetService.layer,
1000 ),
1001 ),
1002 );
1003
1004 expect(resolved.otlpTracesExport.headers).toEqual({
1005 authorization: "Bearer abc==",
1006 "x-tenant": "t3",
1007 });
1008 expect(resolved.otlpTracesUrl).toBe("http://collector.internal:4318");
1009 }),
1010 );
1011
1012 it.effect("gives every signal the protocol named without one", () =>
1013 Effect.gen(function* () {
1014 const { join } = yield* Path.Path;
1015 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-protocol-base");
1016
1017 const resolved = yield* resolveServerConfig(
1018 {
1019 mode: Option.some("web"),
1020 port: Option.some(3773),
1021 host: Option.none(),
1022 baseDir: Option.some(baseDir),
1023 cwd: Option.none(),
1024 devUrl: Option.none(),
1025 noBrowser: Option.none(),
1026 bootstrapFd: Option.none(),
1027 autoBootstrapProjectFromCwd: Option.none(),
1028 logWebSocketEvents: Option.none(),
1029 tailscaleServeEnabled: Option.none(),
1030 tailscaleServePort: Option.none(),
1031 },
1032 Option.none(),
1033 ).pipe(
1034 Effect.provide(
1035 Layer.mergeAll(
1036 ConfigProvider.layer(
1037 ConfigProvider.fromEnv({ env: { T3CODE_OTLP_PROTOCOL: "http/protobuf" } }),
1038 ),
1039 NetService.layer,
1040 ),
1041 ),
1042 );
1043
1044 expect([
1045 resolved.otlpTracesExport.protocol,
1046 resolved.otlpMetricsExport.protocol,
1047 resolved.otlpLogsExport.protocol,
1048 ]).toEqual(["http/protobuf", "http/protobuf", "http/protobuf"]);
1049 }),
1050 );
1051
1052 it.effect("reads the OTLP logs URL from env", () =>
1053 Effect.gen(function* () {
1054 const { join } = yield* Path.Path;
1055 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-logs-url-base");
1056
1057 const resolved = yield* resolveServerConfig(
1058 {
1059 mode: Option.some("web"),
1060 port: Option.some(3773),
1061 host: Option.none(),
1062 baseDir: Option.some(baseDir),
1063 cwd: Option.none(),
1064 devUrl: Option.none(),
1065 noBrowser: Option.none(),
1066 bootstrapFd: Option.none(),
1067 autoBootstrapProjectFromCwd: Option.none(),
1068 logWebSocketEvents: Option.none(),
1069 tailscaleServeEnabled: Option.none(),
1070 tailscaleServePort: Option.none(),
1071 },
1072 Option.none(),
1073 ).pipe(
1074 Effect.provide(
1075 Layer.mergeAll(
1076 ConfigProvider.layer(
1077 ConfigProvider.fromEnv({
1078 env: { T3CODE_OTLP_LOGS_URL: "http://collector.internal:4318/v1/logs" },
1079 }),
1080 ),
1081 NetService.layer,
1082 ),
1083 ),
1084 );
1085
1086 expect(resolved.otlpLogsUrl).toBe("http://collector.internal:4318/v1/logs");
1087 }),
1088 );
1089
1090 const minimalWebFlags = (baseDir: string) => ({
1091 mode: Option.some("web" as const),
1092 port: Option.some(3773),
1093 host: Option.none<string>(),
1094 baseDir: Option.some(baseDir),
1095 cwd: Option.none<string>(),
1096 devUrl: Option.none<URL>(),
1097 noBrowser: Option.none<boolean>(),
1098 bootstrapFd: Option.none<number>(),
1099 autoBootstrapProjectFromCwd: Option.none<boolean>(),
1100 logWebSocketEvents: Option.none<boolean>(),
1101 tailscaleServeEnabled: Option.none<boolean>(),
1102 tailscaleServePort: Option.none<number>(),
1103 });
1104
1105 it.effect(
1106 "resolves each signal's endpoint through T3CODE_OTLP_*_URL, an OTEL endpoint, the bootstrap envelope, and persisted Settings, in that order",
1107 () =>
1108 Effect.gen(function* () {
1109 const fs = yield* FileSystem.FileSystem;
1110 const path = yield* Path.Path;
1111 const baseDir = yield* fs.makeTempDirectoryScoped({
1112 prefix: "t3-cli-config-otel-precedence-",
1113 });
1114 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
1115 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
1116 yield* fs.writeFileString(
1117 derivedPaths.settingsPath,
1118 `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`,
1119 );
1120
1121 const fd = yield* openBootstrapFd(
1122 makeDesktopBootstrap({
1123 otlpMetricsUrl: "http://bootstrap:4318/v1/metrics",
1124 // Blank, not an endpoint: it must not stand in front of Settings.
1125 otlpLogsUrl: "",
1126 }),
1127 );
1128
1129 const resolved = yield* resolveServerConfig(
1130 {
1131 ...minimalWebFlags(baseDir),
1132 mode: Option.some("desktop"),
1133 port: Option.some(4888),
1134 bootstrapFd: Option.some(fd),
1135 },
1136 Option.none(),
1137 ).pipe(
1138 Effect.provide(
1139 Layer.mergeAll(
1140 ConfigProvider.layer(
1141 ConfigProvider.fromEnv({
1142 env: {
1143 T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces",
1144 T3CODE_OTLP_HEADERS: "x-key=secret",
1145 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-traces:4318/custom",
1146 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "http://otel-metrics:4318/custom",
1147 OTEL_EXPORTER_OTLP_HEADERS: "x-key=otel",
1148 },
1149 }),
1150 ),
1151 NetService.layer,
1152 ),
1153 ),
1154 );
1155
1156 // T3CODE_OTLP_TRACES_URL wins over the OTEL variable for the same
1157 // signal, and keeps T3 Code's own headers since T3 Code still owns it.
1158 expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces");
1159 expect(resolved.otlpTracesExport.headers).toEqual({ "x-key": "secret" });
1160 // Metrics named no T3CODE_OTLP_METRICS_URL, so the OTEL endpoint wins
1161 // over the bootstrap envelope and brings the OTEL headers and protocol.
1162 expect(resolved.otlpMetricsUrl).toBe("http://otel-metrics:4318/custom");
1163 expect(resolved.otlpMetricsExport).toEqual({
1164 ...DEFAULT_SIGNAL_EXPORT,
1165 protocol: "http/protobuf",
1166 headers: { "x-key": "otel" },
1167 });
1168 // Logs named no T3 or OTEL endpoint and a blank bootstrap value, so
1169 // Settings answers, and logs keep the shared headers since no OTEL
1170 // endpoint claimed them.
1171 expect(resolved.otlpLogsUrl).toBe("http://settings:4318/v1/logs");
1172 expect(resolved.otlpLogsExport.headers).toEqual({ "x-key": "secret" });
1173 }),
1174 );
1175
1176 it.effect(
1177 "exports nothing for a signal an OTEL endpoint claimed with a protocol or headers that do not read",
1178 () =>
1179 Effect.gen(function* () {
1180 const fs = yield* FileSystem.FileSystem;
1181 const path = yield* Path.Path;
1182 const baseDir = yield* fs.makeTempDirectoryScoped({
1183 prefix: "t3-cli-config-otel-off-",
1184 });
1185 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
1186 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
1187 yield* fs.writeFileString(
1188 derivedPaths.settingsPath,
1189 `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`,
1190 );
1191
1192 const fd = yield* openBootstrapFd(
1193 makeDesktopBootstrap({ otlpMetricsUrl: "http://bootstrap:4318/v1/metrics" }),
1194 );
1195
1196 const resolved = yield* resolveServerConfig(
1197 {
1198 ...minimalWebFlags(baseDir),
1199 mode: Option.some("desktop"),
1200 port: Option.some(4888),
1201 bootstrapFd: Option.some(fd),
1202 },
1203 Option.none(),
1204 ).pipe(
1205 Effect.provide(
1206 Layer.mergeAll(
1207 ConfigProvider.layer(
1208 ConfigProvider.fromEnv({
1209 env: {
1210 T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces",
1211 OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel:4318",
1212 OTEL_EXPORTER_OTLP_HEADERS: "x-key=%zz",
1213 },
1214 }),
1215 ),
1216 NetService.layer,
1217 ),
1218 ),
1219 );
1220
1221 // T3CODE_OTLP_TRACES_URL still wins outright.
1222 expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces");
1223 // The OTEL endpoint claimed metrics and logs, so neither the bootstrap
1224 // envelope nor Settings receives them with T3 Code's headers.
1225 expect(resolved.otlpMetricsUrl).toBeUndefined();
1226 expect(resolved.otlpLogsUrl).toBeUndefined();
1227 }),
1228 );
1229});