1import * as HostProcess from "@t3tools/shared/HostProcess";
2import * as Effect from "effect/Effect";
3import * as FileSystem from "effect/FileSystem";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
6import { Command, GlobalFlag } from "effect/cli";
7import * as CliError from "effect/cli/CliError";
8
9import * as ServerConfig from "../config.ts";
10import { runServer } from "../server.ts";
11import { type CliServerFlags, resolveServerConfig, sharedServerCommandFlags } from "./config.ts";
12
13const encodeCommand = Schema.encodeEffect(Schema.fromJsonString(Schema.String));
14
15const runServerCommand = (
16 flags: CliServerFlags,
17 options?: {
18 readonly startupPresentation?: ServerConfig.StartupPresentation;
19 readonly forceAutoBootstrapProjectFromCwd?: boolean;
20 readonly rejectRunningServer?: boolean;
21 },
22) =>
23 Effect.gen(function* () {
24 const logLevel = yield* GlobalFlag.LogLevel;
25 const config = yield* resolveServerConfig(flags, logLevel, options);
26 return yield* runServer.pipe(Effect.provideService(ServerConfig.ServerConfig, config));
27 });
28
29/** Bare words can name existing directories, but must not create typo projects. */
30export const runDefaultServerCommand = (flags: CliServerFlags) =>
31 Effect.gen(function* () {
32 if (Option.isSome(flags.cwd)) {
33 const cwd = flags.cwd.value.trim();
34 const fs = yield* FileSystem.FileSystem;
35 const platform = yield* HostProcess.Platform;
36 const explicitPath =
37 cwd === "." ||
38 cwd === ".." ||
39 cwd === "~" ||
40 /[/\\]/.test(cwd) ||
41 (platform === "win32" && /^[a-z]:/i.test(cwd));
42 if (
43 !explicitPath &&
44 (!(yield* fs.exists(cwd)) || (yield* fs.stat(cwd)).type !== "Directory")
45 ) {
46 return yield* new CliError.UserError({
47 cause: cwd,
48 userMessage: `Unknown command ${yield* encodeCommand(cwd)}. Use "t3 --help" for commands or an explicit path such as "t3 ./my-project" for a new directory.`,
49 });
50 }
51 }
52 return yield* runServerCommand(flags, { rejectRunningServer: true });
53 });
54
55export const startCommand = Command.make("start", { ...sharedServerCommandFlags }).pipe(
56 Command.withDescription("Run the T3 Code server."),
57 Command.withHandler((flags) => runServerCommand(flags, { rejectRunningServer: true })),
58);
59
60export const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(
61 Command.withDescription(
62 "Run the T3 Code server without opening a browser and print headless pairing details.",
63 ),
64 Command.withHandler((flags) =>
65 runServerCommand(flags, {
66 startupPresentation: "headless",
67 forceAutoBootstrapProjectFromCwd: false,
68 }),
69 ),
70);