Repository navigation
Expand file tree
/
Copy pathserver.ts
More file actions
101 lines (94 loc) · 4.29 KB
/
Copy pathserver.ts
File metadata and controls
101 lines (94 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import { Command, GlobalFlag } from "effect/cli";
import * as CliError from "effect/cli/CliError";
import * as ServerConfig from "../config.ts";
import { awaitLauncherLoss } from "../launcherWatch.ts";
import * as DatabaseMaintenance from "../persistence/DatabaseMaintenance.ts";
import * as ProcessRunner from "../processRunner.ts";
import * as AutomaticPort from "./automaticPort.ts";
import { type CliServerFlags, resolveServerConfig, sharedServerCommandFlags } from "./config.ts";
const encodeCommand = Schema.encodeEffect(Schema.fromJsonString(Schema.String));
// The desktop launcher reads this process's stdout and stderr. Once it is gone,
// every log write fails, and Node turns an unhandled stream error into a crash
// that skips shutdown and leaves the ownership lock and runtime record behind.
function ignoreClosedLauncherPipe(error: NodeJS.ErrnoException): void {
if (error.code !== "EPIPE" && error.code !== "ERR_STREAM_DESTROYED") throw error;
}
const runServerCommand = Effect.fn("server.runServerCommand")(function* (
flags: CliServerFlags,
options?: {
readonly startupPresentation?: ServerConfig.StartupPresentation;
readonly forceAutoBootstrapProjectFromCwd?: boolean;
readonly rejectRunningServer?: boolean;
},
) {
// Before config resolution, which claims a port and creates directories.
const { runServer } = yield* Effect.promise(() => import("../server.ts"));
const logLevel = yield* GlobalFlag.LogLevel;
const selectAutomaticPort = yield* AutomaticPort.make.pipe(Effect.provide(ProcessRunner.layer));
const config = yield* resolveServerConfig(flags, logLevel, { ...options, selectAutomaticPort });
const run = runServer.pipe(
Effect.provideService(ServerConfig.ServerConfig, config),
// Settings' Reclaim stops the server; repair once the database is closed, before the restart.
Effect.ensuring(
DatabaseMaintenance.runRequestedReclaim(config.dbPath).pipe(
Effect.provide(DatabaseMaintenance.layer),
),
),
);
if (config.mode !== "desktop" || config.desktopBootstrapToken === undefined) return yield* run;
yield* Effect.sync(() => {
process.stdout.on("error", ignoreClosedLauncherPipe);
process.stderr.on("error", ignoreClosedLauncherPipe);
});
return yield* Effect.raceFirst(
run,
awaitLauncherLoss().pipe(
// End like SIGTERM does (interrupt-only exit, so the runtime forces process exit).
Effect.andThen(Effect.interrupt),
),
);
});
export const runDefaultServerCommand = Effect.fn("server.runDefaultServerCommand")(function* (
flags: CliServerFlags,
) {
if (Option.isSome(flags.cwd)) {
const cwd = flags.cwd.value.trim();
const fs = yield* FileSystem.FileSystem;
const platform = yield* HostProcess.Platform;
const explicitPath =
cwd === "." ||
cwd === ".." ||
cwd === "~" ||
/[/\\]/.test(cwd) ||
(platform === "win32" && /^[a-z]:/i.test(cwd));
if (!explicitPath && (!(yield* fs.exists(cwd)) || (yield* fs.stat(cwd)).type !== "Directory")) {
return yield* new CliError.UserError({
cause: cwd,
userMessage: `Unknown command ${yield* encodeCommand(cwd)}. Use "t3 --help" for commands or an explicit path such as "t3 ./my-project" for a new directory.`,
});
}
}
return yield* runServerCommand(flags, { rejectRunningServer: true });
});
export const startCommand = Command.make("start", { ...sharedServerCommandFlags }).pipe(
Command.withDescription("Same as `t3`."),
Command.unlisted,
Command.withHandler((flags) => runServerCommand(flags, { rejectRunningServer: true })),
);
export const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(
Command.withShortDescription("Run T3 Code in this terminal, with no browser."),
Command.withDescription(
"Run T3 Code in this terminal without opening a browser, and print pairing details. The background service runs this. To set up access from your other devices, run `t3 up`.",
),
Command.withHandler((flags) =>
runServerCommand(flags, {
startupPresentation: "headless",
forceAutoBootstrapProjectFromCwd: false,
}),
),
);