@muhammedaksam/easiarr 1.3.5 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +8 -5
  2. package/dist/index.js +29837 -0
  3. package/package.json +18 -16
  4. package/src/VersionInfo.ts +0 -12
  5. package/src/api/arr-api.ts +0 -508
  6. package/src/api/auto-setup-types.ts +0 -64
  7. package/src/api/bazarr-api.ts +0 -356
  8. package/src/api/cloudflare-api.ts +0 -577
  9. package/src/api/custom-format-api.ts +0 -212
  10. package/src/api/grafana-api.ts +0 -314
  11. package/src/api/heimdall-api.ts +0 -209
  12. package/src/api/homarr-api.ts +0 -296
  13. package/src/api/huntarr-api.ts +0 -622
  14. package/src/api/index.ts +0 -1
  15. package/src/api/jellyfin-api.ts +0 -473
  16. package/src/api/jellyseerr-api.ts +0 -660
  17. package/src/api/maintainerr-api.ts +0 -731
  18. package/src/api/naming-config.ts +0 -93
  19. package/src/api/overseerr-api.ts +0 -513
  20. package/src/api/plex-api.ts +0 -358
  21. package/src/api/portainer-api.ts +0 -467
  22. package/src/api/profilarr-api.ts +0 -380
  23. package/src/api/prowlarr-api.ts +0 -859
  24. package/src/api/qbittorrent-api.ts +0 -339
  25. package/src/api/quality-profile-api.ts +0 -269
  26. package/src/api/tautulli-api.ts +0 -277
  27. package/src/api/uptime-kuma-api.ts +0 -342
  28. package/src/apps/categories.ts +0 -14
  29. package/src/apps/index.ts +0 -2
  30. package/src/apps/registry.ts +0 -1255
  31. package/src/compose/caddy-config.ts +0 -129
  32. package/src/compose/generator.ts +0 -262
  33. package/src/compose/index.ts +0 -3
  34. package/src/compose/templates.ts +0 -78
  35. package/src/compose/traefik-config.ts +0 -185
  36. package/src/config/bookmarks-generator.ts +0 -149
  37. package/src/config/defaults.ts +0 -37
  38. package/src/config/homepage-config.ts +0 -376
  39. package/src/config/index.ts +0 -3
  40. package/src/config/manager.ts +0 -149
  41. package/src/config/recyclarr-config.ts +0 -179
  42. package/src/config/schema.ts +0 -310
  43. package/src/config/slskd-config.ts +0 -85
  44. package/src/config/soularr-config.ts +0 -105
  45. package/src/config/trash-quality-definitions.ts +0 -213
  46. package/src/data/lidarr-custom-formats.ts +0 -127
  47. package/src/data/trash-profiles.ts +0 -326
  48. package/src/docker/client.ts +0 -291
  49. package/src/docker/index.ts +0 -1
  50. package/src/index.ts +0 -77
  51. package/src/structure/manager.ts +0 -128
  52. package/src/ui/App.ts +0 -109
  53. package/src/ui/components/ApplicationSelector.ts +0 -272
  54. package/src/ui/components/FileEditor.ts +0 -91
  55. package/src/ui/components/FooterHint.ts +0 -119
  56. package/src/ui/components/PageLayout.ts +0 -152
  57. package/src/ui/components/UpdateNotification.ts +0 -101
  58. package/src/ui/index.ts +0 -1
  59. package/src/ui/screens/AdvancedSettings.ts +0 -181
  60. package/src/ui/screens/ApiKeyViewer.ts +0 -500
  61. package/src/ui/screens/AppConfigurator.ts +0 -731
  62. package/src/ui/screens/AppManager.ts +0 -276
  63. package/src/ui/screens/CloudflaredSetup.ts +0 -974
  64. package/src/ui/screens/ContainerControl.ts +0 -579
  65. package/src/ui/screens/FullAutoSetup.ts +0 -1719
  66. package/src/ui/screens/HomepageSetup.ts +0 -306
  67. package/src/ui/screens/JellyfinSetup.ts +0 -471
  68. package/src/ui/screens/JellyseerrSetup.ts +0 -612
  69. package/src/ui/screens/LogsViewer.ts +0 -468
  70. package/src/ui/screens/MainMenu.ts +0 -288
  71. package/src/ui/screens/MonitorDashboard.ts +0 -866
  72. package/src/ui/screens/ProwlarrSetup.ts +0 -623
  73. package/src/ui/screens/QBittorrentSetup.ts +0 -268
  74. package/src/ui/screens/QuickSetup.ts +0 -1161
  75. package/src/ui/screens/RecyclarrSetup.ts +0 -418
  76. package/src/ui/screens/SecretsEditor.ts +0 -214
  77. package/src/ui/screens/SettingsScreen.ts +0 -605
  78. package/src/ui/screens/TRaSHProfileSetup.ts +0 -395
  79. package/src/ui/screens/index.ts +0 -4
  80. package/src/util/arch.ts +0 -78
  81. package/src/utils/browser.ts +0 -26
  82. package/src/utils/categories.ts +0 -59
  83. package/src/utils/debug.ts +0 -91
  84. package/src/utils/env.ts +0 -137
  85. package/src/utils/logs.ts +0 -118
  86. package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -101
  87. package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -90
  88. package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -44
  89. package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -107
  90. package/src/utils/migrations.ts +0 -164
  91. package/src/utils/password.ts +0 -42
  92. package/src/utils/unraid.ts +0 -101
  93. package/src/utils/update-checker.ts +0 -210
  94. package/src/utils/url-utils.ts +0 -38
