11import type * as CommandExecutor from "@effect/platform/CommandExecutor"
22import type { PlatformError } from "@effect/platform/Error"
3- import type { Effect } from "effect"
3+ import { readFileSync } from "node:fs"
4+ import { Effect } from "effect"
45
56import { runCommandCapture , runCommandExitCode , runCommandWithExitCodes } from "./command-runner.js"
67
@@ -20,6 +21,122 @@ export type DockerAuthSpec = {
2021 readonly interactive : boolean
2122}
2223
24+ type DockerMountBinding = {
25+ readonly source : string
26+ readonly destination : string
27+ }
28+
29+ const resolveEnvValue = ( key : string ) : string | null => {
30+ const value = process . env [ key ] ?. trim ( )
31+ return value && value . length > 0 ? value : null
32+ }
33+
34+ const trimTrailingSlash = ( value : string ) : string => value . replace ( / [ \\ / ] + $ / u, "" )
35+
36+ const pathStartsWith = ( candidate : string , prefix : string ) : boolean =>
37+ candidate === prefix || candidate . startsWith ( `${ prefix } /` ) || candidate . startsWith ( `${ prefix } \\` )
38+
39+ const translatePathPrefix = ( candidate : string , sourcePrefix : string , targetPrefix : string ) : string | null =>
40+ pathStartsWith ( candidate , sourcePrefix )
41+ ? `${ targetPrefix } ${ candidate . slice ( sourcePrefix . length ) } `
42+ : null
43+
44+ const resolveContainerProjectsRoot = ( ) : string | null => {
45+ const explicit = resolveEnvValue ( "DOCKER_GIT_PROJECTS_ROOT" )
46+ if ( explicit !== null ) {
47+ return explicit
48+ }
49+
50+ const home = resolveEnvValue ( "HOME" ) ?? resolveEnvValue ( "USERPROFILE" )
51+ return home === null ? null : `${ trimTrailingSlash ( home ) } /.docker-git`
52+ }
53+
54+ const resolveProjectsRootHostOverride = ( ) : string | null => resolveEnvValue ( "DOCKER_GIT_PROJECTS_ROOT_HOST" )
55+
56+ const resolveCurrentContainerId = ( ) : string | null => {
57+ const fromEnv = resolveEnvValue ( "HOSTNAME" )
58+ if ( fromEnv !== null ) {
59+ return fromEnv
60+ }
61+
62+ try {
63+ const fromHostnameFile = readFileSync ( "/etc/hostname" , "utf8" ) . trim ( )
64+ return fromHostnameFile . length > 0 ? fromHostnameFile : null
65+ } catch {
66+ return null
67+ }
68+ }
69+
70+ const parseDockerInspectMounts = ( raw : string ) : ReadonlyArray < DockerMountBinding > => {
71+ try {
72+ const parsed = JSON . parse ( raw ) as unknown
73+ if ( ! Array . isArray ( parsed ) ) {
74+ return [ ]
75+ }
76+ return parsed . flatMap ( ( item ) => {
77+ if ( typeof item !== "object" || item === null ) {
78+ return [ ]
79+ }
80+ const source = Reflect . get ( item , "Source" )
81+ const destination = Reflect . get ( item , "Destination" )
82+ return typeof source === "string" && typeof destination === "string"
83+ ? [ { source, destination } ]
84+ : [ ]
85+ } )
86+ } catch {
87+ return [ ]
88+ }
89+ }
90+
91+ export const remapDockerBindHostPathFromMounts = (
92+ hostPath : string ,
93+ mounts : ReadonlyArray < DockerMountBinding >
94+ ) : string => {
95+ const match = mounts
96+ . filter ( ( mount ) => pathStartsWith ( hostPath , mount . destination ) )
97+ . sort ( ( left , right ) => right . destination . length - left . destination . length ) [ 0 ]
98+
99+ if ( match === undefined ) {
100+ return hostPath
101+ }
102+
103+ return `${ match . source } ${ hostPath . slice ( match . destination . length ) } `
104+ }
105+
106+ export const resolveDockerVolumeHostPath = (
107+ cwd : string ,
108+ hostPath : string
109+ ) : Effect . Effect < string , never , CommandExecutor . CommandExecutor > =>
110+ Effect . gen ( function * ( _ ) {
111+ const containerProjectsRoot = resolveContainerProjectsRoot ( )
112+ const hostProjectsRoot = resolveProjectsRootHostOverride ( )
113+ if ( containerProjectsRoot !== null && hostProjectsRoot !== null ) {
114+ const remapped = translatePathPrefix ( hostPath , containerProjectsRoot , hostProjectsRoot )
115+ if ( remapped !== null ) {
116+ return remapped
117+ }
118+ }
119+
120+ const containerId = resolveCurrentContainerId ( )
121+ if ( containerId === null ) {
122+ return hostPath
123+ }
124+
125+ const mountsJson = yield * _ (
126+ runCommandCapture (
127+ {
128+ cwd,
129+ command : "docker" ,
130+ args : [ "inspect" , containerId , "--format" , "{{json .Mounts}}" ]
131+ } ,
132+ [ 0 ] ,
133+ ( ) => new Error ( "docker inspect current container failed" )
134+ ) . pipe ( Effect . orElseSucceed ( ( ) => "" ) )
135+ )
136+
137+ return remapDockerBindHostPathFromMounts ( hostPath , parseDockerInspectMounts ( mountsJson ) )
138+ } )
139+
23140export const resolveDefaultDockerUser = ( ) : string | null => {
24141 const getUid = Reflect . get ( process , "getuid" )
25142 const getGid = Reflect . get ( process , "getgid" )
@@ -93,11 +210,20 @@ export const runDockerAuth = <E>(
93210 okExitCodes : ReadonlyArray < number > ,
94211 onFailure : ( exitCode : number ) => E
95212) : Effect . Effect < void , E | PlatformError , CommandExecutor . CommandExecutor > =>
96- runCommandWithExitCodes (
97- { cwd : spec . cwd , command : "docker" , args : buildDockerArgs ( spec ) } ,
98- okExitCodes ,
99- onFailure
100- )
213+ Effect . gen ( function * ( _ ) {
214+ const hostPath = yield * _ ( resolveDockerVolumeHostPath ( spec . cwd , spec . volume . hostPath ) )
215+ yield * _ (
216+ runCommandWithExitCodes (
217+ {
218+ cwd : spec . cwd ,
219+ command : "docker" ,
220+ args : buildDockerArgs ( { ...spec , volume : { ...spec . volume , hostPath } } )
221+ } ,
222+ okExitCodes ,
223+ onFailure
224+ )
225+ )
226+ } )
101227
102228// CHANGE: run a docker auth command and capture stdout
103229// WHY: obtain tokens from container auth flows
@@ -114,11 +240,20 @@ export const runDockerAuthCapture = <E>(
114240 okExitCodes : ReadonlyArray < number > ,
115241 onFailure : ( exitCode : number ) => E
116242) : Effect . Effect < string , E | PlatformError , CommandExecutor . CommandExecutor > =>
117- runCommandCapture (
118- { cwd : spec . cwd , command : "docker" , args : buildDockerArgs ( spec ) } ,
119- okExitCodes ,
120- onFailure
121- )
243+ Effect . gen ( function * ( _ ) {
244+ const hostPath = yield * _ ( resolveDockerVolumeHostPath ( spec . cwd , spec . volume . hostPath ) )
245+ return yield * _ (
246+ runCommandCapture (
247+ {
248+ cwd : spec . cwd ,
249+ command : "docker" ,
250+ args : buildDockerArgs ( { ...spec , volume : { ...spec . volume , hostPath } } )
251+ } ,
252+ okExitCodes ,
253+ onFailure
254+ )
255+ )
256+ } )
122257
123258// CHANGE: run a docker auth command and return the exit code
124259// WHY: allow status checks without throwing
@@ -133,4 +268,13 @@ export const runDockerAuthCapture = <E>(
133268export const runDockerAuthExitCode = (
134269 spec : DockerAuthSpec
135270) : Effect . Effect < number , PlatformError , CommandExecutor . CommandExecutor > =>
136- runCommandExitCode ( { cwd : spec . cwd , command : "docker" , args : buildDockerArgs ( spec ) } )
271+ Effect . gen ( function * ( _ ) {
272+ const hostPath = yield * _ ( resolveDockerVolumeHostPath ( spec . cwd , spec . volume . hostPath ) )
273+ return yield * _ (
274+ runCommandExitCode ( {
275+ cwd : spec . cwd ,
276+ command : "docker" ,
277+ args : buildDockerArgs ( { ...spec , volume : { ...spec . volume , hostPath } } )
278+ } )
279+ )
280+ } )
0 commit comments