292 lines · 9.7 KB · TypeScript
RawDownload
import { NetService } from "@t3tools/shared/Net";import { Config, Effect, LogLevel, Option, Schema } from "effect";import { Command, Flag, GlobalFlag } from "effect/unstable/cli"; import {  DEFAULT_PORT,  deriveServerPaths,  ensureServerDirectories,  resolveStaticDir,  ServerConfig,  RuntimeMode,  type ServerConfigShape,} from "./config";import { readBootstrapEnvelope } from "./bootstrap";import { resolveBaseDir } from "./os-jank";import { runServer } from "./server"; const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })); const BootstrapEnvelopeSchema = Schema.Struct({  mode: Schema.optional(RuntimeMode),  port: Schema.optional(PortSchema),  host: Schema.optional(Schema.String),  t3Home: Schema.optional(Schema.String),  devUrl: Schema.optional(Schema.URLFromString),  noBrowser: Schema.optional(Schema.Boolean),  authToken: Schema.optional(Schema.String),  autoBootstrapProjectFromCwd: Schema.optional(Schema.Boolean),  logWebSocketEvents: Schema.optional(Schema.Boolean),}); const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(  Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),  Flag.optional,);const portFlag = Flag.integer("port").pipe(  Flag.withSchema(PortSchema),  Flag.withDescription("Port for the HTTP/WebSocket server."),  Flag.optional,);const hostFlag = Flag.string("host").pipe(  Flag.withDescription("Host/interface to bind (for example 127.0.0.1, 0.0.0.0, or a Tailnet IP)."),  Flag.optional,);const baseDirFlag = Flag.string("base-dir").pipe(  Flag.withDescription("Base directory path (equivalent to T3CODE_HOME)."),  Flag.optional,);const devUrlFlag = Flag.string("dev-url").pipe(  Flag.withSchema(Schema.URLFromString),  Flag.withDescription("Dev web URL to proxy/redirect to (equivalent to VITE_DEV_SERVER_URL)."),  Flag.optional,);const noBrowserFlag = Flag.boolean("no-browser").pipe(  Flag.withDescription("Disable automatic browser opening."),  Flag.optional,);const authTokenFlag = Flag.string("auth-token").pipe(  Flag.withDescription("Auth token required for WebSocket connections."),  Flag.withAlias("token"),  Flag.optional,);const bootstrapFdFlag = Flag.integer("bootstrap-fd").pipe(  Flag.withSchema(Schema.Int),  Flag.withDescription("Read one-time bootstrap secrets from the given file descriptor."),  Flag.optional,);const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-from-cwd").pipe(  Flag.withDescription(    "Create a project for the current working directory on startup when missing.",  ),  Flag.optional,);const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe(  Flag.withDescription(    "Emit server-side logs for outbound WebSocket push traffic (equivalent to T3CODE_LOG_WS_EVENTS).",  ),  Flag.withAlias("log-ws-events"),  Flag.optional,); const EnvServerConfig = Config.all({  logLevel: Config.logLevel("T3CODE_LOG_LEVEL").pipe(Config.withDefault("Info")),  mode: Config.schema(RuntimeMode, "T3CODE_MODE").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),  port: Config.port("T3CODE_PORT").pipe(Config.option, Config.map(Option.getOrUndefined)),  host: Config.string("T3CODE_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)),  t3Home: Config.string("T3CODE_HOME").pipe(Config.option, Config.map(Option.getOrUndefined)),  devUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option, Config.map(Option.getOrUndefined)),  noBrowser: Config.boolean("T3CODE_NO_BROWSER").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),  authToken: Config.string("T3CODE_AUTH_TOKEN").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),  bootstrapFd: Config.int("T3CODE_BOOTSTRAP_FD").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),  autoBootstrapProjectFromCwd: Config.boolean("T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),  logWebSocketEvents: Config.boolean("T3CODE_LOG_WS_EVENTS").pipe(    Config.option,    Config.map(Option.getOrUndefined),  ),}); interface CliServerFlags {  readonly mode: Option.Option<RuntimeMode>;  readonly port: Option.Option<number>;  readonly host: Option.Option<string>;  readonly baseDir: Option.Option<string>;  readonly devUrl: Option.Option<URL>;  readonly noBrowser: Option.Option<boolean>;  readonly authToken: Option.Option<string>;  readonly bootstrapFd: Option.Option<number>;  readonly autoBootstrapProjectFromCwd: Option.Option<boolean>;  readonly logWebSocketEvents: Option.Option<boolean>;} const resolveBooleanFlag = (flag: Option.Option<boolean>, envValue: boolean) =>  Option.getOrElse(Option.filter(flag, Boolean), () => envValue); const resolveOptionPrecedence = <Value>(  ...values: ReadonlyArray<Option.Option<Value>>): Option.Option<Value> => Option.firstSomeOf(values); export const resolveServerConfig = (  flags: CliServerFlags,  cliLogLevel: Option.Option<LogLevel.LogLevel>,) =>  Effect.gen(function* () {    const { findAvailablePort } = yield* NetService;    const env = yield* EnvServerConfig;    const bootstrapFd = Option.getOrUndefined(flags.bootstrapFd) ?? env.bootstrapFd;    const bootstrapEnvelope =      bootstrapFd !== undefined        ? yield* readBootstrapEnvelope(BootstrapEnvelopeSchema, bootstrapFd)        : Option.none();     const mode: RuntimeMode = Option.getOrElse(      resolveOptionPrecedence(        flags.mode,        Option.fromUndefinedOr(env.mode),        Option.flatMap(bootstrapEnvelope, (bootstrap) => Option.fromUndefinedOr(bootstrap.mode)),      ),      () => "web",    );     const port = yield* Option.match(      resolveOptionPrecedence(        flags.port,        Option.fromUndefinedOr(env.port),        Option.flatMap(bootstrapEnvelope, (bootstrap) => Option.fromUndefinedOr(bootstrap.port)),      ),      {        onSome: (value) => Effect.succeed(value),        onNone: () => {          if (mode === "desktop") {            return Effect.succeed(DEFAULT_PORT);          }          return findAvailablePort(DEFAULT_PORT);        },      },    );    const devUrl = Option.getOrElse(      resolveOptionPrecedence(        flags.devUrl,        Option.fromUndefinedOr(env.devUrl),        Option.flatMap(bootstrapEnvelope, (bootstrap) => Option.fromUndefinedOr(bootstrap.devUrl)),      ),      () => undefined,    );    const baseDir = yield* resolveBaseDir(      Option.getOrUndefined(        resolveOptionPrecedence(          flags.baseDir,          Option.fromUndefinedOr(env.t3Home),          Option.flatMap(bootstrapEnvelope, (bootstrap) =>            Option.fromUndefinedOr(bootstrap.t3Home),          ),        ),      ),    );    const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);    yield* ensureServerDirectories(derivedPaths);    const noBrowser = resolveBooleanFlag(      flags.noBrowser,      Option.getOrElse(        resolveOptionPrecedence(          Option.fromUndefinedOr(env.noBrowser),          Option.flatMap(bootstrapEnvelope, (bootstrap) =>            Option.fromUndefinedOr(bootstrap.noBrowser),          ),        ),        () => mode === "desktop",      ),    );    const authToken = Option.getOrUndefined(      resolveOptionPrecedence(        flags.authToken,        Option.fromUndefinedOr(env.authToken),        Option.flatMap(bootstrapEnvelope, (bootstrap) =>          Option.fromUndefinedOr(bootstrap.authToken),        ),      ),    );    const autoBootstrapProjectFromCwd = resolveBooleanFlag(      flags.autoBootstrapProjectFromCwd,      Option.getOrElse(        resolveOptionPrecedence(          Option.fromUndefinedOr(env.autoBootstrapProjectFromCwd),          Option.flatMap(bootstrapEnvelope, (bootstrap) =>            Option.fromUndefinedOr(bootstrap.autoBootstrapProjectFromCwd),          ),        ),        () => mode === "web",      ),    );    const logWebSocketEvents = resolveBooleanFlag(      flags.logWebSocketEvents,      Option.getOrElse(        resolveOptionPrecedence(          Option.fromUndefinedOr(env.logWebSocketEvents),          Option.flatMap(bootstrapEnvelope, (bootstrap) =>            Option.fromUndefinedOr(bootstrap.logWebSocketEvents),          ),        ),        () => Boolean(devUrl),      ),    );    const staticDir = devUrl ? undefined : yield* resolveStaticDir();    const host = Option.getOrElse(      resolveOptionPrecedence(        flags.host,        Option.fromUndefinedOr(env.host),        Option.flatMap(bootstrapEnvelope, (bootstrap) => Option.fromUndefinedOr(bootstrap.host)),      ),      () => (mode === "desktop" ? "127.0.0.1" : undefined),    );    const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel);     const config: ServerConfigShape = {      logLevel,      mode,      port,      cwd: process.cwd(),      baseDir,      ...derivedPaths,      host,      staticDir,      devUrl,      noBrowser,      authToken,      autoBootstrapProjectFromCwd,      logWebSocketEvents,    };     return config;  }); const commandFlags = {  mode: modeFlag,  port: portFlag,  host: hostFlag,  baseDir: baseDirFlag,  devUrl: devUrlFlag,  noBrowser: noBrowserFlag,  authToken: authTokenFlag,  bootstrapFd: bootstrapFdFlag,  autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag,  logWebSocketEvents: logWebSocketEventsFlag,} as const; const rootCommand = Command.make("t3", commandFlags).pipe(  Command.withDescription("Run the T3 Code server."),  Command.withHandler((flags) =>    Effect.gen(function* () {      const logLevel = yield* GlobalFlag.LogLevel;      const config = yield* resolveServerConfig(flags, logLevel);      return yield* runServer.pipe(Effect.provideService(ServerConfig, config));    }),  ),); export const cli = rootCommand;