@@ -64,6 +64,13 @@ function mockSpawnerLayer(statusJson = "{}") {
6464 ) ;
6565}
6666
67+ function dieOnSpawnLayer ( ) {
68+ return Layer . succeed (
69+ ChildProcessSpawner . ChildProcessSpawner ,
70+ ChildProcessSpawner . make ( ( ) => Effect . die ( "unexpected tailscale spawn" ) ) ,
71+ ) ;
72+ }
73+
6774function makeEnvironmentLayer ( baseDir : string , env : Record < string , string | undefined > = { } ) {
6875 return makeDesktopEnvironmentLayer ( {
6976 dirname : "/repo/apps/desktop/src" ,
@@ -86,6 +93,7 @@ function makeLayer(input: {
8693 readonly baseDir : string ;
8794 readonly networkInterfaces ?: DesktopNetworkInterfaces ;
8895 readonly env ?: Record < string , string | undefined > ;
96+ readonly spawnerLayer ?: Layer . Layer < ChildProcessSpawner . ChildProcessSpawner > ;
8997} ) {
9098 const env = { T3CODE_HOME : input . baseDir , ...input . env } ;
9199 const environmentLayer = makeEnvironmentLayer ( input . baseDir , env ) ;
@@ -97,7 +105,7 @@ function makeLayer(input: {
97105 Layer . provideMerge ( DesktopAppSettings . layer ) ,
98106 Layer . provideMerge ( NodeFileSystem . layer ) ,
99107 Layer . provideMerge ( NodeHttpClient . layerUndici ) ,
100- Layer . provideMerge ( mockSpawnerLayer ( ) ) ,
108+ Layer . provideMerge ( input . spawnerLayer ?? mockSpawnerLayer ( ) ) ,
101109 Layer . provideMerge ( networkLayer ) ,
102110 Layer . provideMerge ( DesktopConfig . layerTest ( env ) ) ,
103111 Layer . provideMerge ( environmentLayer ) ,
@@ -116,13 +124,23 @@ const withHarness = <A, E, R>(
116124 | DesktopAppSettings . DesktopAppSettings
117125 > ,
118126 env : Record < string , string | undefined > = { } ,
127+ spawnerLayer ?: Layer . Layer < ChildProcessSpawner . ChildProcessSpawner > ,
119128) =>
120129 Effect . gen ( function * ( ) {
121130 const fileSystem = yield * FileSystem . FileSystem ;
122131 const baseDir = yield * fileSystem . makeTempDirectoryScoped ( {
123132 prefix : "t3-desktop-server-exposure-test-" ,
124133 } ) ;
125- return yield * effect . pipe ( Effect . provide ( makeLayer ( { baseDir, networkInterfaces, env } ) ) ) ;
134+ return yield * effect . pipe (
135+ Effect . provide (
136+ makeLayer ( {
137+ baseDir,
138+ networkInterfaces,
139+ env,
140+ ...( spawnerLayer ? { spawnerLayer } : { } ) ,
141+ } ) ,
142+ ) ,
143+ ) ;
126144 } ) . pipe ( Effect . provide ( NodeServices . layer ) , Effect . scoped ) ;
127145
128146describe ( "DesktopServerExposure" , ( ) => {
@@ -239,6 +257,27 @@ describe("DesktopServerExposure", () => {
239257 ) ,
240258 ) ;
241259
260+ it . effect ( "does not spawn the tailscale CLI while server exposure is local-only" , ( ) =>
261+ withHarness (
262+ lanNetworkInterfaces ,
263+ Effect . gen ( function * ( ) {
264+ const serverExposure = yield * DesktopServerExposure . DesktopServerExposure ;
265+ yield * serverExposure . configureFromSettings ( { port : 4173 } ) ;
266+ // mode stays at default "local-only", tailscaleServeEnabled stays false.
267+
268+ const endpoints = yield * serverExposure . getAdvertisedEndpoints ;
269+ // Only the loopback endpoint; no tailscale spawn means the dieOnSpawnLayer
270+ // would have crashed the test if the gate was missing.
271+ assert . deepEqual (
272+ endpoints . map ( ( endpoint ) => endpoint . httpBaseUrl ) ,
273+ [ "http://127.0.0.1:4173/" ] ,
274+ ) ;
275+ } ) ,
276+ { } ,
277+ dieOnSpawnLayer ( ) ,
278+ ) ,
279+ ) ;
280+
242281 it . effect ( "uses ConfigProvider desktop exposure overrides" , ( ) =>
243282 withHarness (
244283 lanNetworkInterfaces ,
0 commit comments