@@ -17,10 +17,12 @@ import * as NetService from "@t3tools/shared/Net";
1717import * as ConfigProvider from "effect/ConfigProvider" ;
1818import * as Effect from "effect/Effect" ;
1919import * as Layer from "effect/Layer" ;
20+ import * as Schema from "effect/Schema" ;
2021import { Command } from "effect/unstable/cli" ;
2122import { afterEach , describe , expect , vi } from "vite-plus/test" ;
2223
2324import { makeCli } from "../binCli.ts" ;
25+ import { PersistedServerRuntimeState } from "../serverRuntimeState.ts" ;
2426
2527vi . mock ( "node:os" , async ( importOriginal ) => {
2628 const os = await importOriginal < typeof import ( "node:os" ) > ( ) ;
@@ -29,6 +31,8 @@ vi.mock("node:os", async (importOriginal) => {
2931
3032afterEach ( ( ) => vi . mocked ( NodeOS . homedir ) . mockReset ( ) ) ;
3133
34+ const encodeRuntimeState = Schema . encodeEffect ( Schema . fromJsonString ( PersistedServerRuntimeState ) ) ;
35+
3236const runCli = ( args : ReadonlyArray < string > , env : Record < string , string > = { } ) =>
3337 Command . runWith ( makeCli ( ) , { version : "0.0.0" } ) ( args ) . pipe (
3438 Effect . provide (
@@ -130,6 +134,88 @@ const withTempDirectory = <A, E, R>(
130134 ( root ) => Effect . promise ( ( ) => NodeFSP . rm ( root , { recursive : true , force : true } ) ) ,
131135 ) ;
132136
137+ describe ( "t3 server command safety" , ( ) => {
138+ it . effect ( "rejects unknown command words without creating a home or project" , ( ) =>
139+ withTempDirectory ( "t3-cli-unknown-" , ( root ) =>
140+ Effect . gen ( function * ( ) {
141+ const baseDir = NodePath . join ( root , "home" ) ;
142+ for ( const word of [
143+ "account" ,
144+ "login" ,
145+ "clients" ,
146+ "conenct" ,
147+ "package.json" ,
148+ "C:new-project" ,
149+ ] ) {
150+ const error = yield * runCli ( [ word , "--base-dir" , baseDir ] ) . pipe (
151+ Effect . provideService ( HostProcessPlatform , "linux" ) ,
152+ Effect . flip ,
153+ ) ;
154+ expect ( String ( error ) ) . toContain ( `Unknown command "${ word } "` ) ;
155+ expect ( yield * pathExists ( word ) ) . toBe ( word === "package.json" ) ;
156+ expect ( yield * pathExists ( baseDir ) ) . toBe ( false ) ;
157+ }
158+ } ) ,
159+ ) ,
160+ ) ;
161+
162+ it . effect ( "shows help without creating state" , ( ) =>
163+ withTempDirectory ( "t3-cli-help-" , ( root ) =>
164+ Effect . gen ( function * ( ) {
165+ const baseDir = NodePath . join ( root , "home" ) ;
166+ const help = yield * runCli ( [ "help" ] , { T3CODE_HOME : baseDir } ) . pipe ( Effect . flip ) ;
167+ expect ( help ) . toMatchObject ( { _tag : "ShowHelp" , commandPath : [ "t3" ] , errors : [ ] } ) ;
168+ expect ( yield * pathExists ( baseDir ) ) . toBe ( false ) ;
169+ } ) ,
170+ ) ,
171+ ) ;
172+
173+ it . effect ( "refuses manual startup over a live server before creating directories" , ( ) =>
174+ withTempDirectory ( "t3-cli-running-" , ( root ) =>
175+ Effect . gen ( function * ( ) {
176+ const baseDir = NodePath . join ( root , "home" ) ;
177+ const stateDir = NodePath . join ( baseDir , "userdata" ) ;
178+ const statePath = NodePath . join ( stateDir , "server-runtime.json" ) ;
179+ const record = yield * encodeRuntimeState ( {
180+ version : 1 ,
181+ pid : process . pid ,
182+ port : 3773 ,
183+ origin : "http://127.0.0.1:3773" ,
184+ startedAt : "2026-10-01T00:00:00.000Z" ,
185+ serviceManaged : true ,
186+ } ) ;
187+ yield * Effect . promise ( ( ) => NodeFSP . mkdir ( stateDir , { recursive : true } ) ) ;
188+ yield * Effect . promise ( ( ) => NodeFSP . writeFile ( statePath , record ) ) ;
189+ const newDirectory = NodePath . join ( root , "new-project" ) ;
190+ const platform = yield * HostProcessPlatform ;
191+ for ( const args of [
192+ [ ] ,
193+ [ "start" ] ,
194+ [ "." ] ,
195+ [ "node_modules" ] ,
196+ [ "C:new-project" ] ,
197+ [ newDirectory ] ,
198+ [ "start" , newDirectory ] ,
199+ ] ) {
200+ const error = yield * runCli ( args , { T3CODE_HOME : baseDir } ) . pipe (
201+ Effect . provideService (
202+ HostProcessPlatform ,
203+ args [ 0 ] === "C:new-project" ? "win32" : platform ,
204+ ) ,
205+ Effect . flip ,
206+ ) ;
207+ expect ( String ( error ) ) . toContain ( "A T3 Code server is already running" ) ;
208+ expect ( yield * Effect . promise ( ( ) => NodeFSP . readFile ( statePath , "utf8" ) ) ) . toBe ( record ) ;
209+ expect ( yield * pathExists ( newDirectory ) ) . toBe ( false ) ;
210+ expect ( yield * Effect . promise ( ( ) => NodeFSP . readdir ( stateDir ) ) ) . toEqual ( [
211+ "server-runtime.json" ,
212+ ] ) ;
213+ }
214+ } ) ,
215+ ) ,
216+ ) ;
217+ } ) ;
218+
133219describe ( "t3 app" , ( ) => {
134220 it . effect ( "rejects SSH before it tries to reach a desktop app" , ( ) =>
135221 withTempDirectory ( "t3-app-ssh-test-" , ( root ) =>
0 commit comments