authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-25 20:21:20-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 13:20:36-08:00
log0c4c027e92bae27716d94d14e3531c390d9ecef1
tree0882fdf8fb3966279ec85163e5f3a2a85b273998
parentfefea0bdf6b09a596b768047bc8432af48ba1123

small config change


5 files changed, 99 insertions(+), 60 deletions(-)

apps/server/src/config.ts+2
...@@ -16,6 +16,8 @@ export interface ServerConfigShape {...@@ -16,6 +16,8 @@ export interface ServerConfigShape {
16 readonly devUrl: URL | undefined;16 readonly devUrl: URL | undefined;
17 readonly noBrowser: boolean;17 readonly noBrowser: boolean;
18 readonly authToken: string | undefined;18 readonly authToken: string | undefined;
19 readonly autoBootstrapProjectFromCwd: boolean;
20 readonly logWebSocketEvents: boolean;
19}21}
2022
21export class ServerConfig extends ServiceMap.Service<ServerConfig, ServerConfigShape>()(23export class ServerConfig extends ServiceMap.Service<ServerConfig, ServerConfigShape>()(
apps/server/src/main.test.ts+36-33
...@@ -10,20 +10,18 @@ import { beforeEach } from "vitest";...@@ -10,20 +10,18 @@ import { beforeEach } from "vitest";
10import { CliConfig, t3Cli, type CliConfigShape } from "./main";10import { CliConfig, t3Cli, type CliConfigShape } from "./main";
11import { NetService, ServerConfig, type ServerConfigShape } from "./config";11import { NetService, ServerConfig, type ServerConfigShape } from "./config";
12import { Open, type OpenShape } from "./open";12import { Open, type OpenShape } from "./open";
13import { Server, type ServerShape, type ServerOptions } from "./wsServer";13import { Server, type ServerShape } from "./wsServer";
1414
15const start = vi.fn(() => undefined);15const start = vi.fn(() => undefined);
16const stop = vi.fn(() => undefined);16const stop = vi.fn(() => undefined);
17let resolvedConfig: ServerConfigShape | null = null;17let resolvedConfig: ServerConfigShape | null = null;
18const createServer = vi.fn((_: ServerOptions | undefined) =>18const serverStart = Effect.acquireRelease(
19 Effect.acquireRelease(19 Effect.gen(function* () {
20 Effect.gen(function* () {20 resolvedConfig = yield* ServerConfig;
21 resolvedConfig = yield* ServerConfig;21 start();
22 start();22 return {} as unknown as Http.Server;
23 return {} as unknown as Http.Server;23 }),
24 }),24 () => Effect.sync(() => stop()),
25 () => Effect.sync(() => stop()),
26 ),
27);25);
28const findAvailablePort = vi.fn((preferred: number) => Effect.succeed(preferred));26const findAvailablePort = vi.fn((preferred: number) => Effect.succeed(preferred));
2927
...@@ -37,7 +35,7 @@ const testLayer = Layer.mergeAll(...@@ -37,7 +35,7 @@ const testLayer = Layer.mergeAll(
37 findAvailablePort,35 findAvailablePort,
38 }),36 }),
39 Layer.succeed(Server, {37 Layer.succeed(Server, {
40 createServer,38 start: serverStart,
41 stopSignal: Effect.void,39 stopSignal: Effect.void,
42 } satisfies ServerShape),40 } satisfies ServerShape),
43 Layer.succeed(Open, {41 Layer.succeed(Open, {
...@@ -60,16 +58,6 @@ beforeEach(() => {...@@ -60,16 +58,6 @@ beforeEach(() => {
60 resolvedConfig = null;58 resolvedConfig = null;
61 start.mockImplementation(() => undefined);59 start.mockImplementation(() => undefined);
62 stop.mockImplementation(() => undefined);60 stop.mockImplementation(() => undefined);
63 createServer.mockImplementation((_: ServerOptions | undefined) =>
64 Effect.acquireRelease(
65 Effect.gen(function* () {
66 resolvedConfig = yield* ServerConfig;
67 start();
68 return {} as unknown as Http.Server;
69 }),
70 () => Effect.sync(() => stop()),
71 ),
72 );
73 findAvailablePort.mockImplementation((preferred: number) => Effect.succeed(preferred));61 findAvailablePort.mockImplementation((preferred: number) => Effect.succeed(preferred));
74});62});
7563
...@@ -92,7 +80,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -92,7 +80,7 @@ it.layer(testLayer)("server cli", (it) => {
92 "auth-secret",80 "auth-secret",
93 ]);81 ]);
9482
95 assert.equal(createServer.mock.calls.length, 1);83 assert.equal(start.mock.calls.length, 1);
96 assert.equal(resolvedConfig?.mode, "desktop");84 assert.equal(resolvedConfig?.mode, "desktop");
97 assert.equal(resolvedConfig?.port, 4010);85 assert.equal(resolvedConfig?.port, 4010);
98 assert.equal(resolvedConfig?.host, "0.0.0.0");86 assert.equal(resolvedConfig?.host, "0.0.0.0");
...@@ -100,7 +88,8 @@ it.layer(testLayer)("server cli", (it) => {...@@ -100,7 +88,8 @@ it.layer(testLayer)("server cli", (it) => {
100 assert.equal(resolvedConfig?.devUrl?.toString(), "http://127.0.0.1:5173/");88 assert.equal(resolvedConfig?.devUrl?.toString(), "http://127.0.0.1:5173/");
101 assert.equal(resolvedConfig?.noBrowser, true);89 assert.equal(resolvedConfig?.noBrowser, true);
102 assert.equal(resolvedConfig?.authToken, "auth-secret");90 assert.equal(resolvedConfig?.authToken, "auth-secret");
103 assert.equal(start.mock.calls.length, 1);91 assert.equal(resolvedConfig?.autoBootstrapProjectFromCwd, false);
92 assert.equal(resolvedConfig?.logWebSocketEvents, true);
104 assert.equal(stop.mock.calls.length, 1);93 assert.equal(stop.mock.calls.length, 1);
105 }),94 }),
106 );95 );
...@@ -109,7 +98,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -109,7 +98,7 @@ it.layer(testLayer)("server cli", (it) => {
109 Effect.gen(function* () {98 Effect.gen(function* () {
110 yield* runCli(["--token", "token-secret"]);99 yield* runCli(["--token", "token-secret"]);
111100
112 assert.equal(createServer.mock.calls.length, 1);101 assert.equal(start.mock.calls.length, 1);
113 assert.equal(resolvedConfig?.authToken, "token-secret");102 assert.equal(resolvedConfig?.authToken, "token-secret");
114 }),103 }),
115 );104 );
...@@ -126,7 +115,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -126,7 +115,7 @@ it.layer(testLayer)("server cli", (it) => {
126 T3CODE_AUTH_TOKEN: "env-token",115 T3CODE_AUTH_TOKEN: "env-token",
127 });116 });
128117
129 assert.equal(createServer.mock.calls.length, 1);118 assert.equal(start.mock.calls.length, 1);
130 assert.equal(resolvedConfig?.mode, "desktop");119 assert.equal(resolvedConfig?.mode, "desktop");
131 assert.equal(resolvedConfig?.port, 4999);120 assert.equal(resolvedConfig?.port, 4999);
132 assert.equal(resolvedConfig?.host, "100.88.10.4");121 assert.equal(resolvedConfig?.host, "100.88.10.4");
...@@ -134,6 +123,8 @@ it.layer(testLayer)("server cli", (it) => {...@@ -134,6 +123,8 @@ it.layer(testLayer)("server cli", (it) => {
134 assert.equal(resolvedConfig?.devUrl?.toString(), "http://localhost:5173/");123 assert.equal(resolvedConfig?.devUrl?.toString(), "http://localhost:5173/");
135 assert.equal(resolvedConfig?.noBrowser, true);124 assert.equal(resolvedConfig?.noBrowser, true);
136 assert.equal(resolvedConfig?.authToken, "env-token");125 assert.equal(resolvedConfig?.authToken, "env-token");
126 assert.equal(resolvedConfig?.autoBootstrapProjectFromCwd, false);
127 assert.equal(resolvedConfig?.logWebSocketEvents, true);
137 assert.equal(findAvailablePort.mock.calls.length, 0);128 assert.equal(findAvailablePort.mock.calls.length, 0);
138 }),129 }),
139 );130 );
...@@ -147,7 +138,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -147,7 +138,7 @@ it.layer(testLayer)("server cli", (it) => {
147 });138 });
148139
149 assert.deepStrictEqual(findAvailablePort.mock.calls, [[3773]]);140 assert.deepStrictEqual(findAvailablePort.mock.calls, [[3773]]);
150 assert.equal(createServer.mock.calls.length, 1);141 assert.equal(start.mock.calls.length, 1);
151 assert.equal(resolvedConfig?.mode, "web");142 assert.equal(resolvedConfig?.mode, "web");
152 assert.equal(resolvedConfig?.port, 4666);143 assert.equal(resolvedConfig?.port, 4666);
153 assert.equal(resolvedConfig?.host, undefined);144 assert.equal(resolvedConfig?.host, undefined);
...@@ -160,7 +151,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -160,7 +151,7 @@ it.layer(testLayer)("server cli", (it) => {
160 T3CODE_NO_BROWSER: "false",151 T3CODE_NO_BROWSER: "false",
161 });152 });
162153
163 assert.equal(createServer.mock.calls.length, 1);154 assert.equal(start.mock.calls.length, 1);
164 assert.equal(resolvedConfig?.noBrowser, true);155 assert.equal(resolvedConfig?.noBrowser, true);
165 }),156 }),
166 );157 );
...@@ -171,7 +162,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -171,7 +162,7 @@ it.layer(testLayer)("server cli", (it) => {
171 yield* runCli([]);162 yield* runCli([]);
172163
173 assert.deepStrictEqual(findAvailablePort.mock.calls, [[3773]]);164 assert.deepStrictEqual(findAvailablePort.mock.calls, [[3773]]);
174 assert.equal(createServer.mock.calls.length, 1);165 assert.equal(start.mock.calls.length, 1);
175 assert.equal(resolvedConfig?.port, 5444);166 assert.equal(resolvedConfig?.port, 5444);
176 assert.equal(resolvedConfig?.mode, "web");167 assert.equal(resolvedConfig?.mode, "web");
177 }),168 }),
...@@ -185,7 +176,7 @@ it.layer(testLayer)("server cli", (it) => {...@@ -185,7 +176,7 @@ it.layer(testLayer)("server cli", (it) => {
185 });176 });
186177
187 assert.equal(findAvailablePort.mock.calls.length, 0);178 assert.equal(findAvailablePort.mock.calls.length, 0);
188 assert.equal(createServer.mock.calls.length, 1);179 assert.equal(start.mock.calls.length, 1);
189 assert.equal(resolvedConfig?.port, 3773);180 assert.equal(resolvedConfig?.port, 3773);
190 assert.equal(resolvedConfig?.host, "127.0.0.1");181 assert.equal(resolvedConfig?.host, "127.0.0.1");
191 assert.equal(resolvedConfig?.mode, "desktop");182 assert.equal(resolvedConfig?.mode, "desktop");
...@@ -199,17 +190,31 @@ it.layer(testLayer)("server cli", (it) => {...@@ -199,17 +190,31 @@ it.layer(testLayer)("server cli", (it) => {
199 T3CODE_NO_BROWSER: "true",190 T3CODE_NO_BROWSER: "true",
200 });191 });
201192
202 assert.equal(createServer.mock.calls.length, 1);193 assert.equal(start.mock.calls.length, 1);
203 assert.equal(resolvedConfig?.mode, "desktop");194 assert.equal(resolvedConfig?.mode, "desktop");
204 assert.equal(resolvedConfig?.host, "0.0.0.0");195 assert.equal(resolvedConfig?.host, "0.0.0.0");
205 }),196 }),
206 );197 );
207198
199 it.effect("supports CLI and env for bootstrap/log websocket toggles", () =>
200 Effect.gen(function* () {
201 yield* runCli(["--auto-bootstrap-project-from-cwd"], {
202 T3CODE_MODE: "desktop",
203 T3CODE_LOG_WS_EVENTS: "false",
204 T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false",
205 T3CODE_NO_BROWSER: "true",
206 });
207
208 assert.equal(start.mock.calls.length, 1);
209 assert.equal(resolvedConfig?.autoBootstrapProjectFromCwd, true);
210 assert.equal(resolvedConfig?.logWebSocketEvents, false);
211 }),
212 );
213
208 it.effect("does not start server for invalid --mode values", () =>214 it.effect("does not start server for invalid --mode values", () =>
209 Effect.gen(function* () {215 Effect.gen(function* () {
210 yield* runCli(["--mode", "invalid"]);216 yield* runCli(["--mode", "invalid"]);
211217
212 assert.equal(createServer.mock.calls.length, 0);
213 assert.equal(start.mock.calls.length, 0);218 assert.equal(start.mock.calls.length, 0);
214 assert.equal(stop.mock.calls.length, 0);219 assert.equal(stop.mock.calls.length, 0);
215 }),220 }),
...@@ -219,7 +224,6 @@ it.layer(testLayer)("server cli", (it) => {...@@ -219,7 +224,6 @@ it.layer(testLayer)("server cli", (it) => {
219 Effect.gen(function* () {224 Effect.gen(function* () {
220 yield* runCli(["--dev-url", "not-a-url"]).pipe(Effect.catch(() => Effect.void));225 yield* runCli(["--dev-url", "not-a-url"]).pipe(Effect.catch(() => Effect.void));
221226
222 assert.equal(createServer.mock.calls.length, 0);
223 assert.equal(start.mock.calls.length, 0);227 assert.equal(start.mock.calls.length, 0);
224 assert.equal(stop.mock.calls.length, 0);228 assert.equal(stop.mock.calls.length, 0);
225 }),229 }),
...@@ -230,7 +234,6 @@ it.layer(testLayer)("server cli", (it) => {...@@ -230,7 +234,6 @@ it.layer(testLayer)("server cli", (it) => {
230 yield* runCli(["--port", "70000"]);234 yield* runCli(["--port", "70000"]);
231235
232 // effect/unstable/cli renders help/errors for parse failures and returns success.236 // effect/unstable/cli renders help/errors for parse failures and returns success.
233 assert.equal(createServer.mock.calls.length, 0);
234 assert.equal(start.mock.calls.length, 0);237 assert.equal(start.mock.calls.length, 0);
235 assert.equal(stop.mock.calls.length, 0);238 assert.equal(stop.mock.calls.length, 0);
236 }),239 }),
apps/server/src/main.ts+41-2
...@@ -28,6 +28,8 @@ interface CliInput {...@@ -28,6 +28,8 @@ interface CliInput {
28 readonly devUrl: Option.Option<URL>;28 readonly devUrl: Option.Option<URL>;
29 readonly noBrowser: Option.Option<boolean>;29 readonly noBrowser: Option.Option<boolean>;
30 readonly authToken: Option.Option<string>;30 readonly authToken: Option.Option<string>;
31 readonly autoBootstrapProjectFromCwd: Option.Option<boolean>;
32 readonly logWebSocketEvents: Option.Option<boolean>;
31}33}
3234
33export interface CliConfigShape {35export interface CliConfigShape {
...@@ -79,6 +81,14 @@ const CliEnvConfig = Config.all({...@@ -79,6 +81,14 @@ const CliEnvConfig = Config.all({
79 Config.option,81 Config.option,
80 Config.map(Option.getOrUndefined),82 Config.map(Option.getOrUndefined),
81 ),83 ),
84 autoBootstrapProjectFromCwd: Config.boolean("T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe(
85 Config.option,
86 Config.map(Option.getOrUndefined),
87 ),
88 logWebSocketEvents: Config.boolean("T3CODE_LOG_WS_EVENTS").pipe(
89 Config.option,
90 Config.map(Option.getOrUndefined),
91 ),
82});92});
8393
84const ServerConfigLive = (input: CliInput) =>94const ServerConfigLive = (input: CliInput) =>
...@@ -119,6 +129,18 @@ const ServerConfigLive = (input: CliInput) =>...@@ -119,6 +129,18 @@ const ServerConfigLive = (input: CliInput) =>
119 onNone: () => env.noBrowser ?? mode === "desktop",129 onNone: () => env.noBrowser ?? mode === "desktop",
120 });130 });
121 const authToken = Option.getOrUndefined(input.authToken) ?? env.authToken;131 const authToken = Option.getOrUndefined(input.authToken) ?? env.authToken;
132 const autoBootstrapProjectFromCwd = Option.match(input.autoBootstrapProjectFromCwd, {
133 // effect/cli boolean flags parse to `false` when absent; in that case
134 // we still want env/mode fallbacks to apply.
135 onSome: (value) => (value ? true : (env.autoBootstrapProjectFromCwd ?? mode === "web")),
136 onNone: () => env.autoBootstrapProjectFromCwd ?? mode === "web",
137 });
138 const logWebSocketEvents = Option.match(input.logWebSocketEvents, {
139 // effect/cli boolean flags parse to `false` when absent; in that case
140 // we still want env/dev fallbacks to apply.
141 onSome: (value) => (value ? true : (env.logWebSocketEvents ?? Boolean(devUrl))),
142 onNone: () => env.logWebSocketEvents ?? Boolean(devUrl),
143 });
122 const staticDir = devUrl ? undefined : yield* cliConfig.resolveStaticDir;144 const staticDir = devUrl ? undefined : yield* cliConfig.resolveStaticDir;
123 const { join } = yield* Path.Path;145 const { join } = yield* Path.Path;
124 const keybindingsConfigPath = join(stateDir, "keybindings.json");146 const keybindingsConfigPath = join(stateDir, "keybindings.json");
...@@ -138,6 +160,8 @@ const ServerConfigLive = (input: CliInput) =>...@@ -138,6 +160,8 @@ const ServerConfigLive = (input: CliInput) =>
138 devUrl,160 devUrl,
139 noBrowser,161 noBrowser,
140 authToken,162 authToken,
163 autoBootstrapProjectFromCwd,
164 logWebSocketEvents,
141 } satisfies ServerConfigShape;165 } satisfies ServerConfigShape;
142 }),166 }),
143 );167 );
...@@ -159,7 +183,7 @@ const formatHostForUrl = (host: string): string =>...@@ -159,7 +183,7 @@ const formatHostForUrl = (host: string): string =>
159const makeServerProgram = (input: CliInput) =>183const makeServerProgram = (input: CliInput) =>
160 Effect.gen(function* () {184 Effect.gen(function* () {
161 const cliConfig = yield* CliConfig;185 const cliConfig = yield* CliConfig;
162 const { createServer, stopSignal } = yield* Server;186 const { start, stopSignal } = yield* Server;
163 const openDeps = yield* Open;187 const openDeps = yield* Open;
164 yield* cliConfig.fixPath;188 yield* cliConfig.fixPath;
165189
...@@ -174,7 +198,7 @@ const makeServerProgram = (input: CliInput) =>...@@ -174,7 +198,7 @@ const makeServerProgram = (input: CliInput) =>
174 );198 );
175 }199 }
176200
177 yield* createServer();201 yield* start;
178202
179 const localUrl = `http://localhost:${config.port}`;203 const localUrl = `http://localhost:${config.port}`;
180 const bindUrl =204 const bindUrl =
...@@ -240,6 +264,19 @@ const authTokenFlag = Flag.string("auth-token").pipe(...@@ -240,6 +264,19 @@ const authTokenFlag = Flag.string("auth-token").pipe(
240 Flag.withAlias("token"),264 Flag.withAlias("token"),
241 Flag.optional,265 Flag.optional,
242);266);
267const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-from-cwd").pipe(
268 Flag.withDescription(
269 "Create a project for the current working directory on startup when missing.",
270 ),
271 Flag.optional,
272);
273const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe(
274 Flag.withDescription(
275 "Emit server-side logs for outbound WebSocket push traffic (equivalent to T3CODE_LOG_WS_EVENTS).",
276 ),
277 Flag.withAlias("log-ws-events"),
278 Flag.optional,
279);
243280
244export const t3Cli = Command.make("t3", {281export const t3Cli = Command.make("t3", {
245 mode: modeFlag,282 mode: modeFlag,
...@@ -249,6 +286,8 @@ export const t3Cli = Command.make("t3", {...@@ -249,6 +286,8 @@ export const t3Cli = Command.make("t3", {
249 devUrl: devUrlFlag,286 devUrl: devUrlFlag,
250 noBrowser: noBrowserFlag,287 noBrowser: noBrowserFlag,
251 authToken: authTokenFlag,288 authToken: authTokenFlag,
289 autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag,
290 logWebSocketEvents: logWebSocketEventsFlag,
252}).pipe(291}).pipe(
253 Command.withDescription("Run the T3 Code server."),292 Command.withDescription("Run the T3 Code server."),
254 Command.withHandler((input) => Effect.scoped(makeServerProgram(input))),293 Command.withHandler((input) => Effect.scoped(makeServerProgram(input))),
apps/server/src/wsServer.test.ts+4-3
...@@ -338,6 +338,7 @@ describe("WebSocket Server", () => {...@@ -338,6 +338,7 @@ describe("WebSocket Server", () => {
338 persistenceLayer?: Layer.Layer<SqlClient.SqlClient, any>;338 persistenceLayer?: Layer.Layer<SqlClient.SqlClient, any>;
339 cwd?: string;339 cwd?: string;
340 autoBootstrapProjectFromCwd?: boolean;340 autoBootstrapProjectFromCwd?: boolean;
341 logWebSocketEvents?: boolean;
341 devUrl?: string;342 devUrl?: string;
342 authToken?: string;343 authToken?: string;
343 stateDir?: string;344 stateDir?: string;
...@@ -368,6 +369,8 @@ describe("WebSocket Server", () => {...@@ -368,6 +369,8 @@ describe("WebSocket Server", () => {
368 devUrl: options.devUrl ? new URL(options.devUrl) : undefined,369 devUrl: options.devUrl ? new URL(options.devUrl) : undefined,
369 noBrowser: true,370 noBrowser: true,
370 authToken: options.authToken,371 authToken: options.authToken,
372 autoBootstrapProjectFromCwd: options.autoBootstrapProjectFromCwd ?? false,
373 logWebSocketEvents: options.logWebSocketEvents ?? Boolean(options.devUrl),
371 } satisfies ServerConfigShape);374 } satisfies ServerConfigShape);
372 const infrastructureLayer = providerLayer.pipe(Layer.provideMerge(persistenceLayer));375 const infrastructureLayer = providerLayer.pipe(Layer.provideMerge(persistenceLayer));
373 const runtimeOverrides = Layer.mergeAll(376 const runtimeOverrides = Layer.mergeAll(
...@@ -397,9 +400,7 @@ describe("WebSocket Server", () => {...@@ -397,9 +400,7 @@ describe("WebSocket Server", () => {
397400
398 try {401 try {
399 const runtime = await Effect.runPromise(402 const runtime = await Effect.runPromise(
400 createServer({403 createServer().pipe(Effect.provide(runtimeServices), Scope.provide(scope)),
401 autoBootstrapProjectFromCwd: options.autoBootstrapProjectFromCwd ?? false,
402 }).pipe(Effect.provide(runtimeServices), Scope.provide(scope)),
403 );404 );
404 serverScope = scope;405 serverScope = scope;
405 return runtime;406 return runtime;
apps/server/src/wsServer.ts+16-22
...@@ -49,15 +49,12 @@ const MIME_TYPES: Record<string, string> = {...@@ -49,15 +49,12 @@ const MIME_TYPES: Record<string, string> = {
49 ".map": "application/json",49 ".map": "application/json",
50};50};
5151
52export interface ServerOptions {
53 autoBootstrapProjectFromCwd?: boolean | undefined;
54 logWebSocketEvents?: boolean | undefined;
55}
56
57export interface ServerShape {52export interface ServerShape {
58 readonly createServer: (53 readonly start: Effect.Effect<
59 options?: ServerOptions,54 http.Server,
60 ) => Effect.Effect<http.Server, unknown, Scope.Scope | ServerRuntimeServices | ServerConfig>;55 unknown,
56 Scope.Scope | ServerRuntimeServices | ServerConfig
57 >;
61 readonly stopSignal: Effect.Effect<unknown, never>;58 readonly stopSignal: Effect.Effect<unknown, never>;
62}59}
6360
...@@ -82,14 +79,6 @@ function rejectUpgrade(socket: Duplex, statusCode: number, message: string): voi...@@ -82,14 +79,6 @@ function rejectUpgrade(socket: Duplex, statusCode: number, message: string): voi
82 );79 );
83}80}
8481
85function parseBooleanEnv(value: string | undefined): boolean | undefined {
86 if (value === undefined) return undefined;
87 const normalized = value.trim().toLowerCase();
88 if (["1", "true", "yes", "on"].includes(normalized)) return true;
89 if (["0", "false", "no", "off"].includes(normalized)) return false;
90 return undefined;
91}
92
93function websocketRawToString(raw: unknown): string | null {82function websocketRawToString(raw: unknown): string | null {
94 if (typeof raw === "string") {83 if (typeof raw === "string") {
95 return raw;84 return raw;
...@@ -153,17 +142,22 @@ class RouteRequestError extends Schema.TaggedErrorClass<RouteRequestError>()("Ro...@@ -153,17 +142,22 @@ class RouteRequestError extends Schema.TaggedErrorClass<RouteRequestError>()("Ro
153 message: Schema.String,142 message: Schema.String,
154}) {}143}) {}
155144
156export const createServer = Effect.fn(function* (options: ServerOptions = {}): Effect.fn.Return<145export const createServer = Effect.fn(function* (): Effect.fn.Return<
157 http.Server,146 http.Server,
158 unknown, // TODO: This should not be unknown147 unknown, // TODO: This should not be unknown
159 Scope.Scope | ServerRuntimeServices | ServerConfig148 Scope.Scope | ServerRuntimeServices | ServerConfig
160> {149> {
161 const serverConfig = yield* ServerConfig;150 const serverConfig = yield* ServerConfig;
162 const { port, cwd, staticDir, devUrl, authToken, mode, host } = serverConfig;
163 const {151 const {
164 logWebSocketEvents = parseBooleanEnv(process.env.T3CODE_LOG_WS_EVENTS) ?? Boolean(devUrl),152 port,
165 autoBootstrapProjectFromCwd = mode === "web",153 cwd,
166 } = options;154 staticDir,
155 devUrl,
156 authToken,
157 host,
158 logWebSocketEvents,
159 autoBootstrapProjectFromCwd,
160 } = serverConfig;
167161
168 const gitManager = yield* GitManager;162 const gitManager = yield* GitManager;
169 const terminalManager = yield* TerminalManager;163 const terminalManager = yield* TerminalManager;
...@@ -599,6 +593,6 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E...@@ -599,6 +593,6 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E
599});593});
600594
601export const ServerLive = Layer.succeed(Server, {595export const ServerLive = Layer.succeed(Server, {
602 createServer,596 start: createServer(),
603 stopSignal: Effect.never,597 stopSignal: Effect.never,
604} satisfies ServerShape);598} satisfies ServerShape);