Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cleanslice/ranch",
"version": "0.1.12",
"version": "0.1.13",
"type": "module",
"description": "Ranch project CLI",
"license": "MIT",
Expand Down
11 changes: 9 additions & 2 deletions cli/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { consola } from "consola";
import { ensureRanchRoot } from "../utils/setup";
import { run } from "../utils/exec";
import { freePorts } from "../utils/ports";
import { ensureK3dRunning } from "../utils/k3d";
import { ensureK3dInstalled, ensureK3dNetwork, ensureK3dRunning } from "../utils/k3d";
import { ensurePortForwards } from "../utils/port-forward";
import { ensureDepsInstalled } from "../utils/deps";
import { ensureDockerRunning } from "../utils/docker";
Expand Down Expand Up @@ -46,8 +46,15 @@ export const devCommand = defineCommand({
}

const needsDocker = !target || target === "api";
const needsK3d = !args["no-k3d"] && needsDocker;
if (needsDocker) {
ensureDockerRunning();
if (needsK3d) {
// Fail before any image pull — compose needs k3d-ranch, which k3d creates.
ensureK3dInstalled();
} else {
ensureK3dNetwork({ createIfMissing: true });
}
await ensureBrowserPoolImage(root);
}

Expand Down Expand Up @@ -80,9 +87,9 @@ export const devCommand = defineCommand({
turboArgs.push(`--filter=${target}`);
}

const needsK3d = !args["no-k3d"] && (!target || target === "api");
if (needsK3d) {
await ensureK3dRunning(root);
ensureK3dNetwork();
ensurePortForwards([
{ label: "Argo Workflows", namespace: "argo", service: "argo-workflows-server", port: 2746 },
]);
Expand Down
19 changes: 19 additions & 0 deletions cli/src/utils/k3d.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, test } from "bun:test";
import { k3dInstallHint } from "./k3d";

describe("k3dInstallHint", () => {
test("windows uses winget/choco/scoop", () => {
const hint = k3dInstallHint("win32");
expect(hint).toContain("winget install");
expect(hint).toContain("choco install k3d");
expect(hint).not.toContain("brew install k3d");
});

test("mac uses brew", () => {
expect(k3dInstallHint("darwin")).toBe("brew install k3d");
});

test("linux uses the k3d install script", () => {
expect(k3dInstallHint("linux")).toContain("install.sh");
});
});
53 changes: 48 additions & 5 deletions cli/src/utils/k3d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execSync, spawn } from "node:child_process";
import { execSync, spawn, spawnSync } from "node:child_process";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
Expand All @@ -10,8 +10,54 @@ const READY_TIMEOUT_MS = 90_000;
const READY_POLL_MS = 2_000;

const CLUSTER = "ranch";
export const K3D_NETWORK = "k3d-ranch";
const KUBECONFIG_LOCAL = join(homedir(), ".kube", "ranch-local.yaml");

export function k3dInstallHint(platform = process.platform): string {
if (platform === "win32") {
return "winget install -e --id k3d.k3d\n or: choco install k3d\n or: scoop install k3d";
}
if (platform === "darwin") {
return "brew install k3d";
}
return "curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash";
}

export function ensureK3dInstalled(): void {
if (hasBinary("k3d")) return;
consola.error(
`k3d is not installed. compose needs the ${K3D_NETWORK} docker network that k3d creates — install it before ranch pulls any images.\n\nInstall:\n ${k3dInstallHint()}\n\nThen re-run \`ranch dev\` (it creates the cluster).`,
);
process.exit(1);
}

export function k3dNetworkExists(): boolean {
const result = spawnSync("docker", ["network", "inspect", K3D_NETWORK], {
stdio: "ignore",
windowsHide: true,
});
return result.status === 0;
}

export function ensureK3dNetwork(opts: { createIfMissing?: boolean } = {}): void {
if (k3dNetworkExists()) return;
if (opts.createIfMissing) {
consola.start(`Creating docker network ${K3D_NETWORK}...`);
const created = spawnSync("docker", ["network", "create", K3D_NETWORK], {
encoding: "utf8",
windowsHide: true,
});
if (created.status === 0 && k3dNetworkExists()) {
consola.success(`Created ${K3D_NETWORK} (no k3d cluster)`);
return;
}
}
consola.error(
`Docker network ${K3D_NETWORK} is missing. Install k3d and re-run \`ranch dev\`, or pass --no-k3d to create a dummy network.\n\nInstall:\n ${k3dInstallHint()}`,
);
process.exit(1);
}

type ClusterState = "running" | "stopped" | "missing";

function clusterState(): ClusterState {
Expand Down Expand Up @@ -125,10 +171,7 @@ async function bootstrap(root: string): Promise<void> {
}

export async function ensureK3dRunning(root: string): Promise<void> {
if (!hasBinary("k3d")) {
consola.warn("k3d not installed — skipping cluster start. Install: brew install k3d");
return;
}
ensureK3dInstalled();

const state = clusterState();
if (state === "running") {
Expand Down
35 changes: 34 additions & 1 deletion scripts/docker-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ set -o pipefail

cd "$(dirname "$0")/../api" || exit 1

k3d_install_hint() {
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*) echo "winget install -e --id k3d.k3d (or: choco install k3d / scoop install k3d)" ;;
Darwin*) echo "brew install k3d" ;;
*) echo "curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash" ;;
esac
}

# Fail before compose starts pulling images. The k3d-ranch network is
# `external: true` — compose otherwise downloads postgres/minio and then dies.
K3D_NET=k3d-ranch
if ! docker network inspect "$K3D_NET" >/dev/null 2>&1; then
cat >&2 <<EOF

✖ Docker network \`$K3D_NET\` does not exist (created by k3d).

Install k3d, then re-run \`ranch dev\` — it creates the cluster and network
before any image pull:

$(k3d_install_hint)

Or skip the cluster: \`ranch dev --no-k3d\` (CLI creates a dummy network).

Until this succeeds, api can't finish starting, so it never writes
api/swagger-spec.json — admin/app will sit waiting on it until they time out.
EOF
exit 1
fi


output="$(docker compose up -d 2>&1)"
code=$?
echo "$output" >&2
Expand Down Expand Up @@ -54,7 +84,10 @@ EOF
reach them. It's created by k3d when the local cluster exists.

Fix:
1. brew install k3d (if not installed)
1. Install k3d:
Windows: winget install -e --id k3d.k3d
macOS: brew install k3d
Linux: curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
2. k3d cluster create ranch --api-port 6550 --wait
3. Re-run `ranch dev` (it also does this automatically when k3d is
installed — see cli/src/utils/k3d.ts).
Expand Down
Loading