Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
".reference",
".plans",
"dist",
"dist-electron",
Expand Down
7 changes: 4 additions & 3 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
"directory": "apps/server"
},
"bin": {
"t3": "./dist/index.mjs"
"t3": "./dist/bin.mjs"
},
"files": [
"dist"
],
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"dev": "bun run src/bin.ts",
"build": "node scripts/cli.ts build",
"start": "node dist/index.mjs",
"start": "node dist/bin.mjs",
"prepare": "effect-language-service patch",
"typecheck": "tsc --noEmit",
"test": "vitest run"
Expand All @@ -34,6 +34,7 @@
},
"devDependencies": {
"@effect/language-service": "catalog:",
"@effect/platform-bun": "catalog:",
"@effect/vitest": "catalog:",
"@t3tools/contracts": "workspace:*",
"@t3tools/shared": "workspace:*",
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { Command } from "effect/unstable/cli";

import { NetService } from "@t3tools/shared/Net";
import { cli } from "./cli";
import { version } from "../package.json" with { type: "json" };

Command.run(cli, { version }).pipe(
Effect.provide(Layer.mergeAll(NetService.layer, NodeServices.layer)),
NodeRuntime.runMain,
);
131 changes: 131 additions & 0 deletions apps/server/src/cli-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import os from "node:os";

import { expect, it } from "@effect/vitest";
import { ConfigProvider, Effect, Layer, Option, Path } from "effect";

import { NetService } from "@t3tools/shared/Net";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { deriveServerPaths } from "./config";
import { resolveServerConfig } from "./cli";

it.layer(NodeServices.layer)("cli config resolution", (it) => {
it.effect("falls back to effect/config values when flags are omitted", () =>
Effect.gen(function* () {
const { join } = yield* Path.Path;
const baseDir = join(os.tmpdir(), "t3-cli-config-env-base");
const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:5173"));
const resolved = yield* resolveServerConfig(
{
mode: Option.none(),
port: Option.none(),
host: Option.none(),
baseDir: Option.none(),
devUrl: Option.none(),
noBrowser: Option.none(),
authToken: Option.none(),
autoBootstrapProjectFromCwd: Option.none(),
logWebSocketEvents: Option.none(),
},
Option.none(),
).pipe(
Effect.provide(
Layer.mergeAll(
ConfigProvider.layer(
ConfigProvider.fromEnv({
env: {
T3CODE_LOG_LEVEL: "Warn",
T3CODE_MODE: "desktop",
T3CODE_PORT: "4001",
T3CODE_HOST: "0.0.0.0",
T3CODE_HOME: baseDir,
VITE_DEV_SERVER_URL: "http://127.0.0.1:5173",
T3CODE_NO_BROWSER: "true",
T3CODE_AUTH_TOKEN: "env-token",
T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false",
T3CODE_LOG_WS_EVENTS: "true",
},
}),
),
NetService.layer,
),
),
);

expect(resolved).toEqual({
logLevel: "Warn",
mode: "desktop",
port: 4001,
cwd: process.cwd(),
baseDir,
...derivedPaths,
host: "0.0.0.0",
staticDir: undefined,
devUrl: new URL("http://127.0.0.1:5173"),
noBrowser: true,
authToken: "env-token",
autoBootstrapProjectFromCwd: false,
logWebSocketEvents: true,
});
}),
);

it.effect("uses CLI flags when provided", () =>
Effect.gen(function* () {
const { join } = yield* Path.Path;
const baseDir = join(os.tmpdir(), "t3-cli-config-flags-base");
const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:4173"));
const resolved = yield* resolveServerConfig(
{
mode: Option.some("web"),
port: Option.some(8788),
host: Option.some("127.0.0.1"),
baseDir: Option.some(baseDir),
devUrl: Option.some(new URL("http://127.0.0.1:4173")),
noBrowser: Option.some(true),
authToken: Option.some("flag-token"),
autoBootstrapProjectFromCwd: Option.some(true),
logWebSocketEvents: Option.some(true),
},
Option.some("Debug"),
).pipe(
Effect.provide(
Layer.mergeAll(
ConfigProvider.layer(
ConfigProvider.fromEnv({
env: {
T3CODE_LOG_LEVEL: "Warn",
T3CODE_MODE: "desktop",
T3CODE_PORT: "4001",
T3CODE_HOST: "0.0.0.0",
T3CODE_HOME: join(os.tmpdir(), "ignored-base"),
VITE_DEV_SERVER_URL: "http://127.0.0.1:5173",
T3CODE_NO_BROWSER: "false",
T3CODE_AUTH_TOKEN: "ignored-token",
T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false",
T3CODE_LOG_WS_EVENTS: "false",
},
}),
),
NetService.layer,
),
),
);

expect(resolved).toEqual({
logLevel: "Debug",
mode: "web",
port: 8788,
cwd: process.cwd(),
baseDir,
...derivedPaths,
host: "127.0.0.1",
staticDir: undefined,
devUrl: new URL("http://127.0.0.1:4173"),
noBrowser: true,
authToken: "flag-token",
autoBootstrapProjectFromCwd: true,
logWebSocketEvents: true,
});
}),
);
});
37 changes: 37 additions & 0 deletions apps/server/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { NetService } from "@t3tools/shared/Net";
import { assert, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import * as CliError from "effect/unstable/cli/CliError";
import { Command } from "effect/unstable/cli";

import { cli } from "./cli.ts";

const provideCliRuntime = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
effect.pipe(Effect.provide(Layer.mergeAll(NetService.layer, NodeServices.layer)));

it.layer(NodeServices.layer)("cli log-level parsing", (it) => {
it.effect("accepts the built-in lowercase log-level flag values", () =>
Command.runWith(cli, { version: "0.0.0" })(["--log-level", "debug", "--version"]).pipe(
provideCliRuntime,
),
);

it.effect("rejects invalid log-level casing before launching the server", () =>
Effect.gen(function* () {
const error = yield* Command.runWith(cli, { version: "0.0.0" })([
"--log-level",
"Debug",
]).pipe(provideCliRuntime, Effect.flip);

if (!CliError.isCliError(error)) {
throw new Error(`Expected CliError, got ${String(error)}`);
}
if (error._tag !== "InvalidValue") {
throw new Error(`Expected InvalidValue, got ${error._tag}`);
}
assert.equal(error.option, "log-level");
assert.equal(error.value, "Debug");
}),
);
});
Loading
Loading