From 31e45dcfecfb14bfac680c4a5f32bed48d470e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Mon, 20 Apr 2026 22:49:26 +0100 Subject: [PATCH] fix(cli): default dev mode db to /.kandev-dev/data (#664) --- apps/cli/src/dev.test.ts | 64 ++++++++++++++++++++++++++++++++++++++++ apps/cli/src/dev.ts | 42 ++++++++++++++++---------- 2 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 apps/cli/src/dev.test.ts diff --git a/apps/cli/src/dev.test.ts b/apps/cli/src/dev.test.ts new file mode 100644 index 0000000000..544965c7f3 --- /dev/null +++ b/apps/cli/src/dev.test.ts @@ -0,0 +1,64 @@ +import path from "node:path"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { resolveDevBackendEnv } from "./dev"; + +describe("resolveDevBackendEnv", () => { + const originalTaskId = process.env.KANDEV_TASK_ID; + const originalDbPath = process.env.KANDEV_DATABASE_PATH; + const originalMock = process.env.KANDEV_MOCK_AGENT; + + beforeEach(() => { + delete process.env.KANDEV_TASK_ID; + delete process.env.KANDEV_DATABASE_PATH; + delete process.env.KANDEV_MOCK_AGENT; + vi.spyOn(console, "log").mockImplementation(() => {}); + }); + + afterEach(() => { + restoreEnv("KANDEV_TASK_ID", originalTaskId); + restoreEnv("KANDEV_DATABASE_PATH", originalDbPath); + restoreEnv("KANDEV_MOCK_AGENT", originalMock); + vi.restoreAllMocks(); + }); + + it("defaults to /.kandev-dev/data/kandev.db in a plain shell", () => { + const repoRoot = "/tmp/repo"; + const { dbPath, extra } = resolveDevBackendEnv(repoRoot); + + expect(dbPath).toBe(path.join(repoRoot, ".kandev-dev", "data", "kandev.db")); + expect(extra.KANDEV_HOME_DIR).toBe(path.join(repoRoot, ".kandev-dev")); + expect(extra.KANDEV_DATABASE_PATH).toBe(""); + }); + + it("honors an explicit KANDEV_DATABASE_PATH override in a plain shell", () => { + process.env.KANDEV_DATABASE_PATH = "/tmp/custom.db"; + + const { dbPath, extra } = resolveDevBackendEnv("/tmp/repo"); + + expect(dbPath).toBe("/tmp/custom.db"); + expect(extra.KANDEV_DATABASE_PATH).toBe("/tmp/custom.db"); + expect(extra.KANDEV_HOME_DIR).toBeUndefined(); + }); + + it("ignores a leaked KANDEV_DATABASE_PATH when running inside a kandev task", () => { + process.env.KANDEV_TASK_ID = "fake-task-id"; + process.env.KANDEV_DATABASE_PATH = "/parent/kandev.db"; + const repoRoot = "/tmp/repo"; + + const { dbPath, extra } = resolveDevBackendEnv(repoRoot); + + expect(dbPath).toBe(path.join(repoRoot, ".kandev-dev", "data", "kandev.db")); + expect(extra.KANDEV_HOME_DIR).toBe(path.join(repoRoot, ".kandev-dev")); + expect(extra.KANDEV_DATABASE_PATH).toBe(""); + }); +}); + +function restoreEnv(name: string, value: string | undefined): void { + if (value === undefined) { + delete process.env[name]; + } else { + process.env[name] = value; + } +} diff --git a/apps/cli/src/dev.ts b/apps/cli/src/dev.ts index 6f4fb3bf0f..0c39039e5b 100644 --- a/apps/cli/src/dev.ts +++ b/apps/cli/src/dev.ts @@ -72,25 +72,28 @@ type DevBackendEnv = { extra: Record; }; -// Computes the dev-mode backend env. When invoked from inside a kandev task -// workspace, force-relocates the entire kandev root to /.kandev-dev so -// dev state is isolated from the user's production ~/.kandev. In a normal -// shell, preserves the existing override-friendly behavior (honor an -// explicit KANDEV_DATABASE_PATH, otherwise default to a repo-local db). -function resolveDevBackendEnv(repoRoot: string): DevBackendEnv { +// Computes the dev-mode backend env. Dev mode always roots kandev under +// /.kandev-dev so state is isolated from the user's production ~/.kandev +// and so `make clean-db` (which removes .kandev-dev/) matches what `make dev` +// writes. +// +// When invoked from inside a kandev task workspace, any KANDEV_DATABASE_PATH +// is assumed to be leaked from the parent backend and is ignored. In a normal +// shell, an explicit KANDEV_DATABASE_PATH is honored as an escape hatch. +export function resolveDevBackendEnv(repoRoot: string): DevBackendEnv { const baseExtra: Record = { KANDEV_MOCK_AGENT: process.env.KANDEV_MOCK_AGENT || "true", KANDEV_DEBUG_PPROF_ENABLED: "true", }; + const devHome = devKandevHome(repoRoot); + // Display only; the backend derives its own DB path from KANDEV_HOME_DIR + // via ResolvedDataDir(). Both resolve to the same location. + const devDbPath = path.join(devHome, "data", "kandev.db"); if (isInsideKandevTask(repoRoot)) { - const devHome = devKandevHome(repoRoot); - // Display only; the backend derives its own DB path from KANDEV_HOME_DIR - // via ResolvedDataDir(). Both resolve to the same location. - const dbPath = path.join(devHome, "data", "kandev.db"); console.log("[kandev] task workspace detected → using local dev state"); return { - dbPath, + dbPath: devDbPath, extra: { ...baseExtra, KANDEV_HOME_DIR: devHome, @@ -100,10 +103,19 @@ function resolveDevBackendEnv(repoRoot: string): DevBackendEnv { }; } - const dbPath = - process.env.KANDEV_DATABASE_PATH || path.join(repoRoot, "apps", "backend", "kandev.db"); + const override = process.env.KANDEV_DATABASE_PATH; + if (override) { + return { + dbPath: override, + extra: { ...baseExtra, KANDEV_DATABASE_PATH: override }, + }; + } return { - dbPath, - extra: { ...baseExtra, KANDEV_DATABASE_PATH: dbPath }, + dbPath: devDbPath, + extra: { + ...baseExtra, + KANDEV_HOME_DIR: devHome, + KANDEV_DATABASE_PATH: "", + }, }; }