|
1 | 1 | import fs from 'node:fs/promises'; |
2 | 2 | import path from 'node:path'; |
3 | 3 | import { program } from 'commander'; |
| 4 | +import { execa } from 'execa'; |
4 | 5 | import ora from 'ora'; |
5 | 6 | import pc from 'picocolors'; |
6 | 7 | import { z } from 'zod'; |
@@ -167,3 +168,80 @@ export async function prepareSupportingData() { |
167 | 168 |
|
168 | 169 | return cosmoDir; |
169 | 170 | } |
| 171 | + |
| 172 | +async function isDockerAvailable(): Promise<boolean> { |
| 173 | + try { |
| 174 | + await execa('docker', ['version', '--format', '{{.Client.Version}}']); |
| 175 | + return true; |
| 176 | + } catch { |
| 177 | + return false; |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +async function isBuildxAvailable(): Promise<boolean> { |
| 182 | + try { |
| 183 | + await execa('docker', ['buildx', 'version']); |
| 184 | + return true; |
| 185 | + } catch { |
| 186 | + return false; |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +async function hasDockerContainerBuilder(): Promise<boolean> { |
| 191 | + try { |
| 192 | + const { stdout } = await execa('docker', ['buildx', 'ls']); |
| 193 | + for (const line of stdout.split('\n')) { |
| 194 | + // Builder lines start without leading whitespace; the driver follows the name |
| 195 | + if (!line.startsWith(' ') && line.includes('docker-container')) { |
| 196 | + return true; |
| 197 | + } |
| 198 | + } |
| 199 | + return false; |
| 200 | + } catch { |
| 201 | + return false; |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +async function createDockerContainerBuilder(builderName: string): Promise<void> { |
| 206 | + await execa('docker', ['buildx', 'create', '--use', '--driver', 'docker-container', '--name', builderName]); |
| 207 | + await execa('docker', ['buildx', 'inspect', builderName, '--bootstrap']); |
| 208 | +} |
| 209 | + |
| 210 | +/** |
| 211 | + * Checks whether host system has [docker] installed and whether [buildx] is set up |
| 212 | + * properly. In case of failures, show prompt to install/setup. |
| 213 | + */ |
| 214 | +export async function checkDockerReadiness(): Promise<void> { |
| 215 | + const spinner = ora('Checking Docker availability…').start(); |
| 216 | + |
| 217 | + if (!(await isDockerAvailable())) { |
| 218 | + spinner.fail('Docker is not available.'); |
| 219 | + program.error( |
| 220 | + `Docker CLI is not installed or the daemon is not running.\nInstall Docker: ${pc.underline('https://docs.docker.com/get-docker/')}`, |
| 221 | + ); |
| 222 | + } |
| 223 | + |
| 224 | + if (!(await isBuildxAvailable())) { |
| 225 | + spinner.fail('Docker Buildx is not available.'); |
| 226 | + program.error( |
| 227 | + `Docker Buildx plugin is required for multi-platform builds.\nSee: ${pc.underline('https://docs.docker.com/build/install-buildx/')}`, |
| 228 | + ); |
| 229 | + } |
| 230 | + |
| 231 | + if (await hasDockerContainerBuilder()) { |
| 232 | + spinner.succeed('Docker is ready.'); |
| 233 | + return; |
| 234 | + } |
| 235 | + |
| 236 | + spinner.text = `Creating buildx builder "${config.dockerBuilderName}"…`; |
| 237 | + try { |
| 238 | + await createDockerContainerBuilder(config.dockerBuilderName); |
| 239 | + } catch (err) { |
| 240 | + spinner.fail(`Failed to create buildx builder "${config.dockerBuilderName}".`); |
| 241 | + program.error( |
| 242 | + `Could not create a docker-container buildx builder: ${err instanceof Error ? err.message : String(err)}\nYou can create one manually: docker buildx create --use --driver docker-container --name ${config.dockerBuilderName}`, |
| 243 | + ); |
| 244 | + } |
| 245 | + |
| 246 | + spinner.succeed('Docker is ready.'); |
| 247 | +} |
0 commit comments