@muhammedaksam/easiarr 0.8.4 → 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 +1 -1
- package/src/apps/registry.ts +9 -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/JellyfinSetup.ts +1 -1
- package/src/ui/screens/MainMenu.ts +31 -1
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
package/src/api/jellyfin-api.ts
CHANGED
|
@@ -136,7 +136,7 @@ export class JellyfinClient {
|
|
|
136
136
|
"Content-Type": "application/json",
|
|
137
137
|
// Jellyfin requires client identification
|
|
138
138
|
"X-Emby-Authorization":
|
|
139
|
-
'MediaBrowser Client="
|
|
139
|
+
'MediaBrowser Client="easiarr", Device="Server", DeviceId="easiarr-setup", Version="1.0.0"' +
|
|
140
140
|
(this.accessToken ? `, Token="${this.accessToken}"` : ""),
|
|
141
141
|
...((options.headers as Record<string, string>) || {}),
|
|
142
142
|
}
|
package/src/apps/registry.ts
CHANGED
|
@@ -583,17 +583,20 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
583
583
|
volumes: (root) => [`${root}/config/ddns-updater:/data`],
|
|
584
584
|
},
|
|
585
585
|
|
|
586
|
-
|
|
587
|
-
id: "easiarr
|
|
588
|
-
name: "
|
|
589
|
-
description: "Exposes
|
|
586
|
+
easiarr: {
|
|
587
|
+
id: "easiarr",
|
|
588
|
+
name: "easiarr",
|
|
589
|
+
description: "Exposes easiarr config and bookmarks for Homepage dashboard",
|
|
590
590
|
category: "utility",
|
|
591
591
|
defaultPort: 3010,
|
|
592
592
|
internalPort: 8080,
|
|
593
593
|
image: "halverneus/static-file-server:latest",
|
|
594
594
|
puid: 0,
|
|
595
595
|
pgid: 0,
|
|
596
|
-
volumes: () => [
|
|
596
|
+
volumes: () => [
|
|
597
|
+
"${HOME}/.easiarr/config.json:/web/config.json:ro",
|
|
598
|
+
"${HOME}/.easiarr/bookmarks.html:/web/bookmarks.html:ro",
|
|
599
|
+
],
|
|
597
600
|
environment: {
|
|
598
601
|
FOLDER: "/web",
|
|
599
602
|
CORS: "true",
|
|
@@ -602,7 +605,7 @@ export const APPS: Record<AppId, AppDefinition> = {
|
|
|
602
605
|
icon: "mdi-docker",
|
|
603
606
|
widget: "customapi",
|
|
604
607
|
widgetFields: {
|
|
605
|
-
url: "http://easiarr
|
|
608
|
+
url: "http://easiarr:8080/config.json",
|
|
606
609
|
mappings: JSON.stringify([{ field: "version", label: "Installed" }]),
|
|
607
610
|
},
|
|
608
611
|
},
|
package/src/compose/generator.ts
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bookmarks Generator
|
|
3
|
+
* Generates Netscape-format HTML bookmarks for browser import
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writeFile } from "node:fs/promises"
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
import { homedir } from "node:os"
|
|
9
|
+
import type { EasiarrConfig, AppCategory } from "./schema"
|
|
10
|
+
import { APP_CATEGORIES } from "./schema"
|
|
11
|
+
import { CATEGORY_ORDER } from "../apps/categories"
|
|
12
|
+
import { getApp } from "../apps/registry"
|
|
13
|
+
import { readEnvSync } from "../utils/env"
|
|
14
|
+
|
|
15
|
+
interface BookmarkEntry {
|
|
16
|
+
name: string
|
|
17
|
+
url: string
|
|
18
|
+
description: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type CategoryBookmarks = Map<AppCategory, BookmarkEntry[]>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the URL for an app based on Traefik configuration
|
|
25
|
+
*/
|
|
26
|
+
function getAppUrl(appId: string, port: number, config: EasiarrConfig, useLocalUrls: boolean): string {
|
|
27
|
+
if (!useLocalUrls && config.traefik?.enabled && config.traefik.domain) {
|
|
28
|
+
return `https://${appId}.${config.traefik.domain}/`
|
|
29
|
+
}
|
|
30
|
+
// Read LOCAL_DOCKER_IP from .env file, fallback to localhost
|
|
31
|
+
const env = readEnvSync()
|
|
32
|
+
const host = env.LOCAL_DOCKER_IP || "localhost"
|
|
33
|
+
return `http://${host}:${port}/`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate bookmark entries grouped by category
|
|
38
|
+
*/
|
|
39
|
+
function generateBookmarksByCategory(config: EasiarrConfig, useLocalUrls: boolean): CategoryBookmarks {
|
|
40
|
+
const categoryBookmarks: CategoryBookmarks = new Map()
|
|
41
|
+
|
|
42
|
+
for (const appConfig of config.apps) {
|
|
43
|
+
if (!appConfig.enabled) continue
|
|
44
|
+
|
|
45
|
+
const appDef = getApp(appConfig.id)
|
|
46
|
+
if (!appDef) continue
|
|
47
|
+
|
|
48
|
+
const port = appConfig.port ?? appDef.defaultPort
|
|
49
|
+
const url = getAppUrl(appConfig.id, port, config, useLocalUrls)
|
|
50
|
+
|
|
51
|
+
const entry: BookmarkEntry = {
|
|
52
|
+
name: appDef.name,
|
|
53
|
+
url,
|
|
54
|
+
description: appDef.description,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const category = appDef.category
|
|
58
|
+
if (!categoryBookmarks.has(category)) {
|
|
59
|
+
categoryBookmarks.set(category, [])
|
|
60
|
+
}
|
|
61
|
+
categoryBookmarks.get(category)!.push(entry)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return categoryBookmarks
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate Netscape-format HTML bookmarks
|
|
69
|
+
*/
|
|
70
|
+
export function generateBookmarksHtml(config: EasiarrConfig, useLocalUrls = false): string {
|
|
71
|
+
const categoryBookmarks = generateBookmarksByCategory(config, useLocalUrls)
|
|
72
|
+
|
|
73
|
+
let html = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
|
74
|
+
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
|
75
|
+
<TITLE>Bookmarks</TITLE>
|
|
76
|
+
<H1>Bookmarks</H1>
|
|
77
|
+
<DL><p>
|
|
78
|
+
<DT><H3 PERSONAL_TOOLBAR_FOLDER="true">easiarr</H3>
|
|
79
|
+
<DL><p>
|
|
80
|
+
`
|
|
81
|
+
|
|
82
|
+
// Add external resources first
|
|
83
|
+
html += ` <DT><A HREF="https://github.com/muhammedaksam/easiarr/">GitHub | easiarr Project Repo</A>\n`
|
|
84
|
+
html += ` <DT><A HREF="https://trash-guides.info/">TRaSH Guides</A>\n`
|
|
85
|
+
|
|
86
|
+
// Add apps grouped by category in defined order
|
|
87
|
+
for (const { id: categoryId } of CATEGORY_ORDER) {
|
|
88
|
+
const bookmarks = categoryBookmarks.get(categoryId)
|
|
89
|
+
if (!bookmarks || bookmarks.length === 0) continue
|
|
90
|
+
|
|
91
|
+
const categoryName = APP_CATEGORIES[categoryId]
|
|
92
|
+
|
|
93
|
+
// Add category header as a folder
|
|
94
|
+
html += ` <DT><H3>${categoryName}</H3>\n`
|
|
95
|
+
html += ` <DL><p>\n`
|
|
96
|
+
|
|
97
|
+
for (const bookmark of bookmarks) {
|
|
98
|
+
html += ` <DT><A HREF="${bookmark.url}">${bookmark.name} | ${bookmark.description}</A>\n`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
html += ` </DL><p>\n`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Close the structure
|
|
105
|
+
html += ` </DL><p>
|
|
106
|
+
</DL><p>
|
|
107
|
+
`
|
|
108
|
+
|
|
109
|
+
return html
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the path to the bookmarks file
|
|
114
|
+
*/
|
|
115
|
+
export function getBookmarksPath(): string {
|
|
116
|
+
return join(homedir(), ".easiarr", "bookmarks.html")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Save bookmarks HTML file
|
|
121
|
+
*/
|
|
122
|
+
export async function saveBookmarks(config: EasiarrConfig, useLocalUrls = false): Promise<string> {
|
|
123
|
+
const html = generateBookmarksHtml(config, useLocalUrls)
|
|
124
|
+
const path = getBookmarksPath()
|
|
125
|
+
await writeFile(path, html, "utf-8")
|
|
126
|
+
return path
|
|
127
|
+
}
|
|
@@ -134,18 +134,18 @@ export async function generateServicesYaml(config: EasiarrConfig): Promise<strin
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// Build YAML output
|
|
137
|
-
let yaml = "---\n# Auto-generated by
|
|
137
|
+
let yaml = "---\n# Auto-generated by easiarr\n# https://github.com/muhammedaksam/easiarr\n\n"
|
|
138
138
|
|
|
139
|
-
// Add
|
|
140
|
-
yaml += `-
|
|
141
|
-
// Installed version from local easiarr
|
|
139
|
+
// Add easiarr info section with two widgets - one for installed, one for latest
|
|
140
|
+
yaml += `- easiarr:\n`
|
|
141
|
+
// Installed version from local easiarr container
|
|
142
142
|
yaml += ` - Installed:\n`
|
|
143
143
|
yaml += ` href: https://github.com/muhammedaksam/easiarr\n`
|
|
144
144
|
yaml += ` icon: mdi-docker\n`
|
|
145
145
|
yaml += ` description: Your current version\n`
|
|
146
146
|
yaml += ` widget:\n`
|
|
147
147
|
yaml += ` type: customapi\n`
|
|
148
|
-
yaml += ` url: http://easiarr
|
|
148
|
+
yaml += ` url: http://easiarr:8080/config.json\n`
|
|
149
149
|
yaml += ` refreshInterval: 3600000\n` // 1 hour
|
|
150
150
|
yaml += ` mappings:\n`
|
|
151
151
|
yaml += ` - field: version\n`
|
|
@@ -237,10 +237,10 @@ export async function generateServicesYaml(config: EasiarrConfig): Promise<strin
|
|
|
237
237
|
*/
|
|
238
238
|
export function generateSettingsYaml(): string {
|
|
239
239
|
return `---
|
|
240
|
-
# Auto-generated by
|
|
240
|
+
# Auto-generated by easiarr
|
|
241
241
|
# For configuration options: https://gethomepage.dev/configs/settings/
|
|
242
242
|
|
|
243
|
-
title:
|
|
243
|
+
title: easiarr Dashboard
|
|
244
244
|
|
|
245
245
|
# Background: "Close-up Photography of Leaves With Droplets"
|
|
246
246
|
# Photo by Sohail Nachiti: https://www.pexels.com/photo/close-up-photography-of-leaves-with-droplets-807598/
|
package/src/config/schema.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* easiarr Configuration Schema
|
|
3
3
|
* TypeScript interfaces for configuration management
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -120,7 +120,7 @@ export type AppId =
|
|
|
120
120
|
| "guacamole"
|
|
121
121
|
| "guacd"
|
|
122
122
|
| "ddns-updater"
|
|
123
|
-
| "easiarr
|
|
123
|
+
| "easiarr"
|
|
124
124
|
// VPN
|
|
125
125
|
| "gluetun"
|
|
126
126
|
// Monitoring & Infra
|
package/src/index.ts
CHANGED
|
@@ -301,7 +301,7 @@ export class JellyfinSetup extends BoxRenderable {
|
|
|
301
301
|
// Generate API key
|
|
302
302
|
this.results[1].status = "configuring"
|
|
303
303
|
this.refreshContent()
|
|
304
|
-
const apiKey = await this.jellyfinClient.createApiKey("
|
|
304
|
+
const apiKey = await this.jellyfinClient.createApiKey("easiarr")
|
|
305
305
|
if (!apiKey) {
|
|
306
306
|
throw new Error("Failed to create API key")
|
|
307
307
|
}
|
|
@@ -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"
|
|
@@ -138,6 +140,34 @@ export class MainMenu {
|
|
|
138
140
|
action: () => this.showScreen(JellyseerrSetup),
|
|
139
141
|
})
|
|
140
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
|
+
}
|
|
141
171
|
|
|
142
172
|
items.push({
|
|
143
173
|
name: "❌ Exit",
|
|
@@ -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 })
|