Open History to see the last commit
1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import * as NodeDns from "node:dns";
5import * as NodeOS from "node:os";
6import * as NodeSea from "node:sea";
7
8export const Platform = Context.Reference<NodeJS.Platform>("@t3tools/shared/HostProcess/Platform", {
9 defaultValue: () => process.platform,
10});
11
12export const Architecture = Context.Reference<NodeJS.Architecture>(
13 "@t3tools/shared/HostProcess/Architecture",
14 {
15 defaultValue: () => process.arch,
16 },
17);
18
19export const LinuxLibc = Context.Reference<"gnu" | "musl">(
20 "@t3tools/shared/HostProcess/LinuxLibc",
21 {
22 defaultValue: () => {
23 try {
24 const report = process.report?.getReport() as
25 | { header?: { glibcVersionRuntime?: unknown } }
26 | undefined;
27 return typeof report?.header?.glibcVersionRuntime === "string" ? "gnu" : "musl";
28 } catch {
29 return "musl";
30 }
31 },
32 },
33);
34
35export const Hostname = Context.Reference<string>("@t3tools/shared/HostProcess/Hostname", {
36 defaultValue: () => NodeOS.hostname(),
37});
38
39export const Username = Context.Reference<string | null>("@t3tools/shared/HostProcess/Username", {
40 defaultValue: () => {
41 try {
42 return NodeOS.userInfo().username.trim() || null;
43 } catch {
44 return null;
45 }
46 },
47});
48
49export const HomeDirectory = Context.Reference<string>(
50 "@t3tools/shared/HostProcess/HomeDirectory",
51 {
52 defaultValue: () => NodeOS.homedir(),
53 },
54);
55
56export const Environment = Context.Reference<NodeJS.ProcessEnv>(
57 "@t3tools/shared/HostProcess/Environment",
58 {
59 defaultValue: () => process.env,
60 },
61);
62
63export const WorkingDirectory = Context.Reference<string>(
64 "@t3tools/shared/HostProcess/WorkingDirectory",
65 {
66 defaultValue: () => process.cwd(),
67 },
68);
69
70export const ExecutablePath = Context.Reference<string>(
71 "@t3tools/shared/HostProcess/ExecutablePath",
72 {
73 defaultValue: () => process.execPath,
74 },
75);
76
77export const Arguments = Context.Reference<ReadonlyArray<string>>(
78 "@t3tools/shared/HostProcess/Arguments",
79 {
80 defaultValue: () => process.argv,
81 },
82);
83
84/** Original shell command spelling; argv[0] and execPath instead identify the resolved binary. */
85export const InvokedAs = Context.Reference<string>("@t3tools/shared/HostProcess/InvokedAs", {
86 defaultValue: () => process.argv0,
87});
88
89export const IsExecutable = Context.Reference<boolean>("@t3tools/shared/HostProcess/IsExecutable", {
90 defaultValue: () => NodeSea.isSea(),
91});
92
93export class HostnameLookupError extends Schema.TaggedError<HostnameLookupError>()(
94 "HostnameLookupError",
95 { hostname: Schema.String, cause: Schema.Defect() },
96) {
97 override get message(): string {
98 return `Could not resolve ${this.hostname}.`;
99 }
100}
101
102/** Includes hostname-resolved addresses absent from interfaces, such as Debian's 127.0.1.1 used by Firefox locks. */
103export const Addresses = Context.Reference<Effect.Effect<ReadonlySet<string>>>(
104 "@t3tools/shared/HostProcess/Addresses",
105 {
106 // The enclosing operation owns the span for this inline handler.
107 defaultValue: Effect.fnUntraced(function* () {
108 const interfaces = Object.values(NodeOS.networkInterfaces())
109 .flat()
110 .flatMap((entry) => (entry ? [entry.address] : []));
111 const hostname = NodeOS.hostname();
112 const resolved = yield* Effect.tryPromise({
113 try: () => NodeDns.promises.lookup(hostname, { all: true }),
114 catch: (cause) => new HostnameLookupError({ hostname, cause }),
115 }).pipe(
116 Effect.map((entries) => entries.map((entry) => entry.address)),
117 Effect.orElseSucceed(() => [] as ReadonlyArray<string>),
118 );
119 return new Set([...interfaces, ...resolved]);
120 }),
121 },
122);
123
124export const UserId = Context.Reference<number | undefined>("@t3tools/shared/HostProcess/UserId", {
125 defaultValue: () => process.getuid?.(),
126});
127
128export const isWindows = Effect.map(Platform, (platform) => platform === "win32");