@muhammedaksam/easiarr 0.8.3 → 0.8.5
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.
- package/package.json +1 -1
- package/src/api/jellyfin-api.ts +47 -5
- package/src/api/jellyseerr-api.ts +538 -0
- package/src/apps/registry.ts +10 -6
- package/src/compose/generator.ts +1 -1
- package/src/config/bookmarks-generator.ts +127 -0
- package/src/config/homepage-config.ts +7 -7
- package/src/config/schema.ts +2 -2
- package/src/index.ts +1 -1
- package/src/ui/screens/FullAutoSetup.ts +104 -0
- package/src/ui/screens/JellyfinSetup.ts +1 -1
- package/src/ui/screens/JellyseerrSetup.ts +612 -0
- package/src/ui/screens/MainMenu.ts +160 -174
- package/src/ui/screens/QuickSetup.ts +3 -3
- package/src/utils/browser.ts +26 -0
- package/src/utils/debug.ts +2 -2
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +90 -0
- package/src/utils/migrations.ts +12 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Main Menu Screen
|
|
3
|
-
* Central navigation hub for
|
|
3
|
+
* Central navigation hub for easiarr
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { RenderContext, CliRenderer } from "@opentui/core"
|
|
@@ -9,6 +9,8 @@ import type { App } from "../App"
|
|
|
9
9
|
import type { EasiarrConfig } from "../../config/schema"
|
|
10
10
|
import { createPageLayout } from "../components/PageLayout"
|
|
11
11
|
import { saveCompose } from "../../compose"
|
|
12
|
+
import { saveBookmarks } from "../../config/bookmarks-generator"
|
|
13
|
+
import { openUrl } from "../../utils/browser"
|
|
12
14
|
import { ApiKeyViewer } from "./ApiKeyViewer"
|
|
13
15
|
import { AppConfigurator } from "./AppConfigurator"
|
|
14
16
|
import { TRaSHProfileSetup } from "./TRaSHProfileSetup"
|
|
@@ -18,6 +20,11 @@ import { FullAutoSetup } from "./FullAutoSetup"
|
|
|
18
20
|
import { MonitorDashboard } from "./MonitorDashboard"
|
|
19
21
|
import { HomepageSetup } from "./HomepageSetup"
|
|
20
22
|
import { JellyfinSetup } from "./JellyfinSetup"
|
|
23
|
+
import { JellyseerrSetup } from "./JellyseerrSetup"
|
|
24
|
+
|
|
25
|
+
type MenuItem = { name: string; description: string; action: () => void | Promise<void> }
|
|
26
|
+
|
|
27
|
+
type ScreenConstructor = new (renderer: CliRenderer, config: EasiarrConfig, onBack: () => void) => BoxRenderable
|
|
21
28
|
|
|
22
29
|
export class MainMenu {
|
|
23
30
|
private renderer: RenderContext
|
|
@@ -26,6 +33,7 @@ export class MainMenu {
|
|
|
26
33
|
private config: EasiarrConfig
|
|
27
34
|
private menu!: SelectRenderable
|
|
28
35
|
private page!: BoxRenderable
|
|
36
|
+
private menuItems: MenuItem[] = []
|
|
29
37
|
|
|
30
38
|
constructor(renderer: RenderContext, container: BoxRenderable, app: App, config: EasiarrConfig) {
|
|
31
39
|
this.renderer = renderer
|
|
@@ -36,6 +44,150 @@ export class MainMenu {
|
|
|
36
44
|
this.render()
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
private isAppEnabled(id: string): boolean {
|
|
48
|
+
return this.config.apps.some((a) => a.id === id && a.enabled)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private buildMenuItems(): MenuItem[] {
|
|
52
|
+
const items: MenuItem[] = []
|
|
53
|
+
|
|
54
|
+
// Core items (always shown)
|
|
55
|
+
items.push({
|
|
56
|
+
name: "📦 Manage Apps",
|
|
57
|
+
description: "Add, remove, or configure apps",
|
|
58
|
+
action: () => this.app.navigateTo("appManager"),
|
|
59
|
+
})
|
|
60
|
+
items.push({
|
|
61
|
+
name: "🐳 Container Control",
|
|
62
|
+
description: "Start, stop, restart containers",
|
|
63
|
+
action: () => this.app.navigateTo("containerControl"),
|
|
64
|
+
})
|
|
65
|
+
items.push({
|
|
66
|
+
name: "⚙️ Advanced Settings",
|
|
67
|
+
description: "Customize ports, volumes, env",
|
|
68
|
+
action: () => this.app.navigateTo("advancedSettings"),
|
|
69
|
+
})
|
|
70
|
+
items.push({
|
|
71
|
+
name: "🔑 Extract API Keys",
|
|
72
|
+
description: "Find API keys from running containers",
|
|
73
|
+
action: () => this.showScreen(ApiKeyViewer),
|
|
74
|
+
})
|
|
75
|
+
items.push({
|
|
76
|
+
name: "⚙️ Configure Apps",
|
|
77
|
+
description: "Set root folders and download clients via API",
|
|
78
|
+
action: () => this.showScreen(AppConfigurator),
|
|
79
|
+
})
|
|
80
|
+
items.push({
|
|
81
|
+
name: "🎯 TRaSH Guide Setup",
|
|
82
|
+
description: "Apply TRaSH quality profiles and custom formats",
|
|
83
|
+
action: () => this.showScreen(TRaSHProfileSetup),
|
|
84
|
+
})
|
|
85
|
+
items.push({
|
|
86
|
+
name: "🔄 Regenerate Compose",
|
|
87
|
+
description: "Rebuild docker-compose.yml",
|
|
88
|
+
action: async () => {
|
|
89
|
+
await saveCompose(this.config)
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Conditional items based on enabled apps
|
|
94
|
+
if (this.isAppEnabled("prowlarr")) {
|
|
95
|
+
items.push({
|
|
96
|
+
name: "🔗 Prowlarr Setup",
|
|
97
|
+
description: "Sync indexers to *arr apps, FlareSolverr",
|
|
98
|
+
action: () => this.showScreen(ProwlarrSetup),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
if (this.isAppEnabled("qbittorrent")) {
|
|
102
|
+
items.push({
|
|
103
|
+
name: "⚡ qBittorrent Setup",
|
|
104
|
+
description: "Configure TRaSH-compliant paths and categories",
|
|
105
|
+
action: () => this.showScreen(QBittorrentSetup),
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Full Auto Setup (always shown)
|
|
110
|
+
items.push({
|
|
111
|
+
name: "🚀 Full Auto Setup",
|
|
112
|
+
description: "Run all configurations (Auth, Root Folders, Prowlarr, etc.)",
|
|
113
|
+
action: () => this.showScreen(FullAutoSetup),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
items.push({
|
|
117
|
+
name: "📊 Monitor Dashboard",
|
|
118
|
+
description: "Configure app health monitoring",
|
|
119
|
+
action: () => this.showScreen(MonitorDashboard),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (this.isAppEnabled("homepage")) {
|
|
123
|
+
items.push({
|
|
124
|
+
name: "🏠 Homepage Setup",
|
|
125
|
+
description: "Generate Homepage dashboard config",
|
|
126
|
+
action: () => this.showScreen(HomepageSetup),
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
if (this.isAppEnabled("jellyfin")) {
|
|
130
|
+
items.push({
|
|
131
|
+
name: "🎬 Jellyfin Setup",
|
|
132
|
+
description: "Run Jellyfin setup wizard via API",
|
|
133
|
+
action: () => this.showScreen(JellyfinSetup),
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
if (this.isAppEnabled("jellyseerr")) {
|
|
137
|
+
items.push({
|
|
138
|
+
name: "🎥 Jellyseerr Setup",
|
|
139
|
+
description: "Configure Jellyseerr with media server",
|
|
140
|
+
action: () => this.showScreen(JellyseerrSetup),
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
// Bookmark generation options
|
|
144
|
+
const generateAndOpenBookmarks = async (useLocalUrls: boolean) => {
|
|
145
|
+
await saveBookmarks(this.config, useLocalUrls)
|
|
146
|
+
// Open in browser if easiarr service is enabled
|
|
147
|
+
if (this.isAppEnabled("easiarr")) {
|
|
148
|
+
const port = this.config.apps.find((a) => a.id === "easiarr")?.port ?? 3010
|
|
149
|
+
await openUrl(`http://localhost:${port}/bookmarks.html`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this.config.traefik?.enabled) {
|
|
154
|
+
items.push({
|
|
155
|
+
name: "📑 Bookmarks (Local URLs)",
|
|
156
|
+
description: "Generate bookmarks using localhost addresses",
|
|
157
|
+
action: async () => generateAndOpenBookmarks(true),
|
|
158
|
+
})
|
|
159
|
+
items.push({
|
|
160
|
+
name: "📑 Bookmarks (Traefik URLs)",
|
|
161
|
+
description: `Generate bookmarks using ${this.config.traefik.domain} addresses`,
|
|
162
|
+
action: async () => generateAndOpenBookmarks(false),
|
|
163
|
+
})
|
|
164
|
+
} else {
|
|
165
|
+
items.push({
|
|
166
|
+
name: "📑 Generate Bookmarks",
|
|
167
|
+
description: "Create browser-importable bookmarks file",
|
|
168
|
+
action: async () => generateAndOpenBookmarks(true),
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
items.push({
|
|
173
|
+
name: "❌ Exit",
|
|
174
|
+
description: "Close easiarr",
|
|
175
|
+
action: () => process.exit(0),
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
return items
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private showScreen(ScreenClass: ScreenConstructor): void {
|
|
182
|
+
this.menu.blur()
|
|
183
|
+
this.page.visible = false
|
|
184
|
+
const screen = new ScreenClass(this.renderer as CliRenderer, this.config, () => {
|
|
185
|
+
this.page.visible = true
|
|
186
|
+
this.menu.focus()
|
|
187
|
+
})
|
|
188
|
+
this.container.add(screen)
|
|
189
|
+
}
|
|
190
|
+
|
|
39
191
|
private render(): void {
|
|
40
192
|
const { container: page, content } = createPageLayout(this.renderer as CliRenderer, {
|
|
41
193
|
title: "Main Menu",
|
|
@@ -81,187 +233,21 @@ export class MainMenu {
|
|
|
81
233
|
|
|
82
234
|
content.add(new TextRenderable(this.renderer, { id: "spacer2", content: " " }))
|
|
83
235
|
|
|
236
|
+
// Build menu items dynamically based on enabled apps
|
|
237
|
+
this.menuItems = this.buildMenuItems()
|
|
238
|
+
|
|
84
239
|
// Menu
|
|
85
240
|
this.menu = new SelectRenderable(this.renderer, {
|
|
86
241
|
id: "main-menu-select",
|
|
87
242
|
width: "100%",
|
|
88
243
|
flexGrow: 1,
|
|
89
|
-
options:
|
|
90
|
-
{
|
|
91
|
-
name: "📦 Manage Apps",
|
|
92
|
-
description: "Add, remove, or configure apps",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: "🐳 Container Control",
|
|
96
|
-
description: "Start, stop, restart containers",
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "⚙️ Advanced Settings",
|
|
100
|
-
description: "Customize ports, volumes, env",
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "🔑 Extract API Keys",
|
|
104
|
-
description: "Find API keys from running containers",
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: "⚙️ Configure Apps",
|
|
108
|
-
description: "Set root folders and download clients via API",
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: "🎯 TRaSH Guide Setup",
|
|
112
|
-
description: "Apply TRaSH quality profiles and custom formats",
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
name: "🔄 Regenerate Compose",
|
|
116
|
-
description: "Rebuild docker-compose.yml",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
name: "🔗 Prowlarr Setup",
|
|
120
|
-
description: "Sync indexers to *arr apps, FlareSolverr",
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: "⚡ qBittorrent Setup",
|
|
124
|
-
description: "Configure TRaSH-compliant paths and categories",
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "🚀 Full Auto Setup",
|
|
128
|
-
description: "Run all configurations (Auth, Root Folders, Prowlarr, etc.)",
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: "📊 Monitor Dashboard",
|
|
132
|
-
description: "Configure app health monitoring",
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: "🏠 Homepage Setup",
|
|
136
|
-
description: "Generate Homepage dashboard config",
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: "🎬 Jellyfin Setup",
|
|
140
|
-
description: "Run Jellyfin setup wizard via API",
|
|
141
|
-
},
|
|
142
|
-
{ name: "❌ Exit", description: "Close easiarr" },
|
|
143
|
-
],
|
|
244
|
+
options: this.menuItems.map((item) => ({ name: item.name, description: item.description })),
|
|
144
245
|
})
|
|
145
246
|
|
|
146
247
|
this.menu.on(SelectRenderableEvents.ITEM_SELECTED, async (index) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
break
|
|
151
|
-
case 1:
|
|
152
|
-
this.app.navigateTo("containerControl")
|
|
153
|
-
break
|
|
154
|
-
case 2:
|
|
155
|
-
this.app.navigateTo("advancedSettings")
|
|
156
|
-
break
|
|
157
|
-
case 3: {
|
|
158
|
-
// API Key Extractor
|
|
159
|
-
this.menu.blur()
|
|
160
|
-
this.page.visible = false
|
|
161
|
-
const viewer = new ApiKeyViewer(this.renderer as CliRenderer, this.config, () => {
|
|
162
|
-
// On Back
|
|
163
|
-
this.page.visible = true
|
|
164
|
-
this.menu.focus()
|
|
165
|
-
})
|
|
166
|
-
this.container.add(viewer)
|
|
167
|
-
break
|
|
168
|
-
}
|
|
169
|
-
case 4: {
|
|
170
|
-
// Configure Apps
|
|
171
|
-
this.menu.blur()
|
|
172
|
-
this.page.visible = false
|
|
173
|
-
const configurator = new AppConfigurator(this.renderer as CliRenderer, this.config, () => {
|
|
174
|
-
this.page.visible = true
|
|
175
|
-
this.menu.focus()
|
|
176
|
-
})
|
|
177
|
-
this.container.add(configurator)
|
|
178
|
-
break
|
|
179
|
-
}
|
|
180
|
-
case 5: {
|
|
181
|
-
// TRaSH Profile Setup
|
|
182
|
-
this.menu.blur()
|
|
183
|
-
this.page.visible = false
|
|
184
|
-
const trashSetup = new TRaSHProfileSetup(this.renderer as CliRenderer, this.config, () => {
|
|
185
|
-
this.page.visible = true
|
|
186
|
-
this.menu.focus()
|
|
187
|
-
})
|
|
188
|
-
this.container.add(trashSetup)
|
|
189
|
-
break
|
|
190
|
-
}
|
|
191
|
-
case 6: {
|
|
192
|
-
// Regenerate compose
|
|
193
|
-
await saveCompose(this.config)
|
|
194
|
-
break
|
|
195
|
-
}
|
|
196
|
-
case 7: {
|
|
197
|
-
// Prowlarr Setup
|
|
198
|
-
this.menu.blur()
|
|
199
|
-
this.page.visible = false
|
|
200
|
-
const prowlarrSetup = new ProwlarrSetup(this.renderer as CliRenderer, this.config, () => {
|
|
201
|
-
this.page.visible = true
|
|
202
|
-
this.menu.focus()
|
|
203
|
-
})
|
|
204
|
-
this.container.add(prowlarrSetup)
|
|
205
|
-
break
|
|
206
|
-
}
|
|
207
|
-
case 8: {
|
|
208
|
-
// qBittorrent Setup
|
|
209
|
-
this.menu.blur()
|
|
210
|
-
this.page.visible = false
|
|
211
|
-
const qbSetup = new QBittorrentSetup(this.renderer as CliRenderer, this.config, () => {
|
|
212
|
-
this.page.visible = true
|
|
213
|
-
this.menu.focus()
|
|
214
|
-
})
|
|
215
|
-
this.container.add(qbSetup)
|
|
216
|
-
break
|
|
217
|
-
}
|
|
218
|
-
case 9: {
|
|
219
|
-
// Full Auto Setup
|
|
220
|
-
this.menu.blur()
|
|
221
|
-
this.page.visible = false
|
|
222
|
-
const autoSetup = new FullAutoSetup(this.renderer as CliRenderer, this.config, () => {
|
|
223
|
-
this.page.visible = true
|
|
224
|
-
this.menu.focus()
|
|
225
|
-
})
|
|
226
|
-
this.container.add(autoSetup)
|
|
227
|
-
break
|
|
228
|
-
}
|
|
229
|
-
case 10: {
|
|
230
|
-
// Monitor Dashboard
|
|
231
|
-
this.menu.blur()
|
|
232
|
-
this.page.visible = false
|
|
233
|
-
const monitor = new MonitorDashboard(this.renderer as CliRenderer, this.config, () => {
|
|
234
|
-
this.page.visible = true
|
|
235
|
-
this.menu.focus()
|
|
236
|
-
})
|
|
237
|
-
this.container.add(monitor)
|
|
238
|
-
break
|
|
239
|
-
}
|
|
240
|
-
case 11: {
|
|
241
|
-
// Homepage Setup
|
|
242
|
-
this.menu.blur()
|
|
243
|
-
this.page.visible = false
|
|
244
|
-
const homepageSetup = new HomepageSetup(this.renderer as CliRenderer, this.config, () => {
|
|
245
|
-
this.page.visible = true
|
|
246
|
-
this.menu.focus()
|
|
247
|
-
})
|
|
248
|
-
this.container.add(homepageSetup)
|
|
249
|
-
break
|
|
250
|
-
}
|
|
251
|
-
case 12: {
|
|
252
|
-
// Jellyfin Setup
|
|
253
|
-
this.menu.blur()
|
|
254
|
-
this.page.visible = false
|
|
255
|
-
const jellyfinSetup = new JellyfinSetup(this.renderer as CliRenderer, this.config, () => {
|
|
256
|
-
this.page.visible = true
|
|
257
|
-
this.menu.focus()
|
|
258
|
-
})
|
|
259
|
-
this.container.add(jellyfinSetup)
|
|
260
|
-
break
|
|
261
|
-
}
|
|
262
|
-
case 13:
|
|
263
|
-
process.exit(0)
|
|
264
|
-
break
|
|
248
|
+
const item = this.menuItems[index]
|
|
249
|
+
if (item) {
|
|
250
|
+
await item.action()
|
|
265
251
|
}
|
|
266
252
|
})
|
|
267
253
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Quick Setup Wizard
|
|
3
|
-
* First-time setup flow for
|
|
3
|
+
* First-time setup flow for easiarr
|
|
4
4
|
*/
|
|
5
5
|
import { homedir } from "node:os"
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ export class QuickSetup {
|
|
|
41
41
|
"jellyseerr",
|
|
42
42
|
"flaresolverr",
|
|
43
43
|
"homepage",
|
|
44
|
-
"easiarr
|
|
44
|
+
"easiarr",
|
|
45
45
|
])
|
|
46
46
|
|
|
47
47
|
private rootDir: string = `${homedir()}/media`
|
|
@@ -208,7 +208,7 @@ export class QuickSetup {
|
|
|
208
208
|
selectedBackgroundColor: "#3a4a6e",
|
|
209
209
|
options: [
|
|
210
210
|
{ name: "▶ Start Setup", description: "Begin the configuration wizard" },
|
|
211
|
-
{ name: "📖 About", description: "Learn more about
|
|
211
|
+
{ name: "📖 About", description: "Learn more about easiarr" },
|
|
212
212
|
{ name: "✕ Exit", description: "Quit the application" },
|
|
213
213
|
],
|
|
214
214
|
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Utilities
|
|
3
|
+
* Open URLs in the default browser
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { $ } from "bun"
|
|
7
|
+
import { platform } from "node:os"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Open a URL in the default browser
|
|
11
|
+
*/
|
|
12
|
+
export async function openUrl(url: string): Promise<void> {
|
|
13
|
+
const os = platform()
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
if (os === "linux") {
|
|
17
|
+
await $`xdg-open ${url}`.quiet()
|
|
18
|
+
} else if (os === "darwin") {
|
|
19
|
+
await $`open ${url}`.quiet()
|
|
20
|
+
} else if (os === "win32") {
|
|
21
|
+
await $`cmd /c start ${url}`.quiet()
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Silently fail if browser can't be opened
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/utils/debug.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Debug logging utility for
|
|
2
|
+
* Debug logging utility for easiarr
|
|
3
3
|
*
|
|
4
4
|
* Enable debug logging via:
|
|
5
5
|
* - CLI flag: easiarr --debug
|
|
@@ -25,7 +25,7 @@ const logFile = join(easiarrDir, "debug.log")
|
|
|
25
25
|
export function initDebug(): void {
|
|
26
26
|
if (!DEBUG_ENABLED) return
|
|
27
27
|
try {
|
|
28
|
-
writeFileSync(logFile, `===
|
|
28
|
+
writeFileSync(logFile, `=== easiarr Debug Log - ${new Date().toISOString()} ===\n`)
|
|
29
29
|
} catch {
|
|
30
30
|
// Ignore
|
|
31
31
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
}
|
package/src/utils/migrations.ts
CHANGED
|
@@ -69,6 +69,18 @@ async function loadMigrations(): Promise<Migration[]> {
|
|
|
69
69
|
debugLog("Migrations", `Failed to load migration: ${e}`)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
try {
|
|
73
|
+
const m1765707135 = await import("./migrations/1765707135_rename_easiarr_status")
|
|
74
|
+
migrations.push({
|
|
75
|
+
timestamp: "1765707135",
|
|
76
|
+
name: m1765707135.name,
|
|
77
|
+
up: m1765707135.up,
|
|
78
|
+
down: m1765707135.down,
|
|
79
|
+
})
|
|
80
|
+
} catch (e) {
|
|
81
|
+
debugLog("Migrations", `Failed to load migration: ${e}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
// Add future migrations here:
|
|
73
85
|
// const m2 = await import("./migrations/1734xxxxxx_xxx")
|
|
74
86
|
// migrations.push({ timestamp: "...", name: m2.name, up: m2.up })
|