diff --git a/apps/desktop/.env.example b/apps/desktop/.env.example index b2d236e4..0ee71ca1 100644 --- a/apps/desktop/.env.example +++ b/apps/desktop/.env.example @@ -97,3 +97,10 @@ RENDERER_VITE_API_URL=http://localhost:8080 # Read by core package via process.env.MULTICA_API_URL at runtime # Production example: https://api.multica.ai MULTICA_API_URL=http://localhost:8080 + +# SMC_DATA_DIR +# Root data directory override - Isolates dev data from production. +# When set, all data (sessions, credentials, profiles, app-state, etc.) +# is stored under this directory instead of ~/.super-multica. +# Supports ~ expansion. Set automatically by scripts/dev-local.sh. +# SMC_DATA_DIR=~/.super-multica-dev diff --git a/package.json b/package.json index e6fb4696..ab4b9e66 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "mu": "pnpm --filter @multica/cli dev", "dev": "turbo build --filter=@multica/types --filter=@multica/utils --filter=@multica/core && concurrently -n types,utils,core,desktop -c blue,green,yellow,cyan \"pnpm --filter @multica/types dev\" \"pnpm --filter @multica/utils dev\" \"pnpm --filter @multica/core dev\" \"pnpm --filter @multica/desktop dev\"", "dev:desktop": "pnpm --filter @multica/desktop dev", - "dev:desktop:reset": "rm -rf ~/.super-multica && echo '✓ Deleted ~/.super-multica - Fresh install state restored'", + "dev:desktop:reset": "rm -rf ~/.super-multica && rm -rf ~/.super-multica-dev && echo '✓ Deleted ~/.super-multica and ~/.super-multica-dev - Fresh install state restored'", "dev:desktop:fresh": "pnpm dev:desktop:reset && pnpm dev:desktop", "dev:desktop:onboarding": "pnpm --filter @multica/desktop dev:onboarding", "dev:gateway": "pnpm --filter @multica/gateway dev", "dev:web": "pnpm --filter @multica/web dev", "dev:local": "bash scripts/dev-local.sh", + "dev:local:archive": "bash scripts/archive-dev-data.sh", "dev:all": "concurrently \"pnpm dev:gateway\" \"pnpm dev:web\"", "build": "turbo build", "build:desktop": "pnpm --filter @multica/desktop build", diff --git a/packages/core/src/cron/store.ts b/packages/core/src/cron/store.ts index c7896a8a..5ad9e784 100644 --- a/packages/core/src/cron/store.ts +++ b/packages/core/src/cron/store.ts @@ -7,14 +7,11 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, renameSync } from "fs"; import path from "path"; +import { DATA_DIR } from "@multica/utils"; import type { CronJob, CronRunLogEntry } from "./types.js"; /** Default cron storage directory */ -const DEFAULT_CRON_DIR = path.join( - process.env["HOME"] ?? ".", - ".super-multica", - "cron", -); +const DEFAULT_CRON_DIR = path.join(DATA_DIR, "cron"); /** Store data structure */ type StoreData = { diff --git a/packages/utils/src/paths.test.ts b/packages/utils/src/paths.test.ts new file mode 100644 index 00000000..585df153 --- /dev/null +++ b/packages/utils/src/paths.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { join } from "node:path"; +import { homedir } from "node:os"; + +import { resolveDataDir } from "./paths.js"; + +describe("resolveDataDir", () => { + const original = process.env.SMC_DATA_DIR; + + afterEach(() => { + if (original === undefined) { + delete process.env.SMC_DATA_DIR; + } else { + process.env.SMC_DATA_DIR = original; + } + }); + + it("defaults to ~/.super-multica when SMC_DATA_DIR is not set", () => { + delete process.env.SMC_DATA_DIR; + expect(resolveDataDir()).toBe(join(homedir(), ".super-multica")); + }); + + it("uses absolute path from SMC_DATA_DIR", () => { + process.env.SMC_DATA_DIR = "/tmp/test-multica"; + expect(resolveDataDir()).toBe("/tmp/test-multica"); + }); + + it("expands ~ in SMC_DATA_DIR", () => { + process.env.SMC_DATA_DIR = "~/.super-multica-dev"; + expect(resolveDataDir()).toBe(join(homedir(), ".super-multica-dev")); + }); + + it("handles ~ alone", () => { + process.env.SMC_DATA_DIR = "~"; + expect(resolveDataDir()).toBe(homedir()); + }); +}); diff --git a/packages/utils/src/paths.ts b/packages/utils/src/paths.ts index 69011de0..eecefe14 100644 --- a/packages/utils/src/paths.ts +++ b/packages/utils/src/paths.ts @@ -1,8 +1,23 @@ import { join } from "node:path"; import { homedir } from "node:os"; -/** Root data directory: ~/.super-multica */ -export const DATA_DIR = join(homedir(), ".super-multica"); +/** + * Resolve the root data directory. + * Override with SMC_DATA_DIR env var (supports ~ expansion). + * Defaults to ~/.super-multica. + */ +export function resolveDataDir(): string { + const envDir = process.env.SMC_DATA_DIR; + if (envDir) { + return envDir.startsWith("~") + ? join(homedir(), envDir.slice(1)) + : envDir; + } + return join(homedir(), ".super-multica"); +} + +/** Root data directory (default: ~/.super-multica, override: SMC_DATA_DIR) */ +export const DATA_DIR = resolveDataDir(); /** Cache directory for downloaded media files */ export const MEDIA_CACHE_DIR = join(DATA_DIR, "cache", "media"); diff --git a/scripts/archive-dev-data.sh b/scripts/archive-dev-data.sh new file mode 100755 index 00000000..ae85360f --- /dev/null +++ b/scripts/archive-dev-data.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Archive and clean the dev environment data. +# +# Moves ~/.super-multica-dev and ~/Documents/Multica-dev into a +# timestamped archive directory for later debugging / analysis. +# +# Usage: +# pnpm dev:local:archive +# +# Archives are stored in: ~/.super-multica-dev-archives// + +set -euo pipefail + +TIMESTAMP=$(date +"%Y%m%d-%H%M%S") +ARCHIVE_BASE="$HOME/.super-multica-dev-archives" +ARCHIVE_DIR="$ARCHIVE_BASE/$TIMESTAMP" + +DEV_DATA="$HOME/.super-multica-dev" +DEV_WORKSPACE="$HOME/Documents/Multica-dev" + +# Check if there's anything to archive +if [ ! -d "$DEV_DATA" ] && [ ! -d "$DEV_WORKSPACE" ]; then + echo "Nothing to archive — neither $DEV_DATA nor $DEV_WORKSPACE exists." + exit 0 +fi + +mkdir -p "$ARCHIVE_DIR" + +if [ -d "$DEV_DATA" ]; then + mv "$DEV_DATA" "$ARCHIVE_DIR/data" + echo " Archived $DEV_DATA -> $ARCHIVE_DIR/data" +fi + +if [ -d "$DEV_WORKSPACE" ]; then + mv "$DEV_WORKSPACE" "$ARCHIVE_DIR/workspace" + echo " Archived $DEV_WORKSPACE -> $ARCHIVE_DIR/workspace" +fi + +echo "" +echo "Archived to: $ARCHIVE_DIR" +echo "Dev environment is now clean. Run 'pnpm dev:local' to start fresh." diff --git a/scripts/dev-local.sh b/scripts/dev-local.sh index 27dd9fff..5c45b06a 100755 --- a/scripts/dev-local.sh +++ b/scripts/dev-local.sh @@ -36,6 +36,8 @@ echo "Starting local dev environment..." echo " Gateway: http://localhost:4000 (Telegram long-polling mode)" echo " Web: http://localhost:3000 (OAuth login)" echo " Desktop: connecting to local Gateway + Web" +echo " Data dir: ~/.super-multica-dev (isolated from production)" +echo " Workspace: ~/Documents/Multica-dev (isolated from production)" echo "" # Build shared packages first @@ -49,6 +51,6 @@ exec pnpm concurrently \ "pnpm --filter @multica/types dev" \ "pnpm --filter @multica/utils dev" \ "pnpm --filter @multica/core dev" \ - "PORT=4000 MULTICA_RUN_LOG=1 pnpm --filter @multica/gateway dev" \ + "PORT=4000 SMC_DATA_DIR=~/.super-multica-dev MULTICA_WORKSPACE_DIR=~/Documents/Multica-dev MULTICA_RUN_LOG=1 pnpm --filter @multica/gateway dev" \ "pnpm --filter @multica/web dev" \ - "GATEWAY_URL=http://localhost:4000 MAIN_VITE_WEB_URL=http://localhost:3000 MULTICA_RUN_LOG=1 pnpm --filter @multica/desktop dev" + "GATEWAY_URL=http://localhost:4000 MAIN_VITE_WEB_URL=http://localhost:3000 SMC_DATA_DIR=~/.super-multica-dev MULTICA_WORKSPACE_DIR=~/Documents/Multica-dev MULTICA_RUN_LOG=1 pnpm --filter @multica/desktop dev" diff --git a/scripts/reset-user-data.sh b/scripts/reset-user-data.sh index b56f929a..8981aa2f 100755 --- a/scripts/reset-user-data.sh +++ b/scripts/reset-user-data.sh @@ -15,6 +15,15 @@ else echo " $MULTICA_DATA_DIR does not exist, skipping" fi +# Dev data directory (used by pnpm dev:local) +MULTICA_DEV_DIR="$HOME/.super-multica-dev" +if [ -d "$MULTICA_DEV_DIR" ]; then + echo " Removing $MULTICA_DEV_DIR" + rm -rf "$MULTICA_DEV_DIR" +else + echo " $MULTICA_DEV_DIR does not exist, skipping" +fi + # Electron app data (macOS) if [[ "$OSTYPE" == "darwin"* ]]; then ELECTRON_APP_DATA="$HOME/Library/Application Support/super-multica"