1import { execSync } from 'child_process' 3type DockBuildCaps = { supportsProgress: boolean } 5const capsMemo: Record<string, DockBuildCaps> = {} 6const buildPrefixMemo: Record<string, string> = {} 8// Resolve the build command prefix. Homebrew's docker CLI ships without the buildx plugin, 9// so `docker build` falls back to the legacy builder which rejects RUN --mount. The buildx 10// binary is still on PATH as `docker-buildx` and works standalone. 11export const getDockBuildPrefix = (dockName: string): string => { 12 if (!buildPrefixMemo[dockName]) { 13 buildPrefixMemo[dockName] = (() => { 14 if (dockName !== 'docker') return `${dockName} build` 16 execSync('docker buildx version', {stdio: ['ignore', 'pipe', 'ignore']}) 17 return 'docker buildx build' 18 } catch { /* plugin not registered */ } // catch:userapproved 20 execSync('which docker-buildx', {stdio: ['ignore', 'pipe', 'ignore']}) 21 return 'docker-buildx build' 22 } catch { /* no buildx at all — legacy builder */ } // catch:userapproved 26 return buildPrefixMemo[dockName] 29// Detect the configured builder's capabilities by parsing `<build prefix> --help`. 30// Help-parse is the most direct signal of what the binary actually accepts, robust across 31// docker-legacy (no --progress), docker-BuildKit/buildx, and podman — no need to name the impl. 32export const getDockBuildCaps = (dockName: string): DockBuildCaps => { 33 if (!capsMemo[dockName]) { 34 let supportsProgress = false 36 const help = execSync(`${getDockBuildPrefix(dockName)} --help`, {stdio: ['ignore', 'pipe', 'ignore']}).toString() 37 supportsProgress = help.includes('--progress') 38 } catch { /* binary missing/unrecognized — assume no --progress */ } // catch:userapproved 39 capsMemo[dockName] = {supportsProgress} 41 return capsMemo[dockName]