1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeChildProcess from "node:child_process";
3import * as NodeEvents from "node:events";
4import * as NodeFS from "node:fs";
5import * as NodeOS from "node:os";
6
7import { assert, expect, it } from "@effect/vitest";
8import * as ConfigProvider from "effect/ConfigProvider";
9import * as Effect from "effect/Effect";
10import * as FileSystem from "effect/FileSystem";
11import * as Layer from "effect/Layer";
12import * as Option from "effect/Option";
13import * as Path from "effect/Path";
14import * as Redacted from "effect/Redacted";
15import * as Result from "effect/Result";
16import * as Schema from "effect/Schema";
17
18import {
19 DesktopBackendBootstrap,
20 type DesktopBackendBootstrap as DesktopBackendBootstrapValue,
21} from "@t3tools/contracts";
22import * as NetService from "@t3tools/shared/Net";
23import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
24import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
25import * as NodeServices from "@effect/platform-node/NodeServices";
26import * as HostProcess from "@t3tools/shared/HostProcess";
27import packageJson from "../../package.json" with { type: "json" };
28import { ServiceLauncherHostProcess } from "../cloud/serviceLauncherClient.ts";
29import {
30 SERVICE_LAUNCHER_CONTEXT_ENV,
31 SERVICE_LAUNCHER_PROTOCOL,
32} from "../cloud/serviceProtocol.ts";
33import { deriveServerPaths } from "../config.ts";
34import { resolveServerConfig } from "./config.ts";
35
36const deriveExplicitServerPaths = (baseDir: string, devUrl: URL | undefined) =>
37 deriveServerPaths(baseDir, devUrl, { baseDirIsExplicit: true });
38
39const minimalDesktopFlags = (baseDir: string) => ({
40 mode: Option.some("desktop" as const),
41 port: Option.some(4888),
42 host: Option.none<string>(),
43 baseDir: Option.some(baseDir),
44 cwd: Option.none<string>(),
45 devUrl: Option.none<URL>(),
46 noBrowser: Option.none<boolean>(),
47 bootstrapFd: Option.none<number>(),
48 autoBootstrapProjectFromCwd: Option.none<boolean>(),
49 logWebSocketEvents: Option.none<boolean>(),
50 tailscaleServeEnabled: Option.none<boolean>(),
51 tailscaleServePort: Option.none<number>(),
52 lan: Option.none<boolean>(),
53});
54
55const encodeDesktopBootstrap = Schema.encodeEffect(Schema.fromJsonString(DesktopBackendBootstrap));
56const encodeUnknownJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
57
58const makeDesktopBootstrap = (
59 overrides: Partial<DesktopBackendBootstrapValue> = {},
60): DesktopBackendBootstrapValue => ({
61 mode: "desktop",
62 noBrowser: true,
63 port: 4888,
64 t3Home: "/tmp/t3-bootstrap-home",
65 host: "127.0.0.1",
66 desktopBootstrapToken: "desktop-bootstrap-token",
67 tailscaleServeEnabled: false,
68 tailscaleServePort: 443,
69 ...overrides,
70});
71
72const liveProcess = Effect.acquireRelease(
73 Effect.promise(async () => {
74 const child = NodeChildProcess.spawn(process.execPath, ["-e", "process.stdin.resume()"], {
75 stdio: ["pipe", "ignore", "ignore"],
76 });
77 await NodeEvents.EventEmitter.once(child, "spawn");
78 return child;
79 }),
80 (child) =>
81 Effect.promise(async () => {
82 const exited = NodeEvents.EventEmitter.once(child, "exit");
83 child.kill();
84 await exited;
85 }),
86);
87
88it.layer(NodeServices.layer)("cli config resolution", (it) => {
89 const defaultObservabilityConfig = {
90 traceMinLevel: "Info",
91 traceTimingEnabled: true,
92 traceBatchWindowMs: 1_000,
93 traceMaxBytes: 10 * 1024 * 1024,
94 traceMaxFiles: 10,
95 otlpTracesUrl: undefined,
96 otlpMetricsUrl: undefined,
97 otlpLogsUrl: undefined,
98 otlpTracesExport: DEFAULT_SIGNAL_EXPORT,
99 otlpMetricsExport: DEFAULT_SIGNAL_EXPORT,
100 otlpLogsExport: DEFAULT_SIGNAL_EXPORT,
101 otelEnvironment: OtelEnvironment.none,
102 devAllowedOrigins: [],
103 } as const;
104
105 const openBootstrapFd = Effect.fn(function* (payload: DesktopBackendBootstrapValue) {
106 const fs = yield* FileSystem.FileSystem;
107 const filePath = yield* fs.makeTempFileScoped({ prefix: "t3-bootstrap-", suffix: ".ndjson" });
108 const encoded = yield* encodeDesktopBootstrap(payload);
109 yield* fs.writeFileString(filePath, `${encoded}\n`);
110 return yield* Effect.acquireRelease(
111 Effect.sync(() => NodeFS.openSync(filePath, "r")),
112 // Windows cannot reopen the fd through /proc or /dev/fd, so the reader closes it.
113 (fd) =>
114 Effect.sync(() => {
115 try {
116 NodeFS.closeSync(fd);
117 } catch (error) {
118 if ((error as NodeJS.ErrnoException).code !== "EBADF") throw error;
119 }
120 }),
121 );
122 });
123
124 it.effect("keeps stale records and supervised startup out of the manual launch preflight", () =>
125 Effect.gen(function* () {
126 const fs = yield* FileSystem.FileSystem;
127 const path = yield* Path.Path;
128 const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-preflight-" });
129 const child = yield* liveProcess;
130 assert(child.pid !== undefined);
131 for (const [name, pid, mode, rejectRunningServer] of [
132 ["stale", 2_147_483_647, "web", true],
133 ["own-pid", process.pid, "web", true],
134 ["desktop", child.pid, "desktop", true],
135 ["serve", child.pid, "web", false],
136 ] as const) {
137 const baseDir = path.join(root, name);
138 const stateDir = path.join(baseDir, "userdata");
139 yield* fs.makeDirectory(stateDir, { recursive: true });
140 yield* fs.writeFileString(
141 path.join(stateDir, "server-runtime.json"),
142 yield* encodeUnknownJson({
143 version: 1,
144 pid,
145 port: 3773,
146 origin: "http://127.0.0.1:3773",
147 startedAt: "2026-10-01T00:00:00.000Z",
148 }),
149 );
150 const cwd = path.join(root, `${name}-project`);
151 const configResult = yield* resolveServerConfig(
152 {
153 ...minimalWebFlags(baseDir),
154 mode: Option.some(mode),
155 port: Option.some(8788),
156 cwd: Option.some(cwd),
157 },
158 Option.none(),
159 { rejectRunningServer },
160 ).pipe(
161 Effect.provide(
162 Layer.merge(
163 NetService.layer,
164 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
165 ),
166 ),
167 Effect.result,
168 );
169 assert(Result.isSuccess(configResult));
170 expect(configResult.success.cwd).toBe(cwd);
171 expect(yield* fs.exists(cwd)).toBe(true);
172 }
173 }),
174 );
175
176 it.effect(
177 "refuses a manual web launch when a runtime record belongs to another live process",
178 () =>
179 Effect.gen(function* () {
180 const fs = yield* FileSystem.FileSystem;
181 const path = yield* Path.Path;
182 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-preflight-live-" });
183 const child = yield* liveProcess;
184 assert(child.pid !== undefined);
185 const stateDir = path.join(baseDir, "userdata");
186 yield* fs.makeDirectory(stateDir, { recursive: true });
187 yield* fs.writeFileString(
188 path.join(stateDir, "server-runtime.json"),
189 yield* encodeUnknownJson({
190 version: 1,
191 pid: child.pid,
192 port: 3773,
193 origin: "http://127.0.0.1:3773",
194 startedAt: "2026-10-01T00:00:00.000Z",
195 }),
196 );
197 const cwd = path.join(baseDir, "project");
198 const error = yield* resolveServerConfig(
199 { ...minimalWebFlags(baseDir), cwd: Option.some(cwd) },
200 Option.none(),
201 { rejectRunningServer: true },
202 ).pipe(
203 Effect.provide(
204 Layer.merge(
205 NetService.layer,
206 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
207 ),
208 ),
209 Effect.flip,
210 );
211 expect(String(error)).toContain("A T3 Code server is already running");
212 expect(String(error)).toContain(`pid ${child.pid}`);
213 expect(yield* fs.exists(cwd)).toBe(false);
214 }),
215 );
216
217 it.effect("enables a trimmed reusable auth token only for web dev mode", () =>
218 Effect.gen(function* () {
219 const baseDir = yield* FileSystem.FileSystem.pipe(
220 Effect.flatMap((fs) => fs.makeTempDirectoryScoped({ prefix: "t3-cli-dev-auth-" })),
221 );
222 const flags = {
223 mode: Option.some("web" as const),
224 port: Option.some(8788),
225 host: Option.none<string>(),
226 baseDir: Option.some(baseDir),
227 cwd: Option.none<string>(),
228 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
229 noBrowser: Option.none<boolean>(),
230 bootstrapFd: Option.none<number>(),
231 autoBootstrapProjectFromCwd: Option.none<boolean>(),
232 logWebSocketEvents: Option.none<boolean>(),
233 tailscaleServeEnabled: Option.none<boolean>(),
234 tailscaleServePort: Option.none<number>(),
235 lan: Option.none<boolean>(),
236 };
237 const layerConfig = ConfigProvider.layer(
238 ConfigProvider.fromEnv({
239 env: {
240 T3CODE_DEV_AUTH_TOKEN: " reusable-dev-auth-token-that-is-long-enough ",
241 },
242 }),
243 );
244 const web = yield* resolveServerConfig(flags, Option.none()).pipe(
245 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
246 );
247 const desktop = yield* resolveServerConfig(
248 { ...flags, mode: Option.some("desktop" as const) },
249 Option.none(),
250 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
251
252 expect(web.devAuthToken).toBeDefined();
253 if (web.devAuthToken === undefined) {
254 return yield* Effect.die("Expected reusable dev auth token.");
255 }
256 expect(Redacted.value(web.devAuthToken)).toBe("reusable-dev-auth-token-that-is-long-enough");
257 expect(desktop.devAuthToken).toBeUndefined();
258 }),
259 );
260
261 it.effect("does not expose an invalid reusable auth token", () =>
262 Effect.gen(function* () {
263 const secret = "short-secret";
264 const baseDir = yield* FileSystem.FileSystem.pipe(
265 Effect.flatMap((fs) => fs.makeTempDirectoryScoped({ prefix: "t3-cli-dev-auth-invalid-" })),
266 );
267 const flags = {
268 mode: Option.some("web" as const),
269 port: Option.some(8788),
270 host: Option.none<string>(),
271 baseDir: Option.some(baseDir),
272 cwd: Option.none<string>(),
273 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
274 noBrowser: Option.none<boolean>(),
275 bootstrapFd: Option.none<number>(),
276 autoBootstrapProjectFromCwd: Option.none<boolean>(),
277 logWebSocketEvents: Option.none<boolean>(),
278 tailscaleServeEnabled: Option.none<boolean>(),
279 tailscaleServePort: Option.none<number>(),
280 lan: Option.none<boolean>(),
281 };
282 const layerConfig = ConfigProvider.layer(
283 ConfigProvider.fromEnv({ env: { T3CODE_DEV_AUTH_TOKEN: secret } }),
284 );
285 const error = yield* resolveServerConfig(flags, Option.none()).pipe(
286 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
287 Effect.flip,
288 );
289 const desktop = yield* resolveServerConfig(
290 { ...flags, mode: Option.some("desktop" as const) },
291 Option.none(),
292 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
293 const staticWeb = yield* resolveServerConfig(
294 { ...flags, devUrl: Option.none() },
295 Option.none(),
296 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
297
298 expect(String(error)).not.toContain(secret);
299 const serialized = yield* encodeUnknownJson(error);
300 expect(serialized).not.toContain(secret);
301 expect(desktop.devAuthToken).toBeUndefined();
302 expect(staticWeb.devAuthToken).toBeUndefined();
303 }),
304 );
305
306 it.effect("preserves explicit false CLI boolean flags over env and bootstrap values", () =>
307 Effect.gen(function* () {
308 const { join } = yield* Path.Path;
309 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-false-flags");
310 const fd = yield* openBootstrapFd(
311 makeDesktopBootstrap({
312 noBrowser: true,
313 tailscaleServeEnabled: false,
314 tailscaleServePort: 443,
315 }),
316 );
317 const derivedPaths = yield* deriveExplicitServerPaths(
318 baseDir,
319 new URL("http://127.0.0.1:4173"),
320 );
321
322 const resolved = yield* resolveServerConfig(
323 {
324 mode: Option.some("web"),
325 port: Option.some(8788),
326 host: Option.some("127.0.0.1"),
327 baseDir: Option.some(baseDir),
328 cwd: Option.none(),
329 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
330 noBrowser: Option.some(false),
331 bootstrapFd: Option.none(),
332 autoBootstrapProjectFromCwd: Option.some(false),
333 logWebSocketEvents: Option.some(false),
334 tailscaleServeEnabled: Option.none(),
335 tailscaleServePort: Option.none(),
336 lan: Option.none(),
337 },
338 Option.none(),
339 ).pipe(
340 Effect.provide(
341 Layer.mergeAll(
342 ConfigProvider.layer(
343 ConfigProvider.fromEnv({
344 env: {
345 T3CODE_BOOTSTRAP_FD: String(fd),
346 T3CODE_NO_BROWSER: "true",
347 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
348 T3CODE_LOG_WS_EVENTS: "true",
349 },
350 }),
351 ),
352 NetService.layer,
353 ),
354 ),
355 );
356
357 expect(resolved).toEqual({
358 logLevel: "Info",
359 ...defaultObservabilityConfig,
360 mode: "web",
361 port: 8788,
362 cwd: process.cwd(),
363 baseDir,
364 ...derivedPaths,
365 host: "127.0.0.1",
366 staticDir: undefined,
367 devUrl: new URL("http://127.0.0.1:4173"),
368 noBrowser: false,
369 startupPresentation: "browser",
370 desktopBootstrapToken: "desktop-bootstrap-token",
371 autoBootstrapProjectFromCwd: false,
372 logWebSocketEvents: false,
373 tailscaleServeEnabled: false,
374 tailscaleServePort: 443,
375 });
376 }),
377 );
378
379 it.effect("applies flag then env precedence over bootstrap envelope values", () =>
380 Effect.gen(function* () {
381 const { join } = yield* Path.Path;
382 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-wins");
383 const fd = yield* openBootstrapFd(
384 makeDesktopBootstrap({
385 port: 4888,
386 host: "127.0.0.2",
387 t3Home: "/tmp/t3-bootstrap-home",
388 noBrowser: false,
389 desktopBootstrapToken: "desktop-token",
390 tailscaleServeEnabled: false,
391 tailscaleServePort: 443,
392 }),
393 );
394 const derivedPaths = yield* deriveExplicitServerPaths(
395 baseDir,
396 new URL("http://127.0.0.1:4173"),
397 );
398
399 const resolved = yield* resolveServerConfig(
400 {
401 mode: Option.none(),
402 port: Option.some(8788),
403 host: Option.some("127.0.0.1"),
404 baseDir: Option.none(),
405 cwd: Option.none(),
406 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
407 noBrowser: Option.none(),
408 bootstrapFd: Option.none(),
409 autoBootstrapProjectFromCwd: Option.none(),
410 logWebSocketEvents: Option.none(),
411 tailscaleServeEnabled: Option.none(),
412 tailscaleServePort: Option.none(),
413 lan: Option.none(),
414 },
415 Option.some("Debug"),
416 ).pipe(
417 Effect.provide(
418 Layer.mergeAll(
419 ConfigProvider.layer(
420 ConfigProvider.fromEnv({
421 env: {
422 T3CODE_MODE: "web",
423 T3CODE_BOOTSTRAP_FD: String(fd),
424 T3CODE_HOME: baseDir,
425 T3CODE_NO_BROWSER: "true",
426 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
427 T3CODE_LOG_WS_EVENTS: "true",
428 },
429 }),
430 ),
431 NetService.layer,
432 ),
433 ),
434 );
435
436 expect(resolved).toEqual({
437 logLevel: "Debug",
438 ...defaultObservabilityConfig,
439 mode: "web",
440 port: 8788,
441 cwd: process.cwd(),
442 baseDir,
443 ...derivedPaths,
444 host: "127.0.0.1",
445 staticDir: undefined,
446 devUrl: new URL("http://127.0.0.1:4173"),
447 noBrowser: true,
448 startupPresentation: "browser",
449 desktopBootstrapToken: "desktop-token",
450 autoBootstrapProjectFromCwd: true,
451 logWebSocketEvents: true,
452 tailscaleServeEnabled: false,
453 tailscaleServePort: 443,
454 });
455 }),
456 );
457
458 it.effect("reports the desktop bootstrap as the source of endpoints it supplied", () =>
459 Effect.gen(function* () {
460 const fs = yield* FileSystem.FileSystem;
461 const baseDir = yield* fs.makeTempDirectoryScoped({
462 prefix: "t3-cli-config-otlp-bootstrap-",
463 });
464 const fd = yield* openBootstrapFd(
465 makeDesktopBootstrap({
466 otlpTracesUrl: "http://localhost:4318/v1/traces",
467 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
468 otlpLogsUrl: "http://localhost:4318/v1/logs",
469 }),
470 );
471
472 const resolved = yield* resolveServerConfig(
473 { ...minimalDesktopFlags(baseDir), bootstrapFd: Option.some(fd) },
474 Option.none(),
475 ).pipe(
476 Effect.provide(
477 Layer.mergeAll(
478 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
479 NetService.layer,
480 ),
481 ),
482 );
483
484 expect(resolved).toMatchObject({
485 otlpTracesUrl: "http://localhost:4318/v1/traces",
486 otlpTracesSource: "bootstrap",
487 otlpMetricsSource: "bootstrap",
488 otlpLogsSource: "bootstrap",
489 });
490 }),
491 );
492
493 it.effect("reports saved settings as the source of endpoints read from settings.json", () =>
494 Effect.gen(function* () {
495 const fs = yield* FileSystem.FileSystem;
496 const path = yield* Path.Path;
497 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-otlp-settings-" });
498 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
499 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
500 yield* fs.writeFileString(
501 derivedPaths.settingsPath,
502 `${JSON.stringify({
503 observability: {
504 otlpTracesUrl: "http://localhost:4318/v1/traces",
505 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
506 otlpLogsUrl: "http://localhost:4318/v1/logs",
507 },
508 })}\n`,
509 );
510
511 const resolved = yield* resolveServerConfig(minimalDesktopFlags(baseDir), Option.none()).pipe(
512 Effect.provide(
513 Layer.mergeAll(
514 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
515 NetService.layer,
516 ),
517 ),
518 );
519
520 expect(resolved).toMatchObject({
521 otlpTracesUrl: "http://localhost:4318/v1/traces",
522 otlpTracesSource: "settings",
523 otlpMetricsSource: "settings",
524 otlpLogsSource: "settings",
525 });
526 }),
527 );
528
529 it.effect("zeroes an endpoint stored in Settings when the SDK is disabled", () =>
530 Effect.gen(function* () {
531 const fs = yield* FileSystem.FileSystem;
532 const path = yield* Path.Path;
533 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-otel-off-" });
534 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
535 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
536 yield* fs.writeFileString(
537 derivedPaths.settingsPath,
538 `${JSON.stringify({
539 observability: {
540 otlpTracesUrl: "http://localhost:4318/v1/traces",
541 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
542 otlpLogsUrl: "http://localhost:4318/v1/logs",
543 },
544 })}\n`,
545 );
546
547 const resolved = yield* resolveServerConfig(
548 {
549 mode: Option.some("desktop"),
550 port: Option.some(4888),
551 host: Option.none(),
552 baseDir: Option.some(baseDir),
553 cwd: Option.none(),
554 devUrl: Option.none(),
555 noBrowser: Option.none(),
556 bootstrapFd: Option.none(),
557 autoBootstrapProjectFromCwd: Option.none(),
558 logWebSocketEvents: Option.none(),
559 tailscaleServeEnabled: Option.none(),
560 tailscaleServePort: Option.none(),
561 lan: Option.none(),
562 },
563 Option.none(),
564 ).pipe(
565 Effect.provide(
566 Layer.mergeAll(
567 ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
568 NetService.layer,
569 ),
570 ),
571 );
572
573 expect(resolved.otlpTracesUrl).toBeUndefined();
574 expect(resolved.otlpMetricsUrl).toBeUndefined();
575 expect(resolved.otlpLogsUrl).toBeUndefined();
576 expect(resolved.otelEnvironment.disabled).toBe(true);
577 }),
578 );
579
580 it.effect("lets T3CODE_OTEL_SDK_DISABLED=false override an ambient OTEL_SDK_DISABLED=true", () =>
581 Effect.gen(function* () {
582 const fs = yield* FileSystem.FileSystem;
583 const path = yield* Path.Path;
584 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-otel-on-" });
585 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
586 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
587 yield* fs.writeFileString(
588 derivedPaths.settingsPath,
589 `${JSON.stringify({
590 observability: {
591 otlpTracesUrl: "http://localhost:4318/v1/traces",
592 },
593 })}\n`,
594 );
595
596 const resolved = yield* resolveServerConfig(
597 {
598 mode: Option.some("desktop"),
599 port: Option.some(4888),
600 host: Option.none(),
601 baseDir: Option.some(baseDir),
602 cwd: Option.none(),
603 devUrl: Option.none(),
604 noBrowser: Option.none(),
605 bootstrapFd: Option.none(),
606 autoBootstrapProjectFromCwd: Option.none(),
607 logWebSocketEvents: Option.none(),
608 tailscaleServeEnabled: Option.none(),
609 tailscaleServePort: Option.none(),
610 lan: Option.none(),
611 },
612 Option.none(),
613 ).pipe(
614 Effect.provide(
615 Layer.mergeAll(
616 ConfigProvider.layer(
617 ConfigProvider.fromEnv({
618 env: { T3CODE_OTEL_SDK_DISABLED: "false", OTEL_SDK_DISABLED: "true" },
619 }),
620 ),
621 NetService.layer,
622 ),
623 ),
624 );
625
626 expect(resolved.otelEnvironment.disabled).toBe(false);
627 expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
628 }),
629 );
630
631 it.effect("forces noBrowser and disables auto-bootstrap for headless startup presentation", () =>
632 Effect.gen(function* () {
633 const { join } = yield* Path.Path;
634 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-headless-base");
635 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
636
637 const resolved = yield* resolveServerConfig(
638 {
639 mode: Option.some("web"),
640 port: Option.some(3773),
641 host: Option.none(),
642 baseDir: Option.some(baseDir),
643 cwd: Option.none(),
644 devUrl: Option.none(),
645 noBrowser: Option.none(),
646 bootstrapFd: Option.none(),
647 autoBootstrapProjectFromCwd: Option.none(),
648 logWebSocketEvents: Option.none(),
649 tailscaleServeEnabled: Option.none(),
650 tailscaleServePort: Option.none(),
651 lan: Option.none(),
652 },
653 Option.none(),
654 {
655 startupPresentation: "headless",
656 },
657 ).pipe(
658 Effect.provide(
659 Layer.mergeAll(
660 ConfigProvider.layer(
661 ConfigProvider.fromEnv({
662 env: {
663 T3CODE_NO_BROWSER: "false",
664 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "true",
665 },
666 }),
667 ),
668 NetService.layer,
669 ),
670 ),
671 );
672
673 expect(resolved).toEqual({
674 logLevel: "Info",
675 ...defaultObservabilityConfig,
676 mode: "web",
677 port: 3773,
678 cwd: process.cwd(),
679 baseDir,
680 ...derivedPaths,
681 host: undefined,
682 staticDir: resolved.staticDir,
683 devUrl: undefined,
684 noBrowser: true,
685 startupPresentation: "headless",
686 desktopBootstrapToken: undefined,
687 autoBootstrapProjectFromCwd: false,
688 logWebSocketEvents: false,
689 tailscaleServeEnabled: false,
690 tailscaleServePort: 443,
691 });
692 }),
693 );
694
695 it.effect("keeps whitespace-separated pairs and literal equals signs in OTLP headers", () =>
696 Effect.gen(function* () {
697 const { join } = yield* Path.Path;
698 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-headers-loose-base");
699
700 const resolved = yield* resolveServerConfig(
701 {
702 mode: Option.some("web"),
703 port: Option.some(3773),
704 host: Option.none(),
705 baseDir: Option.some(baseDir),
706 cwd: Option.none(),
707 devUrl: Option.none(),
708 noBrowser: Option.none(),
709 bootstrapFd: Option.none(),
710 autoBootstrapProjectFromCwd: Option.none(),
711 logWebSocketEvents: Option.none(),
712 tailscaleServeEnabled: Option.none(),
713 tailscaleServePort: Option.none(),
714 lan: Option.none(),
715 },
716 Option.none(),
717 ).pipe(
718 Effect.provide(
719 Layer.mergeAll(
720 ConfigProvider.layer(
721 ConfigProvider.fromEnv({
722 env: {
723 T3CODE_OTLP_HEADERS: "authorization=Bearer abc==, x-tenant=t3",
724 T3CODE_OTLP_TRACES_URL: "http://collector.internal:4318",
725 },
726 }),
727 ),
728 NetService.layer,
729 ),
730 ),
731 );
732
733 expect(resolved.otlpTracesExport.headers).toEqual({
734 authorization: "Bearer abc==",
735 "x-tenant": "t3",
736 });
737 expect(resolved.otlpTracesUrl).toBe("http://collector.internal:4318");
738 }),
739 );
740
741 const minimalWebFlags = (baseDir: string) => ({
742 mode: Option.some("web" as const),
743 port: Option.some(3773),
744 host: Option.none<string>(),
745 baseDir: Option.some(baseDir),
746 cwd: Option.none<string>(),
747 devUrl: Option.none<URL>(),
748 noBrowser: Option.none<boolean>(),
749 bootstrapFd: Option.none<number>(),
750 autoBootstrapProjectFromCwd: Option.none<boolean>(),
751 logWebSocketEvents: Option.none<boolean>(),
752 tailscaleServeEnabled: Option.none<boolean>(),
753 tailscaleServePort: Option.none<number>(),
754 lan: Option.none<boolean>(),
755 });
756
757 it.effect("uses the automatic port selector only when no port is configured", () =>
758 Effect.gen(function* () {
759 const baseDir = yield* FileSystem.FileSystem.pipe(
760 Effect.flatMap((fs) => fs.makeTempDirectoryScoped({ prefix: "t3-cli-auto-port-" })),
761 );
762 const selected: number[] = [];
763 const selectAutomaticPort = (preferred: number) =>
764 Effect.sync(() => {
765 selected.push(preferred);
766 return 50_123;
767 });
768 const resolvePort = (flags: {
769 readonly mode?: Option.Option<"web" | "desktop">;
770 readonly port: Option.Option<number>;
771 }) =>
772 resolveServerConfig({ ...minimalWebFlags(baseDir), ...flags }, Option.none(), {
773 selectAutomaticPort,
774 }).pipe(
775 Effect.map((config) => config.port),
776 Effect.provide(
777 Layer.merge(
778 NetService.layer,
779 ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })),
780 ),
781 ),
782 );
783
784 expect(yield* resolvePort({ port: Option.none() })).toBe(50_123);
785 expect(selected).toEqual([3773]);
786 expect(yield* resolvePort({ port: Option.some(8788) })).toBe(8788);
787 expect(
788 yield* resolvePort({ mode: Option.some("desktop" as const), port: Option.none() }),
789 ).toBe(3773);
790 expect(selected).toEqual([3773]);
791 }),
792 );
793
794 it.effect(
795 "resolves each signal's endpoint through T3CODE_OTLP_*_URL, an OTEL endpoint, the bootstrap envelope, and persisted Settings, in that order",
796 () =>
797 Effect.gen(function* () {
798 const fs = yield* FileSystem.FileSystem;
799 const path = yield* Path.Path;
800 const baseDir = yield* fs.makeTempDirectoryScoped({
801 prefix: "t3-cli-config-otel-precedence-",
802 });
803 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
804 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
805 yield* fs.writeFileString(
806 derivedPaths.settingsPath,
807 `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`,
808 );
809
810 const fd = yield* openBootstrapFd(
811 makeDesktopBootstrap({
812 otlpMetricsUrl: "http://bootstrap:4318/v1/metrics",
813 otlpLogsUrl: "",
814 }),
815 );
816
817 const resolved = yield* resolveServerConfig(
818 {
819 ...minimalWebFlags(baseDir),
820 mode: Option.some("desktop"),
821 port: Option.some(4888),
822 bootstrapFd: Option.some(fd),
823 },
824 Option.none(),
825 ).pipe(
826 Effect.provide(
827 Layer.mergeAll(
828 ConfigProvider.layer(
829 ConfigProvider.fromEnv({
830 env: {
831 T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces",
832 T3CODE_OTLP_HEADERS: "x-key=secret",
833 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-traces:4318/custom",
834 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "http://otel-metrics:4318/custom",
835 OTEL_EXPORTER_OTLP_HEADERS: "x-key=otel",
836 },
837 }),
838 ),
839 NetService.layer,
840 ),
841 ),
842 );
843
844 expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces");
845 expect(resolved.otlpTracesExport.headers).toEqual({ "x-key": "secret" });
846 expect(resolved.otlpMetricsUrl).toBe("http://otel-metrics:4318/custom");
847 expect(resolved.otlpMetricsExport).toEqual({
848 ...DEFAULT_SIGNAL_EXPORT,
849 protocol: "http/protobuf",
850 headers: { "x-key": "otel" },
851 });
852 expect(resolved.otlpLogsUrl).toBe("http://settings:4318/v1/logs");
853 expect(resolved.otlpLogsExport.headers).toEqual({ "x-key": "secret" });
854 expect(resolved).toMatchObject({
855 otlpTracesVariable: "T3CODE_OTLP_TRACES_URL",
856 otlpMetricsVariable: "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
857 });
858 expect(resolved.otlpLogsVariable).toBeUndefined();
859 }),
860 );
861
862 it.effect(
863 "exports nothing for a signal an OTEL endpoint claimed with a protocol or headers that do not read",
864 () =>
865 Effect.gen(function* () {
866 const fs = yield* FileSystem.FileSystem;
867 const path = yield* Path.Path;
868 const baseDir = yield* fs.makeTempDirectoryScoped({
869 prefix: "t3-cli-config-otel-off-",
870 });
871 const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined);
872 yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true });
873 yield* fs.writeFileString(
874 derivedPaths.settingsPath,
875 `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`,
876 );
877
878 const fd = yield* openBootstrapFd(
879 makeDesktopBootstrap({ otlpMetricsUrl: "http://bootstrap:4318/v1/metrics" }),
880 );
881
882 const resolved = yield* resolveServerConfig(
883 {
884 ...minimalWebFlags(baseDir),
885 mode: Option.some("desktop"),
886 port: Option.some(4888),
887 bootstrapFd: Option.some(fd),
888 },
889 Option.none(),
890 ).pipe(
891 Effect.provide(
892 Layer.mergeAll(
893 ConfigProvider.layer(
894 ConfigProvider.fromEnv({
895 env: {
896 T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces",
897 OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel:4318",
898 OTEL_EXPORTER_OTLP_HEADERS: "x-key=%zz",
899 },
900 }),
901 ),
902 NetService.layer,
903 ),
904 ),
905 );
906
907 expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces");
908 expect(resolved.otlpMetricsUrl).toBeUndefined();
909 expect(resolved.otlpLogsUrl).toBeUndefined();
910 }),
911 );
912
913 it.effect("listens on private networks per T3CODE_LAN, else under the service launcher", () =>
914 Effect.gen(function* () {
915 const { join } = yield* Path.Path;
916 const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-service-host");
917 const resolveHost = (input: {
918 readonly connected: boolean;
919 readonly env?: Record<string, string>;
920 }) =>
921 resolveServerConfig(minimalWebFlags(baseDir), Option.none()).pipe(
922 Effect.map(({ host, privateNetworkOnly }) => ({ host, privateNetworkOnly })),
923 Effect.provide(
924 Layer.mergeAll(
925 ConfigProvider.layer(ConfigProvider.fromEnv({ env: input.env ?? {} })),
926 NetService.layer,
927 ),
928 ),
929 Effect.provideService(HostProcess.Environment, {
930 [SERVICE_LAUNCHER_CONTEXT_ENV]: JSON.stringify({
931 protocol: SERVICE_LAUNCHER_PROTOCOL,
932 childVersion: packageJson.version,
933 }),
934 }),
935 Effect.provideService(ServiceLauncherHostProcess, {
936 connected: input.connected,
937 send: () => false,
938 on: () => undefined,
939 off: () => undefined,
940 }),
941 );
942
943 // A service installed before T3CODE_LAN existed.
944 expect(yield* resolveHost({ connected: true })).toEqual({
945 host: "0.0.0.0",
946 privateNetworkOnly: true,
947 });
948 // `service.env` reaches the server as its environment.
949 expect(yield* resolveHost({ connected: true, env: { T3CODE_HOST: "127.0.0.1" } })).toEqual({
950 host: "127.0.0.1",
951 privateNetworkOnly: undefined,
952 });
953 expect(yield* resolveHost({ connected: true, env: { T3CODE_LAN: "false" } })).toEqual({
954 host: undefined,
955 privateNetworkOnly: undefined,
956 });
957 expect(yield* resolveHost({ connected: false, env: { T3CODE_LAN: "true" } })).toEqual({
958 host: "0.0.0.0",
959 privateNetworkOnly: true,
960 });
961 // Without the launcher's IPC channel the context was inherited, e.g. by
962 // `t3 serve` started from an agent inside the service.
963 expect(yield* resolveHost({ connected: false })).toEqual({
964 host: undefined,
965 privateNetworkOnly: undefined,
966 });
967 }),
968 );
969});