11import { FetchHttpClient , HttpClient } from "@effect/platform"
22import type * as CommandExecutor from "@effect/platform/CommandExecutor"
3+ import type { PlatformError } from "@effect/platform/Error"
4+ import * as FileSystem from "@effect/platform/FileSystem"
5+ import * as Path from "@effect/platform/Path"
36import { Duration , Effect , pipe , Schedule } from "effect"
4- import { existsSync } from "node:fs"
5- import path from "node:path"
67
78import { runCommandExitCode } from "@lib/shell/command-runner"
89
@@ -11,6 +12,16 @@ import type { ControllerBootstrapError } from "./host-errors.js"
1112const defaultApiPort = "3334"
1213const defaultApiHost = "127.0.0.1"
1314
15+ type ControllerRuntime =
16+ | CommandExecutor . CommandExecutor
17+ | FileSystem . FileSystem
18+ | Path . Path
19+
20+ const controllerBootstrapError = ( message : string ) : ControllerBootstrapError => ( {
21+ _tag : "ControllerBootstrapError" ,
22+ message
23+ } )
24+
1425const trimTrailingSlashes = ( value : string ) : string => {
1526 const parts = value . split ( "/" )
1627 let end = parts . length
@@ -33,22 +44,29 @@ export const resolveApiBaseUrl = (): string => {
3344 return `http://${ host } :${ port } `
3445}
3546
36- const composeFilePath = ( ) : string => {
37- let current = process . cwd ( )
38-
39- for ( ; ; ) {
40- const candidate = path . join ( current , "docker-compose.yml" )
41- if ( existsSync ( candidate ) ) {
42- return candidate
47+ const composeFilePath = ( ) : Effect . Effect < string , PlatformError , FileSystem . FileSystem | Path . Path > =>
48+ Effect . gen ( function * ( _ ) {
49+ const fs = yield * _ ( FileSystem . FileSystem )
50+ const path = yield * _ ( Path . Path )
51+ let current = process . cwd ( )
52+
53+ for ( ; ; ) {
54+ const candidate = path . join ( current , "docker-compose.yml" )
55+ const exists = yield * _ ( fs . exists ( candidate ) )
56+ if ( exists ) {
57+ return candidate
58+ }
59+
60+ const parent = path . dirname ( current )
61+ if ( parent === current ) {
62+ return path . resolve ( process . cwd ( ) , "docker-compose.yml" )
63+ }
64+ current = parent
4365 }
66+ } )
4467
45- const parent = path . dirname ( current )
46- if ( parent === current ) {
47- return path . resolve ( process . cwd ( ) , "docker-compose.yml" )
48- }
49- current = parent
50- }
51- }
68+ const mapComposePathError = ( error : PlatformError ) : ControllerBootstrapError =>
69+ controllerBootstrapError ( `Failed to resolve docker-compose.yml path.\nDetails: ${ String ( error ) } ` )
5270
5371const runExitCode = (
5472 command : string ,
@@ -58,7 +76,12 @@ const runExitCode = (
5876 cwd : process . cwd ( ) ,
5977 command,
6078 args
61- } ) . pipe ( Effect . catchAll ( ( ) => Effect . succeed ( 1 ) ) )
79+ } ) . pipe (
80+ Effect . match ( {
81+ onFailure : ( ) => 1 ,
82+ onSuccess : ( exitCode ) => exitCode
83+ } )
84+ )
6285
6386export const resolveDockerCommand = ( ) : Effect . Effect <
6487 ReadonlyArray < string > ,
@@ -77,15 +100,16 @@ export const resolveDockerCommand = (): Effect.Effect<
77100
78101const runCompose = (
79102 args : ReadonlyArray < string >
80- ) : Effect . Effect < void , ControllerBootstrapError , CommandExecutor . CommandExecutor > =>
103+ ) : Effect . Effect < void , ControllerBootstrapError , ControllerRuntime > =>
81104 Effect . gen ( function * ( _ ) {
82105 const dockerCommand = yield * _ ( resolveDockerCommand ( ) )
106+ const composePath = yield * _ ( composeFilePath ( ) . pipe ( Effect . mapError ( mapComposePathError ) ) )
83107 const command = dockerCommand [ 0 ] ?? "docker"
84108 const commandArgs = [
85109 ...dockerCommand . slice ( 1 ) ,
86110 "compose" ,
87111 "-f" ,
88- composeFilePath ( ) ,
112+ composePath ,
89113 ...args
90114 ]
91115 const exitCode = yield * _ ( runExitCode ( command , commandArgs ) )
@@ -96,14 +120,13 @@ const runCompose = (
96120
97121 return yield * _ (
98122 Effect . fail (
99- {
100- _tag : "ControllerBootstrapError" ,
101- message : [
123+ controllerBootstrapError (
124+ [
102125 "Failed to start docker-git controller." ,
103126 `Command: ${ [ command , ...commandArgs ] . join ( " " ) } ` ,
104127 `Exit code: ${ exitCode } `
105128 ] . join ( "\n" )
106- } satisfies ControllerBootstrapError
129+ )
107130 )
108131 )
109132 } )
@@ -119,10 +142,9 @@ const probeHealth = (apiBaseUrl: string): Effect.Effect<void, ControllerBootstra
119142
120143 return yield * _ (
121144 Effect . fail (
122- {
123- _tag : "ControllerBootstrapError" ,
124- message : `docker-git controller health returned ${ response . status } at ${ apiBaseUrl } /health`
125- } satisfies ControllerBootstrapError
145+ controllerBootstrapError (
146+ `docker-git controller health returned ${ response . status } at ${ apiBaseUrl } /health`
147+ )
126148 )
127149 )
128150 } ) . pipe (
@@ -163,8 +185,10 @@ export const ensureControllerReady = Effect.gen(function*(_) {
163185 const apiBaseUrl = resolveApiBaseUrl ( )
164186 const healthy = yield * _ (
165187 probeHealth ( apiBaseUrl ) . pipe (
166- Effect . as ( true ) ,
167- Effect . catchAll ( ( ) => Effect . succeed ( false ) )
188+ Effect . match ( {
189+ onFailure : ( ) => false ,
190+ onSuccess : ( ) => true
191+ } )
168192 )
169193 )
170194
0 commit comments