From 5484c657564db104e29be2722607447f920d63e5 Mon Sep 17 00:00:00 2001 From: lei Date: Tue, 19 May 2026 11:17:17 +0800 Subject: [PATCH 1/2] fix: use main checkout settings as worktree defaults --- .../renderer/src/lib/ai-api-config.test.ts | 28 ++++--------- packages/core-models/config.ts | 39 ++++++++++++++++--- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/apps/desktop/renderer/src/lib/ai-api-config.test.ts b/apps/desktop/renderer/src/lib/ai-api-config.test.ts index cc83164..4ffa93c 100644 --- a/apps/desktop/renderer/src/lib/ai-api-config.test.ts +++ b/apps/desktop/renderer/src/lib/ai-api-config.test.ts @@ -15,16 +15,6 @@ async function loadConfigModule() { return import("../../../../../packages/core-models/config"); } -function getTestAppDataDir(homeDir) { - if (process.platform === "darwin") { - return join(homeDir, "Library", "Application Support", "dev-Workflow"); - } - if (process.platform === "win32") { - return join(homeDir, "AppData", "Roaming", "dev-Workflow"); - } - return join(process.env.XDG_CONFIG_HOME || join(homeDir, ".config"), "dev-Workflow"); -} - describe("AI API config", () => { beforeEach(async () => { originalUserDataDir = process.env.DEV_WORKFLOW_USER_DATA_DIR; @@ -135,11 +125,12 @@ describe("AI API config", () => { const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); process.env.HOME = homeDir; process.env.APPDATA = join(homeDir, "AppData", "Roaming"); + process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); delete process.env.DEV_WORKFLOW_USER_DATA_DIR; - const appDataDir = getTestAppDataDir(homeDir); - await mkdir(appDataDir, { recursive: true }); + const { CONFIG_FILE, SYSTEM_CONFIG_FILE, readAiApiProfilesForUi, readAiBackendOverride, saveAiApiProfile } = await loadConfigModule(); + await mkdir(join(SYSTEM_CONFIG_FILE, ".."), { recursive: true }); await writeFile( - join(appDataDir, "config.json"), + SYSTEM_CONFIG_FILE, JSON.stringify({ aiBackendOverride: "codex", aiApiProfiles: [ @@ -154,8 +145,6 @@ describe("AI API config", () => { }, null, 2) ); - const { CONFIG_FILE, readAiApiProfilesForUi, readAiBackendOverride, saveAiApiProfile } = await loadConfigModule(); - expect(await readAiBackendOverride()).toBe("codex"); expect(await readAiApiProfilesForUi()).toEqual([ { @@ -193,11 +182,12 @@ describe("AI API config", () => { const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); process.env.HOME = homeDir; process.env.APPDATA = join(homeDir, "AppData", "Roaming"); + process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); delete process.env.DEV_WORKFLOW_USER_DATA_DIR; - const appDataDir = getTestAppDataDir(homeDir); - await mkdir(appDataDir, { recursive: true }); + const { CONFIG_FILE, SYSTEM_CONFIG_FILE, deleteAiApiProfile, readAiApiProfilesForUi } = await loadConfigModule(); + await mkdir(join(SYSTEM_CONFIG_FILE, ".."), { recursive: true }); await writeFile( - join(appDataDir, "config.json"), + SYSTEM_CONFIG_FILE, JSON.stringify({ aiApiProfiles: [ { @@ -211,8 +201,6 @@ describe("AI API config", () => { }, null, 2) ); - const { CONFIG_FILE, deleteAiApiProfile, readAiApiProfilesForUi } = await loadConfigModule(); - expect(await deleteAiApiProfile("deepseek")).toEqual([]); expect(await readAiApiProfilesForUi()).toEqual([]); expect(JSON.parse(await readFile(CONFIG_FILE, "utf-8")).deletedAiApiProfileIds).toEqual(["deepseek"]); diff --git a/packages/core-models/config.ts b/packages/core-models/config.ts index 9cbc66b..13c0567 100644 --- a/packages/core-models/config.ts +++ b/packages/core-models/config.ts @@ -1,6 +1,6 @@ -import { basename, join, dirname } from "path"; +import { basename, dirname, join, resolve } from "path"; import { readFile, writeFile, mkdir } from "fs/promises"; -import { existsSync, readFileSync } from "fs"; +import { existsSync, readFileSync, statSync } from "fs"; import { fileURLToPath } from "url"; import { createHash } from "crypto"; import os from "os"; @@ -35,9 +35,35 @@ function isDevelopmentCheckout() { ); } -function getWorktreeScopedUserDataDir(baseDir) { - const hash = createHash("sha256").update(PROJECT_ROOT).digest("hex").slice(0, 8); - return join(baseDir, "worktrees", `${basename(PROJECT_ROOT)}-${hash}`); +function getWorktreeScopedUserDataDir(baseDir, projectRoot = PROJECT_ROOT) { + const hash = createHash("sha256").update(projectRoot).digest("hex").slice(0, 8); + return join(baseDir, "worktrees", `${basename(projectRoot)}-${hash}`); +} + +function getCommonGitDir() { + const gitPath = join(PROJECT_ROOT, ".git"); + try { + if (statSync(gitPath).isDirectory()) return gitPath; + } catch { + return ""; + } + + const match = readFileSync(gitPath, "utf-8").match(/^gitdir:\s*(.+)\s*$/m); + if (!match) return ""; + const gitDir = resolve(PROJECT_ROOT, match[1]); + + try { + const rawCommonDir = readFileSync(join(gitDir, "commondir"), "utf-8").trim(); + if (rawCommonDir) return resolve(gitDir, rawCommonDir); + } catch {} + + return resolve(gitDir, "..", ".."); +} + +function getSystemDevelopmentCheckoutDir(baseDir) { + const commonGitDir = getCommonGitDir(); + const mainProjectRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : PROJECT_ROOT; + return getWorktreeScopedUserDataDir(baseDir, mainProjectRoot); } export function getDefaultDesktopUserDataDir() { @@ -50,7 +76,8 @@ export function getDefaultDesktopUserDataDir() { } export function getSystemDesktopUserDataDir() { - return getSharedDesktopUserDataDir(); + const baseDir = getSharedDesktopUserDataDir(); + return isDevelopmentCheckout() ? getSystemDevelopmentCheckoutDir(baseDir) : baseDir; } export const SYSTEM_CONFIG_FILE = join(getSystemDesktopUserDataDir(), "config.json"); From 8f727eba96243b6fd5bdd4b46756f17f4030febb Mon Sep 17 00:00:00 2001 From: lei Date: Tue, 19 May 2026 11:37:00 +0800 Subject: [PATCH 2/2] fix: use shared settings directory for all instances --- .../renderer/src/lib/ai-api-config.test.ts | 96 +------------ .../renderer/src/lib/skills-storage.test.ts | 21 ++- .../renderer/src/lib/workflow-storage.test.ts | 14 +- .../src/lib/workfolders-storage.test.ts | 30 ++-- packages/core-models/config.ts | 136 +----------------- scripts/dev-desktop-instance.ts | 12 +- 6 files changed, 36 insertions(+), 273 deletions(-) diff --git a/apps/desktop/renderer/src/lib/ai-api-config.test.ts b/apps/desktop/renderer/src/lib/ai-api-config.test.ts index 4ffa93c..0915f6b 100644 --- a/apps/desktop/renderer/src/lib/ai-api-config.test.ts +++ b/apps/desktop/renderer/src/lib/ai-api-config.test.ts @@ -1,9 +1,8 @@ -import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises"; +import { mkdtemp, rm } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -let configDir = ""; let testHomeDir = ""; let originalUserDataDir; let originalHome; @@ -21,9 +20,8 @@ describe("AI API config", () => { originalHome = process.env.HOME; originalAppData = process.env.APPDATA; originalXdgConfigHome = process.env.XDG_CONFIG_HOME; - configDir = await mkdtemp(join(tmpdir(), "dev-workflow-config-")); testHomeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.DEV_WORKFLOW_USER_DATA_DIR = configDir; + delete process.env.DEV_WORKFLOW_USER_DATA_DIR; process.env.HOME = testHomeDir; process.env.APPDATA = join(testHomeDir, "AppData", "Roaming"); process.env.XDG_CONFIG_HOME = join(testHomeDir, ".config"); @@ -50,9 +48,7 @@ describe("AI API config", () => { } else { process.env.XDG_CONFIG_HOME = originalXdgConfigHome; } - if (configDir) await rm(configDir, { recursive: true, force: true }); if (testHomeDir) await rm(testHomeDir, { recursive: true, force: true }); - configDir = ""; testHomeDir = ""; }); @@ -121,90 +117,10 @@ describe("AI API config", () => { expect(await readAiApiProfiles()).toHaveLength(1); }); - test("merges system AI API profiles with user overrides", async () => { - const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.HOME = homeDir; - process.env.APPDATA = join(homeDir, "AppData", "Roaming"); - process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); - delete process.env.DEV_WORKFLOW_USER_DATA_DIR; - const { CONFIG_FILE, SYSTEM_CONFIG_FILE, readAiApiProfilesForUi, readAiBackendOverride, saveAiApiProfile } = await loadConfigModule(); - await mkdir(join(SYSTEM_CONFIG_FILE, ".."), { recursive: true }); - await writeFile( - SYSTEM_CONFIG_FILE, - JSON.stringify({ - aiBackendOverride: "codex", - aiApiProfiles: [ - { - id: "deepseek", - name: "deepseek", - baseUrl: "https://api.deepseek.com", - apiKey: "sk-deepseek", - model: "deepseek-v4-flash", - }, - ], - }, null, 2) - ); - - expect(await readAiBackendOverride()).toBe("codex"); - expect(await readAiApiProfilesForUi()).toEqual([ - { - id: "deepseek", - name: "deepseek", - baseUrl: "https://api.deepseek.com", - model: "deepseek-v4-flash", - hasApiKey: true, - }, - ]); - - await saveAiApiProfile({ - id: "deepseek", - name: "deepseek", - baseUrl: "https://gateway.example/v1", - apiKey: "", - model: "deepseek-chat", - }); - - expect(await readAiApiProfilesForUi()).toEqual([ - { - id: "deepseek", - name: "deepseek", - baseUrl: "https://gateway.example/v1", - model: "deepseek-chat", - hasApiKey: true, - }, - ]); - expect(JSON.parse(await readFile(CONFIG_FILE, "utf-8")).aiApiProfiles).toHaveLength(1); - - await rm(homeDir, { recursive: true, force: true }); - }); + test("uses one shared config file for user and system settings", async () => { + const { CONFIG_FILE, SYSTEM_CONFIG_FILE, getDefaultDesktopUserDataDir, getSystemDesktopUserDataDir } = await loadConfigModule(); - test("stores a user deletion marker for system AI API profiles", async () => { - const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.HOME = homeDir; - process.env.APPDATA = join(homeDir, "AppData", "Roaming"); - process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); - delete process.env.DEV_WORKFLOW_USER_DATA_DIR; - const { CONFIG_FILE, SYSTEM_CONFIG_FILE, deleteAiApiProfile, readAiApiProfilesForUi } = await loadConfigModule(); - await mkdir(join(SYSTEM_CONFIG_FILE, ".."), { recursive: true }); - await writeFile( - SYSTEM_CONFIG_FILE, - JSON.stringify({ - aiApiProfiles: [ - { - id: "deepseek", - name: "deepseek", - baseUrl: "https://api.deepseek.com", - apiKey: "sk-deepseek", - model: "deepseek-v4-flash", - }, - ], - }, null, 2) - ); - - expect(await deleteAiApiProfile("deepseek")).toEqual([]); - expect(await readAiApiProfilesForUi()).toEqual([]); - expect(JSON.parse(await readFile(CONFIG_FILE, "utf-8")).deletedAiApiProfileIds).toEqual(["deepseek"]); - - await rm(homeDir, { recursive: true, force: true }); + expect(getSystemDesktopUserDataDir()).toBe(getDefaultDesktopUserDataDir()); + expect(SYSTEM_CONFIG_FILE).toBe(CONFIG_FILE); }); }); diff --git a/apps/desktop/renderer/src/lib/skills-storage.test.ts b/apps/desktop/renderer/src/lib/skills-storage.test.ts index 62eeaf0..4c23b67 100644 --- a/apps/desktop/renderer/src/lib/skills-storage.test.ts +++ b/apps/desktop/renderer/src/lib/skills-storage.test.ts @@ -1,9 +1,8 @@ -import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "fs/promises"; +import { mkdir, mkdtemp, rm, stat, writeFile } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -let userDataDir = ""; let homeDir = ""; let originalUserDataDir; let originalHome; @@ -40,9 +39,8 @@ describe("managed skills storage", () => { originalHome = process.env.HOME; originalAppData = process.env.APPDATA; originalXdgConfigHome = process.env.XDG_CONFIG_HOME; - userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-")); homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir; + delete process.env.DEV_WORKFLOW_USER_DATA_DIR; process.env.HOME = homeDir; process.env.APPDATA = join(homeDir, "AppData", "Roaming"); process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); @@ -69,15 +67,14 @@ describe("managed skills storage", () => { } else { process.env.XDG_CONFIG_HOME = originalXdgConfigHome; } - if (userDataDir) await rm(userDataDir, { recursive: true, force: true }); if (homeDir) await rm(homeDir, { recursive: true, force: true }); - userDataDir = ""; homeDir = ""; }); - test("merges system skills with user overrides", async () => { + test("reads managed skills from the shared storage directory", async () => { const { config, skills } = await loadModules(); - await writeSkill(config.getSystemSkillsDir(), "review", "review", "System review", "system body"); + expect(config.getSystemSkillsDir()).toBe(config.getSkillsDir()); + await writeSkill(config.getSkillsDir(), "review", "review", "User review", "user body"); await writeSkill(config.getSkillsDir(), "local", "local", "Local only", "local body"); @@ -90,15 +87,14 @@ describe("managed skills storage", () => { expect(skills.readManagedSkillContentSync("review")).toContain("user body"); }); - test("stores user deletion markers for system skills", async () => { + test("deletes managed skills directly from the shared storage directory", async () => { const { config, skills } = await loadModules(); - await writeSkill(config.getSystemSkillsDir(), "review", "review", "System review", "system body"); + await writeSkill(config.getSkillsDir(), "review", "review", "System review", "system body"); await skills.deleteManagedSkill("review"); expect((await skills.listManagedSkills()).some((skill) => skill.slug === "review")).toBe(false); - await expect(stat(join(config.getSystemSkillsDir(), "review", "SKILL.md"))).resolves.toBeTruthy(); - expect(await readFile(join(config.getSkillsDir(), "review", ".deleted"), "utf-8")).toBe(""); + await expect(stat(join(config.getSkillsDir(), "review", "SKILL.md"))).rejects.toBeTruthy(); await skills.saveManagedSkill({ name: "review", @@ -107,6 +103,5 @@ describe("managed skills storage", () => { }); expect(skills.readManagedSkillContentSync("review")).toContain("restored body"); - await expect(stat(join(config.getSkillsDir(), "review", ".deleted"))).rejects.toBeTruthy(); }); }); diff --git a/apps/desktop/renderer/src/lib/workflow-storage.test.ts b/apps/desktop/renderer/src/lib/workflow-storage.test.ts index bf833d9..b19a525 100644 --- a/apps/desktop/renderer/src/lib/workflow-storage.test.ts +++ b/apps/desktop/renderer/src/lib/workflow-storage.test.ts @@ -3,7 +3,6 @@ import { tmpdir } from "os"; import { join } from "path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -let userDataDir = ""; let homeDir = ""; let originalUserDataDir; let originalHome; @@ -49,9 +48,8 @@ describe("workflow storage", () => { originalHome = process.env.HOME; originalAppData = process.env.APPDATA; originalXdgConfigHome = process.env.XDG_CONFIG_HOME; - userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-")); homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir; + delete process.env.DEV_WORKFLOW_USER_DATA_DIR; process.env.HOME = homeDir; process.env.APPDATA = join(homeDir, "AppData", "Roaming"); process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); @@ -78,20 +76,18 @@ describe("workflow storage", () => { } else { process.env.XDG_CONFIG_HOME = originalXdgConfigHome; } - if (userDataDir) await rm(userDataDir, { recursive: true, force: true }); if (homeDir) await rm(homeDir, { recursive: true, force: true }); - userDataDir = ""; homeDir = ""; }); - test("uses user workflows before system workflows", async () => { + test("uses the shared workflow directory", async () => { const { config, workflow } = await loadModules(); - await writeWorkflow(config.getSystemWorkflowDir(), "default.json", "System Default"); + + expect(config.getSystemWorkflowDir()).toBe(config.getWorkflowDir()); + await writeWorkflow(config.getWorkflowDir(), "default.json", "User Default"); - await writeWorkflow(config.getSystemWorkflowDir(), "system-only.json", "System Only"); expect(workflow.getWorkflowFilePath("default.json")).toBe(join(config.getWorkflowDir(), "default.json")); - expect(workflow.getWorkflowFilePath("system-only.json")).toBe(join(config.getSystemWorkflowDir(), "system-only.json")); expect(workflow.readWorkflowFileSync("default.json").name).toBe("User Default"); }); }); diff --git a/apps/desktop/renderer/src/lib/workfolders-storage.test.ts b/apps/desktop/renderer/src/lib/workfolders-storage.test.ts index c0cedde..e3e5582 100644 --- a/apps/desktop/renderer/src/lib/workfolders-storage.test.ts +++ b/apps/desktop/renderer/src/lib/workfolders-storage.test.ts @@ -1,9 +1,8 @@ -import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises"; +import { mkdir, mkdtemp, rm, writeFile } from "fs/promises"; import { tmpdir } from "os"; import { dirname, join } from "path"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -let userDataDir = ""; let homeDir = ""; let originalUserDataDir; let originalHome; @@ -28,9 +27,8 @@ describe("workfolders storage", () => { originalHome = process.env.HOME; originalAppData = process.env.APPDATA; originalXdgConfigHome = process.env.XDG_CONFIG_HOME; - userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-")); homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-")); - process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir; + delete process.env.DEV_WORKFLOW_USER_DATA_DIR; process.env.HOME = homeDir; process.env.APPDATA = join(homeDir, "AppData", "Roaming"); process.env.XDG_CONFIG_HOME = join(homeDir, ".config"); @@ -57,44 +55,36 @@ describe("workfolders storage", () => { } else { process.env.XDG_CONFIG_HOME = originalXdgConfigHome; } - if (userDataDir) await rm(userDataDir, { recursive: true, force: true }); if (homeDir) await rm(homeDir, { recursive: true, force: true }); - userDataDir = ""; homeDir = ""; }); - test("merges system workfolders with user folders", async () => { + test("reads workfolders from the shared storage directory", async () => { const { config, workfolders } = await loadModules(); const userBaseDir = await config.getBaseDir(); const systemBaseDir = config.getSystemBaseDir(); - await writeWorkfolders(config.getWorkfoldersFile(systemBaseDir), [ - { name: "System", path: "/tmp/system", tasks: [{ taskId: "system-task", status: "running" }] }, - { name: "Shared", path: "/tmp/shared", tasks: [] }, - ]); + + expect(systemBaseDir).toBe(userBaseDir); + await writeWorkfolders(config.getWorkfoldersFile(userBaseDir), [ { name: "Shared User", path: "/tmp/shared", tasks: [{ taskId: "user-task", status: "done" }] }, { name: "User", path: "/tmp/user", tasks: [] }, ]); expect(await workfolders.readWorkfolders()).toEqual([ - { name: "System", path: "/tmp/system", tasks: [] }, { name: "Shared User", path: "/tmp/shared", tasks: [{ taskId: "user-task", runId: "user-task", status: "done" }] }, { name: "User", path: "/tmp/user", tasks: [] }, ]); }); - test("stores user deletion markers for system workfolders", async () => { + test("removes workfolders directly from the shared storage file", async () => { const { config, workfolders } = await loadModules(); const userBaseDir = await config.getBaseDir(); - const systemBaseDir = config.getSystemBaseDir(); - await writeWorkfolders(config.getWorkfoldersFile(systemBaseDir), [ + await writeWorkfolders(config.getWorkfoldersFile(userBaseDir), [ { name: "System", path: "/tmp/system", tasks: [] }, ]); expect(await workfolders.removeWorkfolder("/tmp/system")).toEqual([]); - expect(JSON.parse(await readFile(config.getWorkfoldersFile(userBaseDir), "utf-8"))).toEqual([ - { name: "System", path: "/tmp/system", tasks: [], deleted: true }, - ]); await workfolders.saveWorkfolders([ { name: "User", path: "/tmp/user", tasks: [] }, @@ -103,9 +93,5 @@ describe("workfolders storage", () => { expect(await workfolders.readWorkfolders()).toEqual([ { name: "User", path: "/tmp/user", tasks: [] }, ]); - expect(JSON.parse(await readFile(config.getWorkfoldersFile(userBaseDir), "utf-8"))).toEqual([ - { name: "System", path: "/tmp/system", tasks: [], deleted: true }, - { name: "User", path: "/tmp/user", tasks: [] }, - ]); }); }); diff --git a/packages/core-models/config.ts b/packages/core-models/config.ts index 13c0567..b87a22c 100644 --- a/packages/core-models/config.ts +++ b/packages/core-models/config.ts @@ -1,12 +1,8 @@ -import { basename, dirname, join, resolve } from "path"; +import { dirname, join } from "path"; import { readFile, writeFile, mkdir } from "fs/promises"; -import { existsSync, readFileSync, statSync } from "fs"; -import { fileURLToPath } from "url"; -import { createHash } from "crypto"; +import { readFileSync } from "fs"; import os from "os"; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const PROJECT_ROOT = join(__dirname, "..", ".."); let runtimeBaseDir = ""; let runtimeStorageDir = ""; @@ -28,56 +24,12 @@ export function getSharedDesktopUserDataDir() { return getPlatformUserDataDir(); } -function isDevelopmentCheckout() { - return ( - existsSync(join(PROJECT_ROOT, "package.json")) && - existsSync(join(PROJECT_ROOT, "apps", "desktop", "electron", "main.ts")) - ); -} - -function getWorktreeScopedUserDataDir(baseDir, projectRoot = PROJECT_ROOT) { - const hash = createHash("sha256").update(projectRoot).digest("hex").slice(0, 8); - return join(baseDir, "worktrees", `${basename(projectRoot)}-${hash}`); -} - -function getCommonGitDir() { - const gitPath = join(PROJECT_ROOT, ".git"); - try { - if (statSync(gitPath).isDirectory()) return gitPath; - } catch { - return ""; - } - - const match = readFileSync(gitPath, "utf-8").match(/^gitdir:\s*(.+)\s*$/m); - if (!match) return ""; - const gitDir = resolve(PROJECT_ROOT, match[1]); - - try { - const rawCommonDir = readFileSync(join(gitDir, "commondir"), "utf-8").trim(); - if (rawCommonDir) return resolve(gitDir, rawCommonDir); - } catch {} - - return resolve(gitDir, "..", ".."); -} - -function getSystemDevelopmentCheckoutDir(baseDir) { - const commonGitDir = getCommonGitDir(); - const mainProjectRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : PROJECT_ROOT; - return getWorktreeScopedUserDataDir(baseDir, mainProjectRoot); -} - export function getDefaultDesktopUserDataDir() { - if (process.env.DEV_WORKFLOW_USER_DATA_DIR) { - return process.env.DEV_WORKFLOW_USER_DATA_DIR; - } - - const baseDir = getPlatformUserDataDir(); - return isDevelopmentCheckout() ? getWorktreeScopedUserDataDir(baseDir) : baseDir; + return getPlatformUserDataDir(); } export function getSystemDesktopUserDataDir() { - const baseDir = getSharedDesktopUserDataDir(); - return isDevelopmentCheckout() ? getSystemDevelopmentCheckoutDir(baseDir) : baseDir; + return getDefaultDesktopUserDataDir(); } export const SYSTEM_CONFIG_FILE = join(getSystemDesktopUserDataDir(), "config.json"); @@ -107,78 +59,6 @@ async function readJsonFile(path) { } } -function hasOwn(object, key) { - return Object.prototype.hasOwnProperty.call(object, key); -} - -function mergeAiApiProfiles(systemProfiles = [], userProfiles = [], deletedProfileIds = []) { - const deleted = new Set((Array.isArray(deletedProfileIds) ? deletedProfileIds : []).map((id) => slugifyConfigId(id, ""))); - const merged = normalizeAiApiProfiles(systemProfiles).filter((profile) => !deleted.has(profile.id)); - const byId = new Map(merged.map((profile, index) => [profile.id, index])); - - for (const profile of normalizeAiApiProfiles(userProfiles)) { - const index = byId.get(profile.id); - if (index === undefined) { - byId.set(profile.id, merged.length); - merged.push(profile); - } else { - merged[index] = profile; - } - } - - return merged; -} - -function mergeConfigLayers(systemConfig = {}, userConfig = {}) { - const merged = { - ...systemConfig, - ...userConfig, - }; - merged.aiApiProfiles = mergeAiApiProfiles( - systemConfig.aiApiProfiles, - userConfig.aiApiProfiles, - userConfig.deletedAiApiProfileIds - ); - return merged; -} - -function sameAiApiProfile(a, b) { - return ( - a.id === b.id && - a.name === b.name && - a.baseUrl === b.baseUrl && - a.apiKey === b.apiKey && - a.model === b.model - ); -} - -function buildUserConfig(config, systemConfig) { - const userConfig = {}; - const systemProfiles = normalizeAiApiProfiles(systemConfig.aiApiProfiles); - const nextProfiles = normalizeAiApiProfiles(config.aiApiProfiles); - const nextProfileIds = new Set(nextProfiles.map((profile) => profile.id)); - const systemProfileById = new Map(systemProfiles.map((profile) => [profile.id, profile])); - const userProfiles = nextProfiles.filter((profile) => { - const systemProfile = systemProfileById.get(profile.id); - return !systemProfile || !sameAiApiProfile(profile, systemProfile); - }); - const deletedAiApiProfileIds = systemProfiles - .filter((profile) => !nextProfileIds.has(profile.id)) - .map((profile) => profile.id); - - for (const key of ["activeWorkflow", "mobileAccessEnabled", "aiBackendOverride"]) { - if (hasOwn(config, key) && config[key] !== systemConfig[key]) { - userConfig[key] = config[key]; - } - } - if (Array.isArray(config.deletedWorkflowFiles) && config.deletedWorkflowFiles.length > 0) { - userConfig.deletedWorkflowFiles = config.deletedWorkflowFiles; - } - if (userProfiles.length > 0) userConfig.aiApiProfiles = userProfiles; - if (deletedAiApiProfileIds.length > 0) userConfig.deletedAiApiProfileIds = deletedAiApiProfileIds; - return userConfig; -} - async function ensureConfigDir() { await mkdir(dirname(CONFIG_FILE), { recursive: true }); } @@ -200,18 +80,16 @@ export async function readUserConfig() { } export function readConfigSync() { - return mergeConfigLayers(readSystemConfigSync(), readUserConfigSync()); + return readUserConfigSync(); } export async function readConfig() { - return mergeConfigLayers(await readSystemConfig(), await readUserConfig()); + return readUserConfig(); } export async function saveConfig(config) { - const systemConfig = await readSystemConfig(); - const userConfig = buildUserConfig(config || {}, systemConfig); await ensureConfigDir(); - await writeFile(CONFIG_FILE, JSON.stringify(userConfig, null, 2)); + await writeFile(CONFIG_FILE, JSON.stringify(config || {}, null, 2)); } export async function readMobileAccessEnabled() { diff --git a/scripts/dev-desktop-instance.ts b/scripts/dev-desktop-instance.ts index e9bc177..e95b031 100644 --- a/scripts/dev-desktop-instance.ts +++ b/scripts/dev-desktop-instance.ts @@ -1,7 +1,6 @@ import { spawn } from "child_process"; import net from "net"; -import { basename, join } from "path"; -import { mkdirSync } from "fs"; +import { basename } from "path"; type Options = { name: string; @@ -121,12 +120,6 @@ async function main() { } const { rendererPort, localServerPort, backendPort } = getPorts(portOffset); const name = options.name; - const userDataDir = options.customName - ? join(process.cwd(), ".dev-instances", name) - : process.env.DEV_WORKFLOW_USER_DATA_DIR || ""; - - if (userDataDir) mkdirSync(userDataDir, { recursive: true }); - const env = { ...process.env, RENDERER_PORT: String(rendererPort), @@ -141,14 +134,13 @@ async function main() { DEVICE_NAME: `Desktop ${name}`, DEV_WORKFLOW_INSTANCE_NAME: name, }; - if (userDataDir) env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir; console.log(`[dev-instance] name: ${name}`); console.log(`[dev-instance] port offset: ${portOffset}${options.autoPortOffset ? " (auto)" : ""}`); console.log(`[dev-instance] renderer: http://127.0.0.1:${rendererPort}`); console.log(`[dev-instance] local server: http://127.0.0.1:${localServerPort}`); console.log(`[dev-instance] backend: http://127.0.0.1:${backendPort}`); - console.log(`[dev-instance] user data: ${userDataDir || "default worktree-scoped directory"}`); + console.log("[dev-instance] user data: shared app directory"); const child = spawn("pnpm", ["run", "dev:all:raw"], { env,