Skip to content

Commit 9a66866

Browse files
comatoryclaude
andcommitted
feat: check docker and buildx readiness during onboarding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 01a6a28 commit 9a66866

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

cli/src/commands/demo/command.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import { config } from '../../core/config.js';
66
import { waitForKeyPress, rainbow } from '../../utils.js';
77
import { fetchUserInfo, checkExistingOnboarding } from './api.js';
88
import type { UserInfo } from './types.js';
9-
import { clearScreen, prepareSupportingData, printLogo, resetScreen, updateScreenWithUserInfo } from './util.js';
9+
import {
10+
checkDockerReadiness,
11+
clearScreen,
12+
prepareSupportingData,
13+
printLogo,
14+
resetScreen,
15+
updateScreenWithUserInfo,
16+
} from './util.js';
1017

1118
function printHello() {
1219
printLogo();
@@ -70,6 +77,7 @@ export default function (opts: BaseCommandOptions) {
7077
clearScreen();
7178
printHello();
7279
await prepareSupportingData();
80+
await checkDockerReadiness();
7381
const userInfo = await getUserInfo(opts.client);
7482
updateScreenWithUserInfo(userInfo);
7583

cli/src/commands/demo/util.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import { program } from 'commander';
4+
import { execa } from 'execa';
45
import ora from 'ora';
56
import pc from 'picocolors';
67
import { z } from 'zod';
@@ -167,3 +168,80 @@ export async function prepareSupportingData() {
167168

168169
return cosmoDir;
169170
}
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+
}

cli/src/core/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const config = {
3838
pluginRegistryURL: process.env.PLUGIN_REGISTRY_URL || 'cosmo-registry.wundergraph.com',
3939
demoOnboardingRepositoryName: 'wundergraph/cosmo-onboarding' as const,
4040
demoOnboardingRepositoryBranch: 'main' as const,
41+
dockerBuilderName: 'cosmo-builder' as const,
4142
};
4243

4344
export const getBaseHeaders = (): HeadersInit => {

0 commit comments

Comments
 (0)