package/src/utils/env.ts DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * Environment File Utilities
3
- * Shared functions for reading/writing .env files
4
- */
5
-
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
7
- import { writeFile, readFile } from "node:fs/promises"
8
- import { networkInterfaces } from "node:os"
9
- import { getComposePath } from "../config/manager"
10
-
11
- /**
12
- * Get the local IP address of the Docker host
13
- * Returns the first non-internal IPv4 address found
14
- */
15
- export function getLocalIp(): string {
16
- const nets = networkInterfaces()
17
-
18
- for (const name of Object.keys(nets)) {
19
- const interfaces = nets[name]
20
- if (!interfaces) continue
21
-
22
- for (const net of interfaces) {
23
- // Skip internal (loopback) and non-IPv4 addresses
24
- if (net.family === "IPv4" && !net.internal) {
25
- return net.address
26
- }
27
- }
28
- }
29
-
30
- return "localhost"
31
- }
32
-
33
- /**
34
- * Get the path to the .env file
35
- */
36
- export function getEnvPath(): string {
37
- return getComposePath().replace("docker-compose.yml", ".env")
38
- }
39
-
40
- /**
41
- * Parse an .env file into a key-value object
42
- * Preserves existing values and handles multi-part values (e.g., with = in them)
43
- */
44
- export function parseEnvFile(content: string): Record<string, string> {
45
- const env: Record<string, string> = {}
46
-
47
- for (const line of content.split("\n")) {
48
- const trimmed = line.trim()
49
- if (!trimmed || trimmed.startsWith("#")) continue
50
-
51
- const [key, ...val] = trimmed.split("=")
52
- if (key && val.length > 0) {
53
- env[key.trim()] = val.join("=").trim()
54
- }
55
- }
56
-
57
- return env
58
- }
59
-
60
- /**
61
- * Serialize an env object to .env file format
62
- */
63
- export function serializeEnv(env: Record<string, string>): string {
64
- return Object.entries(env)
65
- .map(([k, v]) => `${k}=${v}`)
66
- .join("\n")
67
- }
68
-
69
- /**
70
- * Read the .env file and return parsed key-value object
71
- * Returns empty object if file doesn't exist
72
- */
73
- export function readEnvSync(): Record<string, string> {
74
- const envPath = getEnvPath()
75
- if (!existsSync(envPath)) return {}
76
-
77
- try {
78
- const content = readFileSync(envPath, "utf-8")
79
- return parseEnvFile(content)
80
- } catch {
81
- return {}
82
- }
83
- }
84
-
85
- /**
86
- * Write to .env file synchronously, merging with existing values
87
- * Preserves existing keys not in the updates object
88
- */
89
- export function writeEnvSync(updates: Record<string, string>): void {
90
- const envPath = getEnvPath()
91
- const current = readEnvSync()
92
-
93
- // Merge updates into current
94
- const merged = { ...current, ...updates }
95
-
96
- // Write back
97
- const content = serializeEnv(merged)
98
- writeFileSync(envPath, content, "utf-8")
99
- }
100
-
101
- /**
102
- * Read the .env file asynchronously
103
- */
104
- export async function readEnv(): Promise<Record<string, string>> {
105
- const envPath = getEnvPath()
106
- if (!existsSync(envPath)) return {}
107
-
108
- try {
109
- const content = await readFile(envPath, "utf-8")
110
- return parseEnvFile(content)
111
- } catch {
112
- return {}
113
- }
114
- }
115
-
116
- /**
117
- * Write to .env file, merging with existing values
118
- * Preserves existing keys not in the updates object
119
- */
120
- export async function updateEnv(updates: Record<string, string>): Promise<void> {
121
- const envPath = getEnvPath()
122
- const current = await readEnv()
123
-
124
- // Merge updates into current
125
- const merged = { ...current, ...updates }
126
-
127
- // Write back
128
- const content = serializeEnv(merged)
129
- await writeFile(envPath, content, "utf-8")
130
- }
131
-
132
- /**
133
- * Get a specific value from .env
134
- */
135
- export function getEnvValue(key: string): string | undefined {
136
- return readEnvSync()[key]
137
- }
package/src/utils/logs.ts DELETED
@@ -1,118 +0,0 @@
1
- /**
2
- * Log File Management Utilities
3
- * Handles saving and managing container logs to ~/.easiarr/logs/
4
- */
5
-
6
- import { mkdir, writeFile, readdir, stat } from "node:fs/promises"
7
- import { join } from "node:path"
8
- import { homedir } from "node:os"
9
- import { debugLog } from "./debug"
10
-
11
- const LOGS_DIR = join(homedir(), ".easiarr", "logs")
12
-
13
- /**
14
- * Get the logs directory path for a specific app
15
- */
16
- export function getLogPath(appId: string): string {
17
- return join(LOGS_DIR, appId)
18
- }
19
-
20
- /**
21
- * Ensure the logs directory exists for an app
22
- */
23
- async function ensureLogDir(appId: string): Promise<string> {
24
- const logDir = getLogPath(appId)
25
- await mkdir(logDir, { recursive: true })
26
- return logDir
27
- }
28
-
29
- /**
30
- * Generate a timestamped log filename
31
- */
32
- function generateLogFilename(): string {
33
- const now = new Date()
34
- const timestamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)
35
- return `${timestamp}.log`
36
- }
37
-
38
- /**
39
- * Save container logs to a file
40
- * @returns The path to the saved log file
41
- */
42
- export async function saveLog(appId: string, content: string): Promise<string> {
43
- const logDir = await ensureLogDir(appId)
44
- const filename = generateLogFilename()
45
- const filepath = join(logDir, filename)
46
-
47
- await writeFile(filepath, content, "utf-8")
48
- debugLog("Logs", `Saved log for ${appId} to ${filepath}`)
49
-
50
- return filepath
51
- }
52
-
53
- /**
54
- * List saved logs for an app
55
- * @returns Array of log file info sorted by date (newest first)
56
- */
57
- export async function listSavedLogs(
58
- appId: string
59
- ): Promise<Array<{ filename: string; path: string; date: Date; size: number }>> {
60
- const logDir = getLogPath(appId)
61
-
62
- try {
63
- const files = await readdir(logDir)
64
- const logFiles = files.filter((f) => f.endsWith(".log"))
65
-
66
- const fileInfos = await Promise.all(
67
- logFiles.map(async (filename) => {
68
- const path = join(logDir, filename)
69
- const stats = await stat(path)
70
- return {
71
- filename,
72
- path,
73
- date: stats.mtime,
74
- size: stats.size,
75
- }
76
- })
77
- )
78
-
79
- // Sort by date, newest first
80
- return fileInfos.sort((a, b) => b.date.getTime() - a.date.getTime())
81
- } catch {
82
- // Directory doesn't exist yet
83
- return []
84
- }
85
- }
86
-
87
- /**
88
- * Get the base logs directory path
89
- */
90
- export function getLogsBaseDir(): string {
91
- return LOGS_DIR
92
- }
93
-
94
- /**
95
- * Format bytes to human-readable size
96
- */
97
- export function formatBytes(bytes: number): string {
98
- if (bytes < 1024) return `${bytes} B`
99
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
100
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
101
- }
102
-
103
- /**
104
- * Format relative time (e.g., "2 hours ago")
105
- */
106
- export function formatRelativeTime(date: Date): string {
107
- const now = new Date()
108
- const diffMs = now.getTime() - date.getTime()
109
- const diffSecs = Math.floor(diffMs / 1000)
110
- const diffMins = Math.floor(diffSecs / 60)
111
- const diffHours = Math.floor(diffMins / 60)
112
- const diffDays = Math.floor(diffHours / 24)
113
-
114
- if (diffDays > 0) return `${diffDays}d ago`
115
- if (diffHours > 0) return `${diffHours}h ago`
116
- if (diffMins > 0) return `${diffMins}m ago`
117
- return "just now"
118
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Migration: Rename env variables to new format
3
- *
4
- * OLD: GLOBAL_PASSWORD, QBITTORRENT_USER, etc.
5
- * NEW: PASSWORD_GLOBAL, USERNAME_QBITTORRENT, etc.
6
- */
7
-
8
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
9
- import { join } from "node:path"
10
- import { homedir } from "node:os"
11
- import { parseEnvFile, serializeEnv } from "../env"
12
- import { debugLog } from "../debug"
13
-
14
- const ENV_FILE = join(homedir(), ".easiarr", ".env")
15
-
16
- export const name = "rename-env-variables"
17
-
18
- const renames: [string, string][] = [
19
- // Global credentials
20
- ["GLOBAL_PASSWORD", "PASSWORD_GLOBAL"],
21
- ["GLOBAL_USERNAME", "USERNAME_GLOBAL"],
22
- // qBittorrent
23
- ["QBITTORRENT_PASSWORD", "PASSWORD_QBITTORRENT"],
24
- ["QBITTORRENT_USER", "USERNAME_QBITTORRENT"],
25
- ["QBITTORRENT_PASS", "PASSWORD_QBITTORRENT"], // Legacy alias
26
- // Portainer
27
- ["PORTAINER_PASSWORD", "PASSWORD_PORTAINER"],
28
- // PostgreSQL
29
- ["POSTGRESQL_USERNAME", "USERNAME_POSTGRESQL"],
30
- ["POSTGRESQL_PASSWORD", "PASSWORD_POSTGRESQL"],
31
- // VPN
32
- ["VPN_USERNAME", "USERNAME_VPN"],
33
- ["VPN_PASSWORD", "PASSWORD_VPN"],
34
- ]
35
-
36
- export function up(): boolean {
37
- if (!existsSync(ENV_FILE)) {
38
- debugLog("Migrations", "No .env file found, skipping migration")
39
- return false
40
- }
41
-
42
- try {
43
- const content = readFileSync(ENV_FILE, "utf-8")
44
- const env = parseEnvFile(content)
45
- let changed = false
46
-
47
- for (const [oldKey, newKey] of renames) {
48
- if (env[oldKey] !== undefined && env[newKey] === undefined) {
49
- env[newKey] = env[oldKey]
50
- delete env[oldKey]
51
- changed = true
52
- debugLog("Migrations", `Renamed ${oldKey} → ${newKey}`)
53
- }
54
- }
55
-
56
- if (changed) {
57
- writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
58
- debugLog("Migrations", "Migration completed: env variables renamed")
59
- return true
60
- }
61
-
62
- debugLog("Migrations", "No changes needed")
63
- return false
64
- } catch (e) {
65
- debugLog("Migrations", `Migration error: ${e}`)
66
- return false
67
- }
68
- }
69
-
70
- export function down(): boolean {
71
- if (!existsSync(ENV_FILE)) {
72
- return false
73
- }
74
-
75
- try {
76
- const content = readFileSync(ENV_FILE, "utf-8")
77
- const env = parseEnvFile(content)
78
- let changed = false
79
-
80
- for (const [oldKey, newKey] of renames) {
81
- // Reverse operation: restore oldKey from newKey
82
- if (env[newKey] !== undefined && env[oldKey] === undefined) {
83
- env[oldKey] = env[newKey]
84
- delete env[newKey]
85
- changed = true
86
- debugLog("Migrations", `Rolled back ${newKey} → ${oldKey}`)
87
- }
88
- }
89
-
90
- if (changed) {
91
- writeFileSync(ENV_FILE, serializeEnv(env), "utf-8")
92
- debugLog("Migrations", "Rollback completed: env variables restored")
93
- return true
94
- }
95
-
96
- return false
97
- } catch (e) {
98
- debugLog("Migrations", `Rollback error: ${e}`)
99
- return false
100
- }
101
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Migration: rename easiarr-status to easiarr
3
- *
4
- * OLD: { "id": "easiarr-status", "enabled": true }
5
- * NEW: { "id": "easiarr", "enabled": true }
6
- */
7
-
8
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
9
- import { join } from "node:path"
10
- import { homedir } from "node:os"
11
- import { debugLog } from "../debug"
12
-
13
- const CONFIG_FILE = join(homedir(), ".easiarr", "config.json")
14
-
15
- export const name = "rename_easiarr_status"
16
-
17
- export function up(): boolean {
18
- if (!existsSync(CONFIG_FILE)) {
19
- debugLog("Migrations", "No config.json file found, skipping migration")
20
- return false
21
- }
22
-
23
- try {
24
- const content = readFileSync(CONFIG_FILE, "utf-8")
25
- const config = JSON.parse(content)
26
-
27
- if (!config.apps || !Array.isArray(config.apps)) {
28
- debugLog("Migrations", "No apps array in config, skipping migration")
29
- return false
30
- }
31
-
32
- let changed = false
33
-
34
- for (const app of config.apps) {
35
- if (app.id === "easiarr-status") {
36
- app.id = "easiarr"
37
- changed = true
38
- debugLog("Migrations", "Renamed easiarr-status → easiarr")
39
- }
40
- }
41
-
42
- if (changed) {
43
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
44
- debugLog("Migrations", "Migration completed: app id renamed")
45
- return true
46
- }
47
-
48
- debugLog("Migrations", "No changes needed")
49
- return false
50
- } catch (e) {
51
- debugLog("Migrations", `Migration error: ${e}`)
52
- return false
53
- }
54
- }
55
-
56
- export function down(): boolean {
57
- if (!existsSync(CONFIG_FILE)) {
58
- return false
59
- }
60
-
61
- try {
62
- const content = readFileSync(CONFIG_FILE, "utf-8")
63
- const config = JSON.parse(content)
64
-
65
- if (!config.apps || !Array.isArray(config.apps)) {
66
- return false
67
- }
68
-
69
- let changed = false
70
-
71
- for (const app of config.apps) {
72
- if (app.id === "easiarr") {
73
- app.id = "easiarr-status"
74
- changed = true
75
- debugLog("Migrations", "Rolled back easiarr → easiarr-status")
76
- }
77
- }
78
-
79
- if (changed) {
80
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8")
81
- debugLog("Migrations", "Rollback completed: app id restored")
82
- return true
83
- }
84
-
85
- return false
86
- } catch (e) {
87
- debugLog("Migrations", `Rollback error: ${e}`)
88
- return false
89
- }
90
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Migration: remove cloudflare dns api token
3
- * Removes the unused CLOUDFLARE_DNS_API_TOKEN from .env
4
- */
5
-
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
7
- import { getEnvPath } from "../env"
8
- import { debugLog } from "../debug"
9
-
10
- export const name = "remove_cloudflare_dns_api_token"
11
-
12
- export function up(): boolean {
13
- debugLog("Migrations", "Running migration: remove_cloudflare_dns_api_token")
14
-
15
- const envPath = getEnvPath()
16
- if (!existsSync(envPath)) {
17
- debugLog("Migrations", ".env file not found, skipping")
18
- return true
19
- }
20
-
21
- const content = readFileSync(envPath, "utf-8")
22
-
23
- // Check if CLOUDFLARE_DNS_API_TOKEN exists
24
- if (!content.includes("CLOUDFLARE_DNS_API_TOKEN")) {
25
- debugLog("Migrations", "CLOUDFLARE_DNS_API_TOKEN not found, skipping")
26
- return true
27
- }
28
-
29
- // Remove the line containing CLOUDFLARE_DNS_API_TOKEN
30
- const lines = content.split("\n")
31
- const filtered = lines.filter((line) => !line.startsWith("CLOUDFLARE_DNS_API_TOKEN="))
32
- const newContent = filtered.join("\n")
33
-
34
- writeFileSync(envPath, newContent, "utf-8")
35
- debugLog("Migrations", "Removed CLOUDFLARE_DNS_API_TOKEN from .env")
36
-
37
- return true
38
- }
39
-
40
- export function down(): boolean {
41
- debugLog("Migrations", "Reverting migration: remove_cloudflare_dns_api_token")
42
- // No revert needed - the token was unused
43
- return true
44
- }
@@ -1,107 +0,0 @@
1
- /**
2
- * Migration: migrate to xdg
3
- *
4
- * Moves config from ~/.easiarr to $XDG_CONFIG_HOME/easiarr
5
- */
6
-
7
- import { existsSync, lstatSync, readdirSync, rmSync } from "node:fs"
8
- import { copyFileSync, cpSync, mkdirSync } from "node:fs"
9
- import { homedir } from "node:os"
10
- import { join } from "node:path"
11
-
12
- import { debugLog } from "../debug"
13
-
14
- export const name = "migrate_to_xdg"
15
-
16
- const OLD_DIR = join(homedir(), ".easiarr")
17
-
18
- function getNewDir(): string {
19
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
20
- return join(xdgConfigHome, "easiarr")
21
- }
22
-
23
- export function up(): boolean {
24
- const newDir = getNewDir()
25
-
26
- // Skip if old directory doesn't exist
27
- if (!existsSync(OLD_DIR)) {
28
- debugLog("Migrations", "No legacy ~/.easiarr directory found, skipping migration")
29
- return false
30
- }
31
-
32
- // Skip if already at XDG location (same path)
33
- if (OLD_DIR === newDir) {
34
- debugLog("Migrations", "Old and new paths are identical, skipping")
35
- return false
36
- }
37
-
38
- // Skip if new directory already has config
39
- if (existsSync(join(newDir, "config.json"))) {
40
- debugLog("Migrations", "XDG config already exists, skipping migration")
41
- return false
42
- }
43
-
44
- debugLog("Migrations", `Migrating from ${OLD_DIR} to ${newDir}`)
45
-
46
- try {
47
- // Ensure target directory exists
48
- if (!existsSync(newDir)) {
49
- mkdirSync(newDir, { recursive: true })
50
- debugLog("Migrations", `Created directory ${newDir}`)
51
- }
52
-
53
- // Get all entries in old directory
54
- const entries = readdirSync(OLD_DIR)
55
-
56
- for (const entry of entries) {
57
- // Skip migrations file - it will be recreated in new location
58
- if (entry === ".migrations.json") continue
59
-
60
- const oldPath = join(OLD_DIR, entry)
61
- const newPath = join(newDir, entry)
62
-
63
- // Skip if entry already exists in new location
64
- if (existsSync(newPath)) {
65
- debugLog("Migrations", `Skipping ${entry}, already exists in new location`)
66
- continue
67
- }
68
-
69
- try {
70
- const stat = lstatSync(oldPath)
71
-
72
- if (stat.isDirectory()) {
73
- // Copy directory recursively (e.g., backups/)
74
- cpSync(oldPath, newPath, { recursive: true })
75
- debugLog("Migrations", `Copied directory ${entry} to XDG location`)
76
- } else if (stat.isFile()) {
77
- // Copy file
78
- copyFileSync(oldPath, newPath)
79
- debugLog("Migrations", `Copied ${entry} to XDG location`)
80
- }
81
- } catch {
82
- debugLog("Migrations", `Failed to copy ${entry}`)
83
- }
84
- }
85
-
86
- // Remove old directory completely
87
- try {
88
- rmSync(OLD_DIR, { recursive: true, force: true })
89
- debugLog("Migrations", `Removed legacy directory ${OLD_DIR}`)
90
- } catch {
91
- debugLog("Migrations", `Could not remove old directory`)
92
- }
93
-
94
- debugLog("Migrations", "XDG migration completed successfully")
95
- return true
96
- } catch (e) {
97
- debugLog("Migrations", `Migration error: ${e}`)
98
- return false
99
- }
100
- }
101
-
102
- export function down(): boolean {
103
- // This migration is not easily reversible
104
- // The old config location is deprecated
105
- debugLog("Migrations", "XDG migration rollback not supported")
106
- return false
107
- }