/ src / lib / server / data-dir.ts
data-dir.ts
 1  import path from 'path'
 2  import os from 'os'
 3  import fs from 'fs'
 4  
 5  function isBuildBootstrapEnv(env: NodeJS.ProcessEnv = process.env, argv: string[] = process.argv): boolean {
 6    if (env.SWARMCLAW_BUILD_MODE === '1') return true
 7    if (env.NEXT_PHASE === 'phase-production-build') return true
 8    const lifecycle = env.npm_lifecycle_event?.trim().toLowerCase()
 9    if (lifecycle === 'build' || lifecycle === 'build:ci' || lifecycle?.startsWith('build:')) return true
10    return argv.some((arg) => /\bnext(?:[\\/](?:dist[\\/]bin[\\/])?next)?\b/.test(arg))
11      && argv.some((arg) => /\bbuild\b/.test(arg))
12  }
13  
14  export const IS_BUILD_BOOTSTRAP = isBuildBootstrapEnv()
15  
16  function resolveSwarmclawHome(): string | null {
17    const configured = process.env.SWARMCLAW_HOME?.trim()
18    return configured ? path.resolve(configured) : null
19  }
20  
21  function resolveDataDir(): string {
22    if (process.env.DATA_DIR) return process.env.DATA_DIR
23    if (IS_BUILD_BOOTSTRAP) return path.join(os.tmpdir(), 'swarmclaw-build-data')
24    const appHome = resolveSwarmclawHome()
25    if (appHome) return path.join(appHome, 'data')
26    return path.join(process.cwd(), 'data')
27  }
28  
29  export const DATA_DIR = resolveDataDir()
30  export const CONNECTORS_DATA_DIR = path.join(DATA_DIR, 'connectors')
31  export const OPENCLAW_DATA_DIR = path.join(DATA_DIR, 'openclaw')
32  export const MEMORY_IMAGES_DIR = path.join(DATA_DIR, 'memory-images')
33  export const APP_LOG_PATH = path.join(DATA_DIR, 'app.log')
34  
35  function supportsChildWrites(dir: string): boolean {
36    try {
37      fs.mkdirSync(dir, { recursive: true })
38      const probeDir = fs.mkdtempSync(path.join(dir, '.swarmclaw-probe-'))
39      fs.rmSync(probeDir, { recursive: true, force: true })
40      return true
41    } catch {
42      return false
43    }
44  }
45  
46  // Workspace lives outside the project directory to avoid triggering Next.js HMR
47  // when agents create/modify files. Falls back to data/workspace for Docker/CI.
48  function resolveWorkspaceDir(): string {
49    if (process.env.WORKSPACE_DIR) return process.env.WORKSPACE_DIR
50    if (IS_BUILD_BOOTSTRAP) return path.join(DATA_DIR, 'workspace')
51    const appHome = resolveSwarmclawHome()
52    if (appHome) return path.join(appHome, 'workspace')
53    const external = path.join(os.homedir(), '.swarmclaw', 'workspace')
54    if (supportsChildWrites(external)) {
55      return external
56    }
57    return path.join(DATA_DIR, 'workspace')
58  }
59  
60  export const WORKSPACE_DIR = resolveWorkspaceDir()
61  
62  function resolveBrowserProfilesDir(): string {
63    if (process.env.BROWSER_PROFILES_DIR) return process.env.BROWSER_PROFILES_DIR
64    if (IS_BUILD_BOOTSTRAP) return path.join(DATA_DIR, 'browser-profiles')
65    const appHome = resolveSwarmclawHome()
66    if (appHome) return path.join(appHome, 'browser-profiles')
67    const external = path.join(os.homedir(), '.swarmclaw', 'browser-profiles')
68    if (supportsChildWrites(external)) {
69      return external
70    }
71    return path.join(DATA_DIR, 'browser-profiles')
72  }
73  
74  export const BROWSER_PROFILES_DIR = resolveBrowserProfilesDir()