authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-04-03 19:49:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-03 19:49:10-07:00
log27c2b145f8d214b5c578eefc463278d0916fb048
tree6fc4853e053d67f3898c2f819a1dc42331346128
parent8515f02770a05d5f1c0f740c5223938fb6ab102b
signature Signed by PGP key B5690EEEBB952194

feat(cli): allow optional positional cwd argument (#1727)

Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>

2 files changed, 22 insertions(+), 3 deletions(-)

apps/server/src/cli-config.test.ts+9
......@@ -40,6 +40,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
4040 port: Option.none(),
4141 host: Option.none(),
4242 baseDir: Option.none(),
43 cwd: Option.none(),
4344 devUrl: Option.none(),
4445 noBrowser: Option.none(),
4546 authToken: Option.none(),
......@@ -102,6 +103,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
102103 port: Option.some(8788),
103104 host: Option.some("127.0.0.1"),
104105 baseDir: Option.some(baseDir),
106 cwd: Option.none(),
105107 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
106108 noBrowser: Option.some(true),
107109 authToken: Option.some("flag-token"),
......@@ -178,6 +180,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
178180 port: Option.none(),
179181 host: Option.none(),
180182 baseDir: Option.none(),
183 cwd: Option.none(),
181184 devUrl: Option.none(),
182185 noBrowser: Option.none(),
183186 authToken: Option.none(),
......@@ -228,6 +231,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
228231 const fs = yield* FileSystem.FileSystem;
229232 const path = yield* Path.Path;
230233 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-dirs-" });
234 const customCwd = path.join(baseDir, "nested", "project");
231235
232236 const resolved = yield* resolveServerConfig(
233237 {
......@@ -235,6 +239,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
235239 port: Option.some(4888),
236240 host: Option.none(),
237241 baseDir: Option.some(baseDir),
242 cwd: Option.some(customCwd),
238243 devUrl: Option.some(new URL("http://127.0.0.1:5173")),
239244 noBrowser: Option.none(),
240245 authToken: Option.none(),
......@@ -253,6 +258,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
253258 );
254259
255260 for (const directory of [
261 customCwd,
256262 resolved.stateDir,
257263 resolved.logsDir,
258264 resolved.providerLogsDir,
......@@ -264,6 +270,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
264270 ]) {
265271 expect(yield* fs.exists(directory)).toBe(true);
266272 }
273 expect(resolved.cwd).toBe(path.resolve(customCwd));
267274 }),
268275 );
269276
......@@ -290,6 +297,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
290297 port: Option.some(8788),
291298 host: Option.some("127.0.0.1"),
292299 baseDir: Option.none(),
300 cwd: Option.none(),
293301 devUrl: Option.some(new URL("http://127.0.0.1:4173")),
294302 noBrowser: Option.none(),
295303 authToken: Option.some("flag-token"),
......@@ -360,6 +368,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
360368 port: Option.some(4888),
361369 host: Option.none(),
362370 baseDir: Option.some(baseDir),
371 cwd: Option.none(),
363372 devUrl: Option.none(),
364373 noBrowser: Option.none(),
365374 authToken: Option.none(),
apps/server/src/cli.ts+13-3
......@@ -1,7 +1,7 @@
11import { NetService } from "@t3tools/shared/Net";
22import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";
33import { Config, Effect, FileSystem, LogLevel, Option, Path, Schema } from "effect";
4import { Command, Flag, GlobalFlag } from "effect/unstable/cli";
4import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli";
55
66import {
77 DEFAULT_PORT,
......@@ -13,7 +13,7 @@ import {
1313 type ServerConfigShape,
1414} from "./config";
1515import { readBootstrapEnvelope } from "./bootstrap";
16import { resolveBaseDir } from "./os-jank";
16import { expandHomePath, resolveBaseDir } from "./os-jank";
1717import { runServer } from "./server";
1818
1919const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
......@@ -140,6 +140,7 @@ interface CliServerFlags {
140140 readonly port: Option.Option<number>;
141141 readonly host: Option.Option<string>;
142142 readonly baseDir: Option.Option<string>;
143 readonly cwd: Option.Option<string>;
143144 readonly devUrl: Option.Option<URL>;
144145 readonly noBrowser: Option.Option<boolean>;
145146 readonly authToken: Option.Option<string>;
......@@ -225,6 +226,9 @@ export const resolveServerConfig = (
225226 ),
226227 ),
227228 );
229 const rawCwd = Option.getOrElse(flags.cwd, () => process.cwd());
230 const cwd = path.resolve(yield* expandHomePath(rawCwd.trim()));
231 yield* fs.makeDirectory(cwd, { recursive: true });
228232 const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
229233 yield* ensureServerDirectories(derivedPaths);
230234 const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings(
......@@ -315,7 +319,7 @@ export const resolveServerConfig = (
315319 otlpServiceName: env.otlpServiceName,
316320 mode,
317321 port,
318 cwd: process.cwd(),
322 cwd,
319323 baseDir,
320324 ...derivedPaths,
321325 serverTracePath,
......@@ -336,6 +340,12 @@ const commandFlags = {
336340 port: portFlag,
337341 host: hostFlag,
338342 baseDir: baseDirFlag,
343 cwd: Argument.string("cwd").pipe(
344 Argument.withDescription(
345 "Working directory for provider sessions (defaults to the current directory).",
346 ),
347 Argument.optional,
348 ),
339349 devUrl: devUrlFlag,
340350 noBrowser: noBrowserFlag,
341351 authToken: authTokenFlag,