From 408df0e2f6d322715ae5361da0c3cb4b33b1329e Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 11:41:09 +0200 Subject: [PATCH 01/37] feat: send logs feature that send logs to a distant service. Full integration in ui + expose logger method to frontEnd (#53) --- .env.example | 2 + .gitignore | 3 + README.md | 22 ++++++- app/ipcHandlers.js | 17 +++++- app/sendReport.js | 54 +++++++++++++++++ main.js | 5 ++ package-lock.json | 1 + package.json | 1 + preload.js | 29 +++++++++ public/index.html | 3 +- public/script/reportBug.js | 121 +++++++++++++++++++++++++++++++++++++ 11 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 app/sendReport.js create mode 100644 public/script/reportBug.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b5b9b88 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +BUG_REPORT_URL= +BUG_REPORT_API_KEY= diff --git a/.gitignore b/.gitignore index 81ae903..3990917 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /out /srpm-out +# .env +*.env + # Config JSON File. Create it and fill it if you want to have dev only settings config/config.dev.json diff --git a/README.md b/README.md index 6944a2e..200fdcb 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Freedom Loader can be configured either through the settings panel in the UI or | Option | Type | Default | Description | |------------------------------|---------|----------|------------------------------------------------------------| | `autoUpdate` | boolean | `true` | Enable automatic application updates | -| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | +| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `customTopBar` | boolean | `true` | Use custom application top bar | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | @@ -367,13 +367,29 @@ Head over to the [Freedom Loader Workshop repository](https://github.com/MasterA ### Bug Reports -Use the GitHub Issues system and include: +You can report bugs either using the built-in application system or via the GitHub Issues system. + +#### Using the Built-in Report Tool + +1. Open Freedom Loader. +2. Trigger the bug report interface. +3. Fill in the title and description of the issue. +4. Choose whether to include today's logs automatically. +5. Submit the report. + +> [!NOTE] +> If you need to attach extra context like screenshots or screen recordings, it is recommended to use the GitHub Issues +system instead. + +#### Using GitHub Issues + +Open a new issue on the GitHub repository and include: - Clear description of the issue - Steps to reproduce - Expected vs actual behavior - Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/` -- Screenshots if applicable +- Screenshots or recordings if applicable ### Feature Requests diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 1f3510a..529437c 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -19,6 +19,7 @@ const config = require("../config"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { userThemesPath } = require("../server/helpers/path.helpers"); const { createSystemTray, destroyTray } = require("./tray"); +const {sendReport} = require("./sendReport"); /** * Security whitelist for feature flags that can be modified at runtime. @@ -180,8 +181,13 @@ function registerIpcHandlers(getMainWindow) { */ ipcMain.on("open-config", () => shell.openPath(configFolderPath)); - - + /** + * Front end logger + */ + ipcMain.on("log-error", (_, message) => logger.error(`[Frontend] ${message}`)); + ipcMain.on("log-info", (_, message) => logger.info(`[Frontend] ${message}`)); + ipcMain.on("log-warn", (_, message) => logger.warn(`[Frontend] ${message}`)); + /** * Retrieves available themes from filesystem. */ @@ -199,6 +205,13 @@ function registerIpcHandlers(getMainWindow) { return await reloadThemes(); }); + /** + * Send bug report + */ + ipcMain.handle("send-report", async (_, params) => { + return await sendReport(params); + }) + /** * Updates a runtime feature flag and persists it to disk. * diff --git a/app/sendReport.js b/app/sendReport.js new file mode 100644 index 0000000..48b50da --- /dev/null +++ b/app/sendReport.js @@ -0,0 +1,54 @@ +const fs = require("fs"); +const path = require("path"); +const {logger, logDir} = require("../server/logger"); + +async function sendReport(params) { + try { + const {title, description, includeLogs} = params; + + if (!title || !description) { + throw new Error("Title and description are required."); + } + + const formData = new FormData(); + formData.append("title", title); + formData.append("context", description); + + if (includeLogs === "yes" && logDir) { + // YYYY-MM-DD + const today = new Date().toISOString().split("T")[0]; + + const logFilePath = path.join(logDir, `LOGS-${today}.log`); + + if (fs.existsSync(logFilePath)) { + const logFileContent = fs.readFileSync(logFilePath); + const blob = new Blob([logFileContent], {type: "text/plain"}); + formData.append("file", blob, `LOGS-${today}.log`); + logger.info("Logs attached to bug report."); + } else { + logger.warn(`Log file not found for today: ${logFilePath}`); + } + } + + const response = await fetch(process.env.BUG_REPORT_URL, { + method: "POST", + headers: { + "X-Api-Key": process.env.BUG_REPORT_API_KEY + }, + body: formData + }); + + if (!response.ok) { + logger.error(`Server returned an error: ${response.status}`); + throw new Error(`Server responded with status ${response.status}`); + } + + logger.info("Bug report successfully sent."); + return true; + } catch (err) { + logger.error(`Failed to send bug report: ${err.message}`); + throw err; + } +} + +module.exports = {sendReport}; \ No newline at end of file diff --git a/main.js b/main.js index 46f56b4..3f063b8 100644 --- a/main.js +++ b/main.js @@ -78,6 +78,11 @@ const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder const {createSystemTray, destroyTray} = require("./app/tray"); const { stopServer } = require("./server/server"); +/** + * Expose .env in process.env + */ +require("dotenv").config(); + /** * Global flag indicating if the application is intentionally shutting down. * Used across the app to bypass the "minimize to tray on close" behavior. diff --git a/package-lock.json b/package-lock.json index ec010cf..661bdd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", + "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", diff --git a/package.json b/package.json index 56b35e4..66ec5ac 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", + "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", diff --git a/preload.js b/preload.js index aee2ac8..b3c3637 100644 --- a/preload.js +++ b/preload.js @@ -9,6 +9,35 @@ const { contextBridge, ipcRenderer } = require("electron"); */ contextBridge.exposeInMainWorld("electronAPI", { + /** + * Sends error log message. + * + * @param {...any} args - Values to log + */ + logError: (...args) => ipcRenderer.send("log-error", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Sends info log message. + * + * @param {...any} args - Values to log + */ + logInfo: (...args) => ipcRenderer.send("log-info", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Sends warning log message. + * + * @param {...any} args - Values to log + */ + logWarn: (...args) => ipcRenderer.send("log-warn", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), + + /** + * Send Bug Report + * + * @param params + * @returns {Promise} + */ + sendReport: (params) => ipcRenderer.invoke('send-report', params), + /** * Return process.platform to renderer */ diff --git a/public/index.html b/public/index.html index 8502f85..b3c2b33 100644 --- a/public/index.html +++ b/public/index.html @@ -21,6 +21,7 @@
+ @@ -295,7 +296,6 @@ -
@@ -314,6 +314,7 @@ + diff --git a/public/script/reportBug.js b/public/script/reportBug.js new file mode 100644 index 0000000..e40079e --- /dev/null +++ b/public/script/reportBug.js @@ -0,0 +1,121 @@ +class BugReportModal { + constructor() { + this.modal = null; + this.form = null; + this.init(); + } + + init() { + this.modal = document.createElement("div"); + this.modal.className = "bug-modal-overlay"; + this.modal.style.cssText = "display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); justify-content:center; align-items:center; z-index:99999; font-family:sans-serif;"; + + this.modal.innerHTML = ` +
+

Report a Bug

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ `; + + document.body.appendChild(this.modal); + this.form = this.modal.querySelector("#bugReportForm"); + + this.bindEvents(); + } + + bindEvents() { + const titleInput = this.modal.querySelector("#bugTitle"); + const descInput = this.modal.querySelector("#bugDescription"); + + titleInput.addEventListener("input", () => { + localStorage.setItem("draft_bug_title", titleInput.value); + }); + + descInput.addEventListener("input", () => { + localStorage.setItem("draft_bug_desc", descInput.value); + }); + + this.modal.querySelector("#bugCancelBtn").addEventListener("click", () => { + this.close(); + }); + + this.modal.addEventListener("click", (e) => { + if (e.target === this.modal) { + this.close(); + } + }); + + this.form.addEventListener("submit", async (e) => { + e.preventDefault(); + const data = { + title: titleInput.value, + description: descInput.value, + includeLogs: this.modal.querySelector("#bugIncludeLogs").value + }; + + window.electronAPI.logInfo("Submitting:", data); + + try { + const success = await window.electronAPI.sendReport(data); + + if (success) { + localStorage.removeItem("draft_bug_title"); + localStorage.removeItem("draft_bug_desc"); + this.form.reset(); + this.close(); + window.showSuccess("Bug report sent successfully!"); + } + } catch (err) { + window.showError("Failed to send bug report."); + window.electronAPI.logError("Failed to send bug report:", err.message); + } + }); + } + + loadState() { + const savedTitle = localStorage.getItem("draft_bug_title"); + const savedDesc = localStorage.getItem("draft_bug_desc"); + + if (savedTitle) { + this.modal.querySelector("#bugTitle").value = savedTitle; + } + if (savedDesc) { + this.modal.querySelector("#bugDescription").value = savedDesc; + } + } + + open() { + this.loadState(); + this.modal.style.display = "flex"; + } + + close() { + this.modal.style.display = "none"; + } +} + +window.bugModal = new BugReportModal(); + +document.getElementById("report-bug-btn").addEventListener("click", () => { + window.bugModal.open(); +}); \ No newline at end of file From 35ffd7077e49a4b939c2237e38ed3f30f1745a7c Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 11:44:37 +0200 Subject: [PATCH 02/37] fix: onClick attribute on Report a Bug (#53) --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index b3c2b33..2d1a1bc 100644 --- a/public/index.html +++ b/public/index.html @@ -21,7 +21,7 @@
- + From 8af5a4a38320fa2e4cae79b3cd9c5b295fa20b11 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 11:56:03 +0200 Subject: [PATCH 03/37] update: bump app version to 1.6.4-preview --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66ec5ac..b3a752d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "freedom-loader", "desktopName": "com.masteracnolo.freedomloader", "productName": "Freedom Loader", - "version": "1.6.3", + "version": "1.6.4-preview", "author": "MasterAcnolo ", "description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/", From 58b6637eeb54cba25896d1d87746171dd101e194 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 12:06:33 +0200 Subject: [PATCH 04/37] feat: implement linter with config file --- app/discordRPC.js | 2 +- app/ipcHandlers.js | 6 +- app/tray.js | 2 +- eslint.config.js | 40 ++ main.js | 8 +- package-lock.json | 1077 +++++++++++++++++++++++++++++++++++- package.json | 6 + public/script/fetchinfo.js | 2 +- 8 files changed, 1127 insertions(+), 16 deletions(-) create mode 100644 eslint.config.js diff --git a/app/discordRPC.js b/app/discordRPC.js index 101b8ff..15d3da8 100644 --- a/app/discordRPC.js +++ b/app/discordRPC.js @@ -97,7 +97,7 @@ function startRPC() { details: `Open Source Download Tools - ${config.version}`, state: "masteracnolo.github.io/FreedomLoader", }; - rpc.clearActivity() + rpc.clearActivity(); rpc.setActivity(presence); }); diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 529437c..854ca09 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -118,11 +118,11 @@ function registerIpcHandlers(getMainWindow) { ipcMain.on("window-minimize", () => { // Minimize to tray if (configFeatures.systemTray) { - getMainWindow()?.hide() + getMainWindow()?.hide(); logger.info("Window Minimized in SystemTray (Using Minimize Button)"); } else { // Native minimize - getMainWindow()?.minimize() + getMainWindow()?.minimize(); logger.info("Window minimized normally"); } } @@ -210,7 +210,7 @@ function registerIpcHandlers(getMainWindow) { */ ipcMain.handle("send-report", async (_, params) => { return await sendReport(params); - }) + }); /** * Updates a runtime feature flag and persists it to disk. diff --git a/app/tray.js b/app/tray.js index 5ab9b57..19dca34 100644 --- a/app/tray.js +++ b/app/tray.js @@ -95,7 +95,7 @@ function createSystemTray(devMode) { } else { window.show(); window.focus(); - logger.info("Window Restored from SystemTray (Double Click on Tray Icon)") + logger.info("Window Restored from SystemTray (Double Click on Tray Icon)"); } }); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0bc67c9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +const js = require("@eslint/js"); +const globals = require("globals"); + +module.exports = [ + js.configs.recommended, + + {ignores: ["node_modules/**", "srpm-out/**", "dist/**", "test/**"]}, + + { + languageOptions: { + ecmaVersion: "latest", + sourceType: "commonjs", + }, + rules: { + "no-unused-vars": ["error", {argsIgnorePattern: "^_"}], + "no-console": "off", + "semi": ["error", "always"] + }, + + + }, + { + files: ["app/**", "server/**", "main.js"], + languageOptions: { + globals: {...globals.node} + } + }, + { + files: ["public/**"], + languageOptions: { + globals: {...globals.browser} + } + }, + { + files: ["preload.js"], + languageOptions: { + globals: {...globals.node, ...globals.browser} + } + } +]; \ No newline at end of file diff --git a/main.js b/main.js index 3f063b8..dd3b873 100644 --- a/main.js +++ b/main.js @@ -48,9 +48,9 @@ const { configFeatures, devMode} = require("./config"); */ if (!configFeatures.enableHardwareAcceleration){ app.disableHardwareAcceleration(); - logger.info("Disabled Hardware Acceleration") + logger.info("Disabled Hardware Acceleration"); } else { - logger.info("Enable Hardware Acceleration") + logger.info("Enable Hardware Acceleration"); } if(devMode){ @@ -58,8 +58,8 @@ if(devMode){ * Start devTron extensions - @see https://github.com/electron/devtron */ const { devtron } = require('@electron/devtron'); - devtron.install(); - logger.info("Loaded DevTron Extension") + devtron.install(); + logger.info("Loaded DevTron Extension"); } /** diff --git a/package-lock.json b/package-lock.json index 661bdd4..ffb6fa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "freedom-loader", - "version": "1.6.3-preview", + "version": "1.6.4-preview", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freedom-loader", - "version": "1.6.3-preview", + "version": "1.6.4-preview", "license": "GPL-3.0-only", "dependencies": { "chalk": "^4.1.2", @@ -23,10 +23,14 @@ }, "devDependencies": { "@electron/devtron": "^2.1.1", + "@eslint/js": "^10.0.1", "@jest/globals": "^30.4.1", "@playwright/test": "^1.62.1", "electron": "^44.3.0", "electron-builder": "^26.15.3", + "eslint": "^10.10.0", + "eslint-formatter-pretty": "^7.1.0", + "globals": "^17.12.0", "jest": "^30.4.2", "nodemon": "^3.1.14", "playwright": "^1.62.1" @@ -575,6 +579,67 @@ "dev": true, "license": "MIT" }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -954,6 +1019,134 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@fontsource-variable/roboto": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource-variable/roboto/-/roboto-5.3.0.tgz", @@ -974,6 +1167,72 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1587,6 +1846,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -1976,6 +2242,31 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -2020,6 +2311,13 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -2447,6 +2745,29 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/ag-charts-types": { "version": "12.3.1", "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-12.3.1.tgz", @@ -2694,6 +3015,19 @@ "node": ">=8" } }, + "node_modules/app-builder-lib/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/app-builder-lib/node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3152,6 +3486,20 @@ "node": ">=6.0.0" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3197,6 +3545,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -3655,6 +4013,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3829,10 +4194,9 @@ } }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3857,6 +4221,19 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4156,6 +4533,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -4253,6 +4643,319 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz", + "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-formatter-pretty": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-7.1.0.tgz", + "integrity": "sha512-iyPrgpwKC3MMM75Wxn0VouD89HolpG+BL95NOxgwOWO0R+Omapo7gFX2xcGVsUDS7KiXLtDuynLbNbOARSE7YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "^9.6.1", + "ansi-escapes": "^7.1.0", + "chalk": "^5.6.2", + "eslint-rule-docs": "^1.1.235", + "log-symbols": "^7.0.1", + "plur": "^5.1.0", + "string-width": "^8.1.0", + "supports-hyperlinks": "^4.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/eslint-rule-docs": { + "version": "1.1.235", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", + "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4267,6 +4970,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4411,6 +5160,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", @@ -4462,6 +5218,16 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, "node_modules/file-stream-rotator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", @@ -4566,6 +5332,25 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -4725,6 +5510,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4870,6 +5668,19 @@ "node": ">=10.0" } }, + "node_modules/globals": { + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -4983,6 +5794,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -4995,6 +5819,13 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -5110,6 +5941,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -5189,6 +6030,16 @@ "node": ">= 0.10" } }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5280,6 +6131,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -6188,6 +7052,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -6265,6 +7136,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -6321,6 +7206,23 @@ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -6977,6 +7879,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -7298,6 +8218,22 @@ "node": ">=10.4.0" } }, + "node_modules/plur": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "irregular-plurals": "^3.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/postject": { "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", @@ -7328,6 +8264,16 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", @@ -7452,6 +8398,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -7489,6 +8445,26 @@ "node": ">=16.0.0" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", @@ -8350,6 +9326,49 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz", + "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/synckit": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", @@ -8605,6 +9624,19 @@ "dev": true, "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -8800,6 +9832,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/use-composed-ref": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", @@ -9032,6 +10074,16 @@ "node": ">= 6" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -9191,6 +10243,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index b3a752d..5fcd40f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "release:dry-run": "bash scripts/release.sh --dry-run", "test": "jest", "test:unit": "jest test/unit", + "lint": "eslint . --format pretty", + "lint:fix": "eslint . --fix --format pretty", "update": "npm update" }, "dependencies": { @@ -49,10 +51,14 @@ }, "devDependencies": { "@electron/devtron": "^2.1.1", + "@eslint/js": "^10.0.1", "@jest/globals": "^30.4.1", "@playwright/test": "^1.62.1", "electron": "^44.3.0", "electron-builder": "^26.15.3", + "eslint": "^10.10.0", + "eslint-formatter-pretty": "^7.1.0", + "globals": "^17.12.0", "jest": "^30.4.2", "nodemon": "^3.1.14", "playwright": "^1.62.1" diff --git a/public/script/fetchinfo.js b/public/script/fetchinfo.js index 2d79d67..6893ea1 100644 --- a/public/script/fetchinfo.js +++ b/public/script/fetchinfo.js @@ -269,7 +269,7 @@ async function init() { infoDiv.classList.add("visible"); }); - }) + }); } From 2c0f5b315bcbb578fd94d0c627dafe75b936ebf5 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 13 Sep 2026 15:53:21 +0200 Subject: [PATCH 05/37] refactor: toast notification style --- public/index.html | 1 + public/script/toast.js | 12 +-- public/styles/components/toast.css | 126 +++++++++++++++++------------ 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/public/index.html b/public/index.html index 2d1a1bc..aec5bf2 100644 --- a/public/index.html +++ b/public/index.html @@ -13,6 +13,7 @@ + diff --git a/public/script/toast.js b/public/script/toast.js index 56afa60..d4b6406 100644 --- a/public/script/toast.js +++ b/public/script/toast.js @@ -17,18 +17,20 @@ window.showToast = function (message, type = "info", duration = 4000) { toast.classList.add("toast", type); const icons = { - success: "✓", - error: "✕", - warning: "⚠", - info: "ℹ" + success: "check_circle", + error: "error", + warning: "warning", + info: "info" }; const icon = icons[type] || icons.info; + toast.style.setProperty("--toast-duration", `${duration}ms`); toast.innerHTML = ` - ${icon} + ${icon} ${message} +
`; const closeBtn = toast.querySelector(".toast-close"); diff --git a/public/styles/components/toast.css b/public/styles/components/toast.css index 5972c0b..efde624 100644 --- a/public/styles/components/toast.css +++ b/public/styles/components/toast.css @@ -5,73 +5,70 @@ right: 20px; display: flex; flex-direction: column; - gap: 8px; + gap: 12px; z-index: 9999; pointer-events: none; } /* Toast */ .toast { + position: relative; display: flex; align-items: center; - gap: 10px; - padding: 10px 14px; - background-color: rgba(50, 50, 50, 0.9); - border-radius: 6px; - color: #ffffff; - font-size: 0.9rem; - max-width: 220px; - min-width: 100px; - animation: slideIn 0.25s ease-out; + gap: 12px; + padding: 14px 16px; + background-color: rgba(30, 30, 30, 0.9); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + color: #f3f4f6; + font-family: inherit; + font-size: 0.875rem; + max-width: 320px; + min-width: 220px; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.2); + animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; pointer-events: auto; - backdrop-filter: blur(8px); -} - -.toast.success { - background-color: rgba(40, 167, 69, 0.15); -} - -.toast.error { - background-color: rgba(220, 53, 69, 0.15); -} - -.toast.warning { - background-color: rgba(255, 193, 7, 0.15); -} - -.toast.info { - background-color: rgba(23, 162, 184, 0.15); + overflow: hidden; } /* Toast Icon */ .toast-icon { flex-shrink: 0; - font-size: 18px; + font-size: 20px; display: flex; align-items: center; justify-content: center; } +.toast-icon svg { + width: 100%; + height: 100%; +} + .toast.success .toast-icon { - color: #28a745; + color: #22c55e; } .toast.error .toast-icon { - color: #dc3545; + color: #ef4444; } .toast.warning .toast-icon { - color: #ffc107; + color: #f59e0b; } .toast.info .toast-icon { - color: #17a2b8; + color: #0ea5e9; } /* Toast Message */ .toast-message { flex: 1; - font-weight: 500; + font-weight: 400; + line-height: 1.4; + word-break: break-word; } /* Toast Close Button */ @@ -79,58 +76,87 @@ flex-shrink: 0; background: none; border: none; - color: currentColor; + color: #9ca3af; cursor: pointer; - font-size: 18px; - padding: 0; + font-size: 16px; + width: 24px; + height: 24px; + border-radius: 4px; display: flex; align-items: center; justify-content: center; - opacity: 0.5; - transition: opacity 0.2s ease; + transition: background-color 0.15s ease, color 0.15s ease; } .toast-close:hover { - opacity: 1; + background-color: rgba(255, 255, 255, 0.1); + color: #ffffff; +} + +.toast-progress { + position: absolute; + bottom: 0; + left: 0; + height: 3px; + width: 100%; + background-color: rgba(255, 255, 255, 0.1); + animation: progress var(--toast-duration, 4000ms) linear forwards; +} + +.toast.success .toast-progress { + background-color: #22c55e; +} + +.toast.error .toast-progress { + background-color: #ef4444; } /* Animations */ @keyframes slideIn { from { - transform: translateX(350px); + transform: translateY(-20px); opacity: 0; } to { - transform: translateX(0); + transform: translateY(0); opacity: 1; } } @keyframes slideOut { from { - transform: translateX(0); + transform: translateY(0); opacity: 1; } to { - transform: translateX(350px); + transform: translateY(-20px); opacity: 0; } } -.toast.removing { - animation: slideOut 0.25s ease-out; +@keyframes progress { + from { + width: 100%; + } + to { + width: 0%; + } +} + +.toast.removing { + animation: slideOut 0.25s ease-in forwards; } -/* Responsive */ @media (max-width: 480px) { #toast-container { - bottom: 12px; - right: 12px; - left: 12px; + top: auto; + bottom: 16px; + right: 16px; + left: 16px; } .toast { max-width: none; min-width: auto; } -} +} \ No newline at end of file From 4a64a40226f57838827a3bdbeeb640c332fac505 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Wed, 16 Sep 2026 20:17:42 +0200 Subject: [PATCH 06/37] fix(front): settings menu transition --- public/script/settingsPanel.js | 5 ++--- public/styles/layout/settingsPanel.css | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/public/script/settingsPanel.js b/public/script/settingsPanel.js index 528679d..37c435e 100644 --- a/public/script/settingsPanel.js +++ b/public/script/settingsPanel.js @@ -16,8 +16,7 @@ let isOpen = false; * Toggles visibility of the settings panel when clicking the settings button. */ settingsButton.addEventListener("click", () => { - const currentlyOpen = settingsPanel.style.display === "block"; - settingsPanel.style.display = currentlyOpen ? "none" : "block"; + settingsPanel.classList.toggle("open"); }); /** @@ -25,7 +24,7 @@ settingsButton.addEventListener("click", () => { */ function closePanel() { isOpen = false; - settingsPanel.style.display = "none"; + settingsPanel.classList.remove("open"); } /** diff --git a/public/styles/layout/settingsPanel.css b/public/styles/layout/settingsPanel.css index 32b81e0..1c26f09 100644 --- a/public/styles/layout/settingsPanel.css +++ b/public/styles/layout/settingsPanel.css @@ -45,11 +45,22 @@ border-radius: 12px; padding: 20px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); - display: none; overflow-y: auto; z-index: 9999; - animation: slideIn 0.2s ease-out; + /*animation: slideIn 0.2s ease-out; */ color: var(--settings-text-color); + + display: block; + opacity: 0; + pointer-events: none; + transform: translateX(20px); + transition: opacity 0.2s ease-out, transform 0.2s ease-out; +} + +.settings-panel.open { + opacity: 1; + pointer-events: auto; + transform: translateX(0); } .settings-panel::-webkit-scrollbar { From b319a1e445f3721181b4e4afbfdef5abc928b47b Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Wed, 16 Sep 2026 22:16:59 +0200 Subject: [PATCH 07/37] fix(ipc): send report IPC API --- preload.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/preload.js b/preload.js index b3c3637..7bc2b2f 100644 --- a/preload.js +++ b/preload.js @@ -30,14 +30,6 @@ contextBridge.exposeInMainWorld("electronAPI", { */ logWarn: (...args) => ipcRenderer.send("log-warn", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")), - /** - * Send Bug Report - * - * @param params - * @returns {Promise} - */ - sendReport: (params) => ipcRenderer.invoke('send-report', params), - /** * Return process.platform to renderer */ @@ -155,4 +147,12 @@ contextBridge.exposeInMainWorld("topbarAPI", { * Opens configuration/settings panel. */ openConfig: () => ipcRenderer.send("open-config"), + + /** + * Send Bug Report + * + * @param params + * @returns {Promise} + */ + sendReport: (params) => ipcRenderer.invoke('send-report', params), }); \ No newline at end of file From 9975b2b583bb322736300d8e3a6e61a26355648a Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Fri, 25 Sep 2026 21:08:18 +0200 Subject: [PATCH 08/37] refactor(update): add an UI indicator in frontend disable OS native pop up menu and fix autoupdate crash on rpm and snap #50 --- app/autoUpdater.js | 89 ++++++++-------- app/ipcHandlers.js | 125 +++++++++++++---------- preload.js | 64 ++++++++++-- public/index.html | 29 +++++- public/script/autoUpdate.js | 81 +++++++++++++++ public/styles/components/autoUpdate.css | 128 ++++++++++++++++++++++++ public/styles/styles.css | 1 + 7 files changed, 409 insertions(+), 108 deletions(-) create mode 100644 public/script/autoUpdate.js create mode 100644 public/styles/components/autoUpdate.css diff --git a/app/autoUpdater.js b/app/autoUpdater.js index bbb800a..bb06b74 100644 --- a/app/autoUpdater.js +++ b/app/autoUpdater.js @@ -9,7 +9,6 @@ */ const { autoUpdater } = require("electron-updater"); -const { dialog } = require("electron"); const { logger } = require("../server/logger"); /** @@ -21,6 +20,26 @@ const { logger } = require("../server/logger"); autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = false; +let updateAvailable = false; + +/** + * Determines how the update should be handled based on the platform + * @returns {"auto" | "snap" | "flatpak" | "linux-native"} + */ +function getUpdateEnvironment() { + if ( + process.env.APPIMAGE || + process.platform === "win32" || + process.platform === "darwin" + ) { + return "auto"; + } + if (process.env.SNAP) return "snap"; + if (process.env.FLATPAK_ID) return "flatpak"; + + return "linux-native"; +} + /** * Initializes application auto-update lifecycle. * @@ -32,7 +51,6 @@ autoUpdater.autoInstallOnAppQuit = false; * @param {BrowserWindow} mainWindow - main Electron window instance */ function initAutoUpdater(mainWindow) { - /** * Triggered when a new version is detected. * Prompts user to install or defer update. @@ -40,23 +58,12 @@ function initAutoUpdater(mainWindow) { autoUpdater.on("update-available", async (info) => { logger.info(`Update available: ${info.version}`); - const { response } = await dialog.showMessageBox(mainWindow, { - type: "info", - title: "Update Available", - message: `Version ${info.version} is available.`, - detail: "Would you like to download and install it now?", - buttons: ["Install Update", "Maybe Later"], - defaultId: 0, - cancelId: 1, + mainWindow?.webContents.send("update-available", { + version: info.version, + environment: getUpdateEnvironment(), }); + updateAvailable = true; - if (response === 0) { - await autoUpdater.downloadUpdate(); - } else { - mainWindow?.webContents.executeJavaScript( - `window.showUpdateBadge && window.showUpdateBadge("${info.version}")` - ); - } }); /** @@ -66,8 +73,10 @@ function initAutoUpdater(mainWindow) { autoUpdater.on("download-progress", (progress) => { logger.info(`Download progress: ${Math.round(progress.percent)}%`); mainWindow?.webContents.send("update-progress", { - percent: Math.round(progress.percent), - speed: progress.bytesPerSecond, + percent: progress.percent, + bytesPerSecond: progress.bytesPerSecond, + transferred: progress.transferred, + total: progress.total, }); }); @@ -78,17 +87,9 @@ function initAutoUpdater(mainWindow) { autoUpdater.on("update-downloaded", async (info) => { logger.info(`Update downloaded: ${info.version}`); - const { response } = await dialog.showMessageBox(mainWindow, { - type: "info", - title: "Update Ready", - message: `Version ${info.version} has been downloaded.`, - detail: "The application will restart to apply the update.", - buttons: ["Install Now", "Later"], - defaultId: 0, - cancelId: 1, - }); + mainWindow?.webContents.send("update-downloaded", info); + - if (response === 0) autoUpdater.quitAndInstall(); }); /** @@ -102,14 +103,15 @@ function initAutoUpdater(mainWindow) { * If no update is available, I put this because there is no Linux version before 1.6.0 * @type {boolean} */ - const isNoUpdateAvailable = /404/.test(msg) || /Cannot find latest.*\.yml/i.test(msg); + const isNoUpdateAvailable = + /404/.test(msg) || /Cannot find latest.*\.yml/i.test(msg); if (isNoUpdateAvailable) { logger.warn("Auto update: no update metadata found (probably no previous release), ignoring", msg); return; } - logger.error("Auto update error:", msg); + logger.error("Auto update error:", err); }); checkForUpdates(); @@ -120,19 +122,7 @@ function initAutoUpdater(mainWindow) { * Separated from init for reusability and testability. */ async function checkForUpdates() { - - /** - * Prevents electron-updater from running inside sandboxed environments. - * Snap and Flatpak lock the file system in read-only mode, causing the updater to crash. - * In these environments, updates are safely handled by snapd or the Flatpak runtime. - * Standalone Linux packages (.deb, .rpm, AppImage) will bypass this and update normally. - */ - if (process.env.SNAP || process.env.FLATPAK_ID) { - logger.info("Running as Snap or Flatpak. Updates are managed by the OS store. Skipping electron-updater."); - return; - } - - if (!require("electron").app.isPackaged) return ; + if (!require("electron").app.isPackaged) return; try { await autoUpdater.checkForUpdates(); @@ -160,4 +150,13 @@ function installUpdate() { autoUpdater.quitAndInstall(); } -module.exports = { initAutoUpdater, downloadUpdate, installUpdate }; \ No newline at end of file +function isUpdateAvailable() { + return updateAvailable; +} + +module.exports = { + initAutoUpdater, + downloadUpdate, + installUpdate, + isUpdateAvailable, +}; diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 854ca09..15dd06b 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -13,13 +13,14 @@ const { ipcMain, dialog, shell } = require("electron"); const fs = require("fs"); const { logger, logDir } = require("../server/logger"); -const {configFeatures, featuresPath, devMode} = require("../config"); +const { configFeatures, featuresPath, devMode } = require("../config"); const { getThemes, reloadThemes } = require("./themeManager"); const config = require("../config"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { userThemesPath } = require("../server/helpers/path.helpers"); const { createSystemTray, destroyTray } = require("./tray"); -const {sendReport} = require("./sendReport"); +const { sendReport } = require("./sendReport"); +const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater"); /** * Security whitelist for feature flags that can be modified at runtime. @@ -65,7 +66,6 @@ const themeFolderPath = userThemesPath; * @param {Function} getMainWindow - Function returning current BrowserWindow instance */ function registerIpcHandlers(getMainWindow) { - /** * Returns application version from config. */ @@ -116,17 +116,16 @@ function registerIpcHandlers(getMainWindow) { * Window minimize request from renderer. */ ipcMain.on("window-minimize", () => { - // Minimize to tray - if (configFeatures.systemTray) { - getMainWindow()?.hide(); - logger.info("Window Minimized in SystemTray (Using Minimize Button)"); - } else { - // Native minimize - getMainWindow()?.minimize(); - logger.info("Window minimized normally"); - } - } - ); + // Minimize to tray + if (configFeatures.systemTray) { + getMainWindow()?.hide(); + logger.info("Window Minimized in SystemTray (Using Minimize Button)"); + } else { + // Native minimize + getMainWindow()?.minimize(); + logger.info("Window minimized normally"); + } + }); /** * Toggles maximize/unmaximize state of main window. @@ -142,7 +141,6 @@ function registerIpcHandlers(getMainWindow) { */ ipcMain.on("window-close", () => getMainWindow()?.close()); - /** * Opens Chromium DevTools in detached mode. */ @@ -153,7 +151,7 @@ function registerIpcHandlers(getMainWindow) { /** * Opens application logs directory in system file explorer. */ - ipcMain.on("open-logs", () => logDir && shell.openPath(logDir)); + ipcMain.on("open-logs", () => logDir && shell.openPath(logDir)); /** * Opens external website in default browser. @@ -212,6 +210,24 @@ function registerIpcHandlers(getMainWindow) { return await sendReport(params); }); + ipcMain.handle("is-update-available", () => { + return isUpdateAvailable(); + }); + + ipcMain.handle("download-update", async () => { + return await downloadUpdate(); + }); + + ipcMain.handle("install-update", async () => { + return installUpdate(); + }); + + ipcMain.on("open-release-page", () => { + shell.openExternal( + "https://github.com/MasterAcnolo/Freedom-Loader/releases/latest", + ); + }); + /** * Updates a runtime feature flag and persists it to disk. * @@ -229,47 +245,46 @@ function registerIpcHandlers(getMainWindow) { * @returns {boolean} success state */ ipcMain.handle("set-feature", (event, { key, value }) => { - try { - if (!FEATURE_WHITELIST.has(key)) { - logger.warn(`Rejected feature (not whitelisted): ${key}`); - return false; - } + try { + if (!FEATURE_WHITELIST.has(key)) { + logger.warn(`Rejected feature (not whitelisted): ${key}`); + return false; + } - if (configFeatures[key] === value) { - return true; - } + if (configFeatures[key] === value) { + return true; + } - /** - * Dynamically intercepts and applies System Tray state changes at runtime. - * Instantiates or destroys the tray icon immediately to prevent window - * lifecycle desynchronization (e.g., minimizing a window to a non-existent tray). - */ - if (key === "systemTray") { - if (value === true) { - logger.info("System Tray enabled dynamically."); - createSystemTray(devMode); - } else { - logger.info("System Tray disabled dynamically."); - destroyTray(); - } - } - - configFeatures[key] = value; - - fs.writeFileSync( - configFolderPath, - JSON.stringify(configFeatures, null, 2), - "utf-8" - ); - - logger.info(`Feature updated: ${key} = ${value}`); - return true; - - } catch (err) { - logger.error(`set-feature failed (${key}): ${err.message}`); - return false; + /** + * Dynamically intercepts and applies System Tray state changes at runtime. + * Instantiates or destroys the tray icon immediately to prevent window + * lifecycle desynchronization (e.g., minimizing a window to a non-existent tray). + */ + if (key === "systemTray") { + if (value === true) { + logger.info("System Tray enabled dynamically."); + createSystemTray(devMode); + } else { + logger.info("System Tray disabled dynamically."); + destroyTray(); } - }); + } + + configFeatures[key] = value; + + fs.writeFileSync( + configFolderPath, + JSON.stringify(configFeatures, null, 2), + "utf-8", + ); + + logger.info(`Feature updated: ${key} = ${value}`); + return true; + } catch (err) { + logger.error(`set-feature failed (${key}): ${err.message}`); + return false; + } + }); } -module.exports = { registerIpcHandlers }; \ No newline at end of file +module.exports = { registerIpcHandlers }; diff --git a/preload.js b/preload.js index 7bc2b2f..82363c8 100644 --- a/preload.js +++ b/preload.js @@ -8,7 +8,6 @@ const { contextBridge, ipcRenderer } = require("electron"); * All calls are explicitly whitelisted. */ contextBridge.exposeInMainWorld("electronAPI", { - /** * Sends error log message. * @@ -63,12 +62,11 @@ contextBridge.exposeInMainWorld("electronAPI", { * @param {string} key - Feature name * @param {any} value - Feature value */ - setFeature: (key, value) => - ipcRenderer.invoke("set-feature", { key, value }), + setFeature: (key, value) => ipcRenderer.invoke("set-feature", { key, value }), /** * Returns the current application version. - */ + */ getVersion: () => ipcRenderer.invoke("version"), /** @@ -88,8 +86,61 @@ contextBridge.exposeInMainWorld("electronAPI", { * Forces a reload of theme files (useful after modification/import). */ reloadThemes: () => ipcRenderer.invoke("reload-themes"), -}); + /* + * ========================================== + * Auto Updater IPC + * ========================================== + */ + + /** + * Checks if an update has already been detected by the main process. + * + * @returns {Promise} Resolves to true if an update is available. + */ + isUpdateAvailable: () => ipcRenderer.invoke("is-update-available"), + + /** + * Subscribes to the update available event pushed by the main process. + * + * @param {Function} callback - Function executed with update info when an update is found. + */ + onUpdateAvailable: (callback) => + ipcRenderer.on("update-available", (_, info) => callback(info)), + + /** + * Instructs the main process to start downloading the available update. + * + * @returns {Promise} + */ + downloadUpdate: () => ipcRenderer.invoke("download-update"), + + /** + * Instructs the main process to quit the application and install the downloaded update. + * + * @returns {Promise} + */ + installUpdate: () => ipcRenderer.invoke("install-update"), + + /** + * Subscribes to the download progress event. + * + * @param {Function} callback - Function executed with progress metrics (percent, bytesPerSecond, transferred, total). + */ + onDownloadProgress: (callback) => + ipcRenderer.on("update-progress", (_, progress) => callback(progress)), + + /** + * Subscribes to the update downloaded event. + * Triggered when the update file is fully downloaded and ready to install. + * + * @param {Function} callback - Function executed with update info. + */ + onUpdateDownloaded: (callback) => + ipcRenderer.on("update-downloaded", (_, info) => callback(info)), + + openReleasePage: () => ipcRenderer.send("open-release-page"), +}); /** * Exposes window control and developer utilities for the custom topbar UI. @@ -97,7 +148,6 @@ contextBridge.exposeInMainWorld("electronAPI", { * These methods forward commands to the Electron main process via IPC. */ contextBridge.exposeInMainWorld("topbarAPI", { - /** * Minimizes the application window. */ @@ -154,5 +204,5 @@ contextBridge.exposeInMainWorld("topbarAPI", { * @param params * @returns {Promise} */ - sendReport: (params) => ipcRenderer.invoke('send-report', params), + sendReport: (params) => ipcRenderer.invoke("send-report", params), }); \ No newline at end of file diff --git a/public/index.html b/public/index.html index aec5bf2..777d620 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,8 @@ - + + @@ -304,6 +305,31 @@

Because Why Not ?

+ @@ -316,6 +342,7 @@ + diff --git a/public/script/autoUpdate.js b/public/script/autoUpdate.js new file mode 100644 index 0000000..f8fbcad --- /dev/null +++ b/public/script/autoUpdate.js @@ -0,0 +1,81 @@ +async function initUpdateUI() { + const container = document.getElementById("updateAvailable"); + + const hasUpdate = await window.electronAPI.isUpdateAvailable(); + if (hasUpdate) { + setupAndShowUpdateUI(); + } + + window.electronAPI.onUpdateAvailable((info) => { + setupAndShowUpdateUI(info); + }); + + function setupAndShowUpdateUI(info) { + container.style.display = "block"; + + const btn = document.getElementById("btn-update"); + const icon = document.getElementById("update-icon"); + const title = document.getElementById("update-title"); + const sub = document.getElementById("update-sub"); + const progressWrap = document.getElementById("progress-wrap"); + const progressFill = document.getElementById("progress-fill"); + const progressPct = document.getElementById("progress-pct"); + const progressEta = document.getElementById("progress-eta"); + + if (info && info.version) { + sub.textContent = `A new version (${info.version}) is ready`; + } + + if (info && info.environment && info.environment !== "auto") { + icon.textContent = "open_in_new"; + + if (info.environment === "snap") { + sub.textContent = `Version ${info.version} is out. Use the Snap Store or run "snap refresh".`; + btn.textContent = "View Release"; + } else { + sub.textContent = `Version ${info.version} is out. Update via your package manager or download it manually.`; + btn.textContent = "Download manually"; + } + + btn.onclick = () => { + window.electronAPI.openReleasePage(); + container.style.display = "none"; + }; + + return; + } + + btn.onclick = () => { + window.electronAPI.downloadUpdate(); + btn.disabled = true; + btn.textContent = "Downloading..."; + icon.textContent = "download"; + title.textContent = "Downloading update"; + progressWrap.style.display = "flex"; + }; + + window.electronAPI.onDownloadProgress((progress) => { + const pct = Math.round(progress.percent); + progressFill.style.width = pct + "%"; + progressPct.textContent = pct + "%"; + + const remaining = (progress.total - progress.transferred) / progress.bytesPerSecond; + progressEta.textContent = + remaining > 0 ? `~${Math.ceil(remaining)}s left` : "almost done"; + }); + + window.electronAPI.onUpdateDownloaded(() => { + progressFill.style.width = "100%"; + progressPct.textContent = "100%"; + progressEta.textContent = "Done"; + icon.textContent = "check"; + title.textContent = "Ready to install"; + sub.textContent = "Restart to apply the update"; + btn.textContent = "Restart"; + btn.disabled = false; + btn.onclick = () => window.electronAPI.installUpdate(); + }); + } +} + +initUpdateUI(); diff --git a/public/styles/components/autoUpdate.css b/public/styles/components/autoUpdate.css new file mode 100644 index 0000000..b19fcac --- /dev/null +++ b/public/styles/components/autoUpdate.css @@ -0,0 +1,128 @@ +:root { + --border-accent: #0ea5e966; + --bg-accent: #0ea5e915; + --fill-accent: #0ea5e9; + --text-accent: #0369a1; + --text-secondary: #64748b; +} + +#updateAvailable { + display: none; + width: fit-content; + max-width: 420px; + position: absolute; + right: 18px; + bottom: 5%; + z-index: 50; +} + +.update-banner { + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px 16px; + border-radius: 10px; + border: 0.5px solid var(--border-accent); + background: var(--bg-accent); + backdrop-filter: blur(8px); +} + +.banner-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.banner-left { + display: flex; + align-items: center; + gap: 12px; + flex: 1; +} + +.icon-wrap { + width: 32px; + height: 32px; + border-radius: 8px; + background: var(--fill-accent); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.icon-wrap i { + font-size: 16px; + color: #fff; +} + +.text-block { + display: flex; + flex-direction: column; + gap: 4px; +} + +.update-title { + font-size: 13px; + font-weight: 600; + color: var(--text-accent); +} + +.update-sub { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.4; +} + +.btn-update { + font-size: 12px; + font-weight: 500; + padding: 6px 14px; + border-radius: 8px; + background: var(--fill-accent); + color: #fff; + border: none; + cursor: pointer; + white-space: nowrap; + flex-shrink: 0; + transition: opacity 0.2s; +} + +.btn-update:hover:not(:disabled) { + opacity: 0.9; +} + +.btn-update:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.progress-wrap { + display: flex; + flex-direction: column; + gap: 6px; +} + +.progress-track { + height: 4px; + border-radius: 99px; + background: var(--border-accent); + overflow: hidden; + width: 100%; +} + +.progress-fill { + height: 100%; + border-radius: 99px; + background: var(--fill-accent); + width: 0%; + transition: width 0.3s ease; +} + +.progress-meta { + font-size: 11px; + color: var(--text-secondary); + display: flex; + justify-content: space-between; +} diff --git a/public/styles/styles.css b/public/styles/styles.css index 948263a..965d5a2 100644 --- a/public/styles/styles.css +++ b/public/styles/styles.css @@ -12,6 +12,7 @@ @import url("components/editpathbutton.css"); @import url("components/progressBar.css"); @import url("components/toast.css"); +@import url("components/autoUpdate.css"); @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); From 1e0b2affeeb59611a7feb300626c0e5e95746d47 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 11:28:05 +0200 Subject: [PATCH 09/37] update: bump app version to 1.6.4 since it's no longer a preview --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fcd40f..d36ae48 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "freedom-loader", "desktopName": "com.masteracnolo.freedomloader", "productName": "Freedom Loader", - "version": "1.6.4-preview", + "version": "1.6.4", "author": "MasterAcnolo ", "description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/", From d11c0f9839dfb34dd2242f6d38d21ca12eb638f3 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 11:30:57 +0200 Subject: [PATCH 10/37] add: .zed and OS Generated files to .gitignore, and better exclusion of IDE folder --- .gitignore | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3990917..3e4ba25 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,14 @@ config/config.dev.json # IDE -/.idea -/.vscode +.idea/ +.vscode/ +.zed/ + +# OS generated files +.DS_Store +.DS_Store? +Thumbs.db # Linux executable ffmpeg From 99b7164ff889dde943bdafb080f1b46d53f16e32 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 16:23:28 +0200 Subject: [PATCH 11/37] fix(update): don't check for update if the app is installed with SNAP or Flatpak --- app/autoUpdater.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/autoUpdater.js b/app/autoUpdater.js index bb06b74..3b840f3 100644 --- a/app/autoUpdater.js +++ b/app/autoUpdater.js @@ -122,6 +122,7 @@ function initAutoUpdater(mainWindow) { * Separated from init for reusability and testability. */ async function checkForUpdates() { + if (process.env.SNAP || process.env.FLATPAK_ID) return; if (!require("electron").app.isPackaged) return; try { From e8c499fe23312ef312db16969406f0252bff2273 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 20:02:56 +0200 Subject: [PATCH 12/37] fix: sendReport wrong IPC instance --- public/script/reportBug.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script/reportBug.js b/public/script/reportBug.js index e40079e..69b428a 100644 --- a/public/script/reportBug.js +++ b/public/script/reportBug.js @@ -76,7 +76,7 @@ class BugReportModal { window.electronAPI.logInfo("Submitting:", data); try { - const success = await window.electronAPI.sendReport(data); + const success = await window.topbarAPI.sendReport(data); if (success) { localStorage.removeItem("draft_bug_title"); From 187e3af5596257cddcef806e4c9aa6260a126b04 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 20:03:25 +0200 Subject: [PATCH 13/37] fix: log transport names files local date --- app/sendReport.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/sendReport.js b/app/sendReport.js index 48b50da..c792cea 100644 --- a/app/sendReport.js +++ b/app/sendReport.js @@ -16,7 +16,12 @@ async function sendReport(params) { if (includeLogs === "yes" && logDir) { // YYYY-MM-DD - const today = new Date().toISOString().split("T")[0]; + const now = new Date(); + const today = [ + now.getFullYear(), + String(now.getMonth() + 1).padStart(2, "0"), + String(now.getDate()).padStart(2, "0") + ].join("-"); const logFilePath = path.join(logDir, `LOGS-${today}.log`); From 7f86d46ae7ca5443838830cbbed64f29536159b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:31:59 +0000 Subject: [PATCH 14/37] fix: validate backend bug report configuration before request Co-authored-by: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> --- app/sendReport.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/sendReport.js b/app/sendReport.js index c792cea..8ddb95a 100644 --- a/app/sendReport.js +++ b/app/sendReport.js @@ -35,10 +35,17 @@ async function sendReport(params) { } } - const response = await fetch(process.env.BUG_REPORT_URL, { + const reportUrl = process.env.BUG_REPORT_URL; + const reportApiKey = process.env.BUG_REPORT_API_KEY; + + if (!reportUrl || !reportApiKey) { + throw new Error("Bug report service is not configured. Missing BUG_REPORT_URL or BUG_REPORT_API_KEY on backend."); + } + + const response = await fetch(reportUrl, { method: "POST", headers: { - "X-Api-Key": process.env.BUG_REPORT_API_KEY + "X-Api-Key": reportApiKey }, body: formData }); From ac1d74ff23d71552be549b28c0072c647d2be468 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 27 Sep 2026 21:15:46 +0200 Subject: [PATCH 15/37] fix(autoUpdate): better error handling in frontEnd #50 --- public/script/autoUpdate.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/public/script/autoUpdate.js b/public/script/autoUpdate.js index f8fbcad..0cced57 100644 --- a/public/script/autoUpdate.js +++ b/public/script/autoUpdate.js @@ -45,13 +45,32 @@ async function initUpdateUI() { return; } - btn.onclick = () => { - window.electronAPI.downloadUpdate(); + const initialButtonText = btn.textContent; + const initialIconText = icon.textContent; + const initialTitleText = title.textContent; + const initialSubText = sub.textContent; + + btn.onclick = async () => { + btn.disabled = true; btn.textContent = "Downloading..."; icon.textContent = "download"; title.textContent = "Downloading update"; progressWrap.style.display = "flex"; + + try { + await window.electronAPI.downloadUpdate(); + } catch { + btn.disabled = false; + btn.textContent = initialButtonText; + icon.textContent = initialIconText; + title.textContent = initialTitleText; + sub.textContent = initialSubText; + progressWrap.style.display = "none"; + progressFill.style.width = "0%"; + progressPct.textContent = "0%"; + progressEta.textContent = ""; + } }; window.electronAPI.onDownloadProgress((progress) => { From 1937738ab85e8526b5602332b51a562470b49c4b Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 17:20:42 +0200 Subject: [PATCH 16/37] fix(autoUpdate): improve error handling and UI reset during update process --- app/autoUpdater.js | 4 ++++ preload.js | 9 +++++++++ public/script/autoUpdate.js | 27 ++++++++++++++++++--------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/app/autoUpdater.js b/app/autoUpdater.js index 3b840f3..d3abab8 100644 --- a/app/autoUpdater.js +++ b/app/autoUpdater.js @@ -112,6 +112,7 @@ function initAutoUpdater(mainWindow) { } logger.error("Auto update error:", err); + mainWindow?.webContents.send("update-error", msg); }); checkForUpdates(); @@ -122,6 +123,8 @@ function initAutoUpdater(mainWindow) { * Separated from init for reusability and testability. */ async function checkForUpdates() { + + // If app is was installed from SNAP or Flatpak. if (process.env.SNAP || process.env.FLATPAK_ID) return; if (!require("electron").app.isPackaged) return; @@ -141,6 +144,7 @@ async function downloadUpdate() { await autoUpdater.downloadUpdate(); } catch (err) { logger.error("Download failed:", err.message); + throw err; } } diff --git a/preload.js b/preload.js index 82363c8..b05d6a6 100644 --- a/preload.js +++ b/preload.js @@ -122,10 +122,19 @@ contextBridge.exposeInMainWorld("electronAPI", { */ installUpdate: () => ipcRenderer.invoke("install-update"), + /** + * Subscribes to the update error event. + * + * @param {Function} callback - Function executed with error information when an update error occurs. + * @returns {Electron.IpcRenderer} + */ + onUpdateError: (callback) => ipcRenderer.on("update-error", (_, err) => callback(err)), + /** * Subscribes to the download progress event. * * @param {Function} callback - Function executed with progress metrics (percent, bytesPerSecond, transferred, total). + * @returns {Electron.IpcRenderer} */ onDownloadProgress: (callback) => ipcRenderer.on("update-progress", (_, progress) => callback(progress)), diff --git a/public/script/autoUpdate.js b/public/script/autoUpdate.js index 0cced57..d11271a 100644 --- a/public/script/autoUpdate.js +++ b/public/script/autoUpdate.js @@ -50,6 +50,23 @@ async function initUpdateUI() { const initialTitleText = title.textContent; const initialSubText = sub.textContent; + const resetUI = () => { + btn.disabled = false; + btn.textContent = initialButtonText; + icon.textContent = initialIconText; + title.textContent = initialTitleText; + sub.textContent = initialSubText; + progressWrap.style.display = "none"; + progressFill.style.width = "0%"; + progressPct.textContent = "0%"; + progressEta.textContent = ""; + }; + + window.electronAPI.onUpdateError((err) => { + console.error("Update failed asynchronously:", err); + resetUI(); + }); + btn.onclick = async () => { btn.disabled = true; @@ -61,15 +78,7 @@ async function initUpdateUI() { try { await window.electronAPI.downloadUpdate(); } catch { - btn.disabled = false; - btn.textContent = initialButtonText; - icon.textContent = initialIconText; - title.textContent = initialTitleText; - sub.textContent = initialSubText; - progressWrap.style.display = "none"; - progressFill.style.width = "0%"; - progressPct.textContent = "0%"; - progressEta.textContent = ""; + resetUI(); } }; From 1e099f6bb6bb97282c51f4ada12cccb6f3eb1234 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 17:27:16 +0200 Subject: [PATCH 17/37] fix(sendReport): remove API key requirement for bug report service configuration --- app/sendReport.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/sendReport.js b/app/sendReport.js index 8ddb95a..8ee05b5 100644 --- a/app/sendReport.js +++ b/app/sendReport.js @@ -36,17 +36,13 @@ async function sendReport(params) { } const reportUrl = process.env.BUG_REPORT_URL; - const reportApiKey = process.env.BUG_REPORT_API_KEY; - if (!reportUrl || !reportApiKey) { - throw new Error("Bug report service is not configured. Missing BUG_REPORT_URL or BUG_REPORT_API_KEY on backend."); + if (!reportUrl) { + throw new Error("Bug report service is not configured. Missing BUG_REPORT_URL on backend."); } const response = await fetch(reportUrl, { method: "POST", - headers: { - "X-Api-Key": reportApiKey - }, body: formData }); From 1c0dd32732751e332db8340d313297eaf6999410 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 21:30:20 +0200 Subject: [PATCH 18/37] feat(bugReport): add service status check and update UI if sendReport server is down --- app/ipcHandlers.js | 7 ++++- app/sendReport.js | 62 +++++++++++++++++++++++++++++++++----- main.js | 4 +++ preload.js | 5 +++ public/script/reportBug.js | 46 +++++++++++++++++++++++++++- 5 files changed, 114 insertions(+), 10 deletions(-) diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 15dd06b..a82ed90 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -19,7 +19,7 @@ const config = require("../config"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { userThemesPath } = require("../server/helpers/path.helpers"); const { createSystemTray, destroyTray } = require("./tray"); -const { sendReport } = require("./sendReport"); +const {sendReport, checkReportService, getReportServiceStatus} = require("./sendReport"); const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater"); /** @@ -210,6 +210,11 @@ function registerIpcHandlers(getMainWindow) { return await sendReport(params); }); + ipcMain.handle("get-report-service-status", async () => { + await checkReportService(); + return getReportServiceStatus(); + }); + ipcMain.handle("is-update-available", () => { return isUpdateAvailable(); }); diff --git a/app/sendReport.js b/app/sendReport.js index 8ee05b5..e37266b 100644 --- a/app/sendReport.js +++ b/app/sendReport.js @@ -2,6 +2,58 @@ const fs = require("fs"); const path = require("path"); const {logger, logDir} = require("../server/logger"); +const REPORT_URL = "https://freedom-loader-report.acnolo.fr"; +const REPORT_SERVICE_TIMEOUT_MS = 5000; +let reportServiceStatus = { + status: "checking", + message: "Checking bug report service..." +}; +let reportServiceCheck = null; + +async function checkReportService() { + if (reportServiceCheck) { + return reportServiceCheck; + } + + reportServiceCheck = (async () => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), REPORT_SERVICE_TIMEOUT_MS); + + try { + const response = await fetch(REPORT_URL, { + method: "GET", + signal: controller.signal + }); + + if (response.status >= 500) { + throw new Error(`Server responded with status ${response.status}`); + } + + reportServiceStatus = { + status: "online", + message: "Bug report service is available." + }; + logger.info(`Bug report service is reachable (status ${response.status}).`); + } catch (err) { + reportServiceStatus = { + status: "offline", + message: "Bug report service is unavailable. Reports may fail to send." + }; + logger.warn(`Bug report service is unreachable: ${err.message}`); + } finally { + clearTimeout(timeout); + } + + return reportServiceStatus; + })(); + + return reportServiceCheck; +} + +function getReportServiceStatus() { + return reportServiceStatus; +} + async function sendReport(params) { try { const {title, description, includeLogs} = params; @@ -35,13 +87,7 @@ async function sendReport(params) { } } - const reportUrl = process.env.BUG_REPORT_URL; - - if (!reportUrl) { - throw new Error("Bug report service is not configured. Missing BUG_REPORT_URL on backend."); - } - - const response = await fetch(reportUrl, { + const response = await fetch(REPORT_URL, { method: "POST", body: formData }); @@ -59,4 +105,4 @@ async function sendReport(params) { } } -module.exports = {sendReport}; \ No newline at end of file +module.exports = {sendReport, checkReportService, getReportServiceStatus}; \ No newline at end of file diff --git a/main.js b/main.js index dd3b873..eac9fad 100644 --- a/main.js +++ b/main.js @@ -77,6 +77,7 @@ const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./ const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers"); const {createSystemTray, destroyTray} = require("./app/tray"); const { stopServer } = require("./server/server"); +const {checkReportService} = require("./app/sendReport"); /** * Expose .env in process.env @@ -137,6 +138,9 @@ app.on("second-instance", () => { app.whenReady().then(async () => { logSessionStart(logDir, defaultDownloadFolder); + checkReportService().catch((err) => { + logger.warn(`Bug report service startup check failed: ${err.message}`); + }); createSplashWindow(); diff --git a/preload.js b/preload.js index b05d6a6..c00d797 100644 --- a/preload.js +++ b/preload.js @@ -214,4 +214,9 @@ contextBridge.exposeInMainWorld("topbarAPI", { * @returns {Promise} */ sendReport: (params) => ipcRenderer.invoke("send-report", params), + + /** + * Returns the startup health status of the bug report service. + */ + getReportServiceStatus: () => ipcRenderer.invoke("get-report-service-status"), }); \ No newline at end of file diff --git a/public/script/reportBug.js b/public/script/reportBug.js index 69b428a..650174c 100644 --- a/public/script/reportBug.js +++ b/public/script/reportBug.js @@ -29,9 +29,13 @@ class BugReportModal {
+
+ + Checking bug report service... +
- +
@@ -46,6 +50,7 @@ class BugReportModal { bindEvents() { const titleInput = this.modal.querySelector("#bugTitle"); const descInput = this.modal.querySelector("#bugDescription"); + const sendButton = this.modal.querySelector("#bugSendBtn"); titleInput.addEventListener("input", () => { localStorage.setItem("draft_bug_title", titleInput.value); @@ -67,6 +72,10 @@ class BugReportModal { this.form.addEventListener("submit", async (e) => { e.preventDefault(); + if (sendButton.disabled) { + return; + } + const data = { title: titleInput.value, description: descInput.value, @@ -107,11 +116,46 @@ class BugReportModal { open() { this.loadState(); this.modal.style.display = "flex"; + this.updateServiceStatus(); } close() { this.modal.style.display = "none"; } + + async updateServiceStatus() { + const statusText = this.modal.querySelector("#bugReportServiceStatusText"); + const statusDot = this.modal.querySelector("#bugReportServiceStatusDot"); + const sendButton = this.modal.querySelector("#bugSendBtn"); + + sendButton.disabled = true; + sendButton.style.setProperty("cursor", "not-allowed", "important"); + sendButton.style.setProperty("opacity", "0.5", "important"); + + try { + const serviceStatus = await window.topbarAPI.getReportServiceStatus(); + const serviceAvailable = serviceStatus.status === "online"; + const statusVisible = !serviceAvailable; + statusText.textContent = serviceStatus.message; + statusDot.style.background = "#ef4444"; + statusText.style.color = "#fca5a5"; + this.modal.querySelector("#bugReportServiceStatus").style.setProperty( + "display", + statusVisible ? "flex" : "none", + "important" + ); + sendButton.disabled = !serviceAvailable; + sendButton.style.setProperty("cursor", serviceAvailable ? "pointer" : "not-allowed", "important"); + sendButton.style.setProperty("opacity", serviceAvailable ? "1" : "0.5", "important"); + } catch (err) { + statusText.textContent = "Unable to check bug report service."; + statusDot.style.background = "#ef4444"; + statusText.style.color = "#fca5a5"; + this.modal.querySelector("#bugReportServiceStatus").style.setProperty("display", "flex", "important"); + sendButton.disabled = true; + window.electronAPI.logError("Failed to retrieve bug report service status:", err.message); + } + } } window.bugModal = new BugReportModal(); From 0835e138401bccefc0991c162895416fbb13bbcb Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 21:57:54 +0200 Subject: [PATCH 19/37] refactor: reduce build bundle size by 50% --- package.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d36ae48..d7ae035 100644 --- a/package.json +++ b/package.json @@ -75,10 +75,15 @@ } ], "files": [ - "**/*", - "node_modules/**/*", - "!config/config.dev.json", - "!theme/**/*" + "app/**/*", + "public/**/*", + "server/**/*", + "build/**/*", + "config/config.default.json", + "config.js", + "main.js", + "preload.js", + "package.json" ], "directories": { "buildResources": "build" From c2123025505f9fcb7104c31c17cd12526449f64a Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 21:59:51 +0200 Subject: [PATCH 20/37] chore: remove dotenv dependency and .env.example file --- .env.example | 2 -- package.json | 1 - 2 files changed, 3 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index b5b9b88..0000000 --- a/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -BUG_REPORT_URL= -BUG_REPORT_API_KEY= diff --git a/package.json b/package.json index d7ae035..c5e2188 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", - "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", From 8614b0e32e27cbe437ad5c26c282e2b1cb669239 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 22:00:40 +0200 Subject: [PATCH 21/37] chore: remove dotenv dependency and update version to 1.6.4 --- main.js | 5 ----- package-lock.json | 17 ++--------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/main.js b/main.js index eac9fad..557b344 100644 --- a/main.js +++ b/main.js @@ -79,11 +79,6 @@ const {createSystemTray, destroyTray} = require("./app/tray"); const { stopServer } = require("./server/server"); const {checkReportService} = require("./app/sendReport"); -/** - * Expose .env in process.env - */ -require("dotenv").config(); - /** * Global flag indicating if the application is intentionally shutting down. * Used across the app to bypass the "minimize to tray on close" behavior. diff --git a/package-lock.json b/package-lock.json index ffb6fa8..f6b1d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "freedom-loader", - "version": "1.6.4-preview", + "version": "1.6.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "freedom-loader", - "version": "1.6.4-preview", + "version": "1.6.4", "license": "GPL-3.0-only", "dependencies": { "chalk": "^4.1.2", "debug": "^4.4.1", "discord-rpc": "^4.0.1", - "dotenv": "^17.4.2", "electron-updater": "^6.6.2", "express": "^5.1.0", "express-rate-limit": "^8.2.1", @@ -4193,18 +4192,6 @@ "js-yaml": "^4.1.0" } }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dotenv-expand": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", From 93983299599565427a33e8e074c7e5af3ac48b4f Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 29 Sep 2026 22:14:28 +0200 Subject: [PATCH 22/37] fix(release): correct AppImage filename format in release script --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 0816e80..21aa40d 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -133,7 +133,7 @@ if [ "$DRY_RUN" = false ]; then "$ROOT_DIR/dist/Freedom-Loader-Setup-${VERSION}.exe.blockmap" \ "$ROOT_DIR/dist/freedom-loader-${VERSION}.x86_64.rpm" \ "$ROOT_DIR/dist/freedom-loader_${VERSION}_amd64.deb" \ - "$ROOT_DIR/dist/Freedom Loader-${VERSION}.AppImage" \ + "$ROOT_DIR/dist/Freedom-Loader-${VERSION}.AppImage" \ "$ROOT_DIR/dist/freedom-loader_${VERSION}_amd64.snap" \ "$ROOT_DIR/dist/latest.yml" \ "$ROOT_DIR/dist/latest-linux.yml" From 37128138339cbd73bf859848adb7a848c48d02fb Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Fri, 2 Oct 2026 09:15:19 +0200 Subject: [PATCH 23/37] fix: update desktop entry and metainfo filename --- main.js | 3 +++ package/com.masteracnolo.freedomloader.metainfo.xml | 2 +- scripts/create-copr-package.sh | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/main.js b/main.js index 557b344..ef0582c 100644 --- a/main.js +++ b/main.js @@ -13,6 +13,9 @@ process.on("unhandledRejection", (reason) => { app.quit(); }); +if (process.platform === 'linux') { + app.setDesktopName('com.masteracnolo.freedomloader.desktop'); +} /** * True if this is the primary instance (lock acquired successfully) diff --git a/package/com.masteracnolo.freedomloader.metainfo.xml b/package/com.masteracnolo.freedomloader.metainfo.xml index 4efdddf..2feae69 100644 --- a/package/com.masteracnolo.freedomloader.metainfo.xml +++ b/package/com.masteracnolo.freedomloader.metainfo.xml @@ -45,7 +45,7 @@

- freedom-loader.desktop + com.masteracnolo.freedomloader.desktop https://github.com/MasterAcnolo/Freedom-Loader https://github.com/MasterAcnolo/Freedom-Loader/issues diff --git a/scripts/create-copr-package.sh b/scripts/create-copr-package.sh index 5bb8161..fcf0ffb 100755 --- a/scripts/create-copr-package.sh +++ b/scripts/create-copr-package.sh @@ -57,7 +57,7 @@ chmod +x "$TMP_WRAPPER/freedom-loader" # Desktop entry file TMP_DESKTOP="$ROOT_DIR/.rpm-pkg-tmp-desktop" mkdir -p "$TMP_DESKTOP" -cat > "$TMP_DESKTOP/freedom-loader.desktop" << 'EOF' +cat > "$TMP_DESKTOP/com.masteracnolo.freedomloader.desktop" << 'EOF' [Desktop Entry] Name=Freedom Loader Exec=/opt/freedom-loader/freedom-loader %U @@ -65,7 +65,7 @@ Icon=freedom-loader Type=Application Categories=AudioVideo;Utility;Network; Comment=Free and open-source GUI for yt-dlp -StartupWMClass=Freedom Loader +StartupWMClass=com.masteracnolo.freedomloader EOF # ------------------------------------------------------------ @@ -89,7 +89,7 @@ echo "Building RPM..." --package "$RPM_OUT" \ "$ROOT_DIR/dist/linux-unpacked/=/opt/freedom-loader" \ "$TMP_WRAPPER/freedom-loader=/usr/bin/freedom-loader" \ - "$TMP_DESKTOP/freedom-loader.desktop=/usr/share/applications/freedom-loader.desktop" \ + "$TMP_DESKTOP/com.masteracnolo.freedomloader.desktop=/usr/share/applications/freedom-loader.desktop" \ "$ROOT_DIR/build/app-icon.png=/usr/share/icons/hicolor/512x512/apps/freedom-loader.png" \ "$ROOT_DIR/package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml" From a308352ea041a1efe4425de3bf3f401290be21c2 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Fri, 2 Oct 2026 09:26:46 +0200 Subject: [PATCH 24/37] fix: update website links to new domain --- .github/ISSUE_TEMPLATE/question.md | 4 ++-- CONTRIBUTING.md | 4 ++-- README.md | 8 ++++---- app/discordRPC.js | 2 +- app/ipcHandlers.js | 4 ++-- package.json | 2 +- scripts/create-copr-package.sh | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index ecfa48e..5c74cfe 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -9,8 +9,8 @@ assignees: 'MasterAcnolo' ## Pre-submission Checklist -- [ ] I have checked the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/#/faq) -- [ ] I have reviewed the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/#/wiki) +- [ ] I have checked the [FAQ](https://freedomloader.acnolo.fr/#/faq) +- [ ] I have reviewed the [Wiki](https://freedomloader.acnolo.fr/#/wiki) - [ ] I have searched existing issues ## Question diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c2f407..ab56aaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,8 +112,8 @@ See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details. ## Questions? -- Check the [FAQ](https://masteracnolo.github.io/Freedom-Loader-Site/faq) -- Review the [Wiki](https://masteracnolo.github.io/Freedom-Loader-Site/wiki) +- Check the [FAQ](https://freedomloader.acnolo.fr/faq) +- Review the [Wiki](https://freedomloader.acnolo.fr/wiki) - Open a [Question issue](https://github.com/MasterAcnolo/Freedom-Loader/issues/new/choose) --- diff --git a/README.md b/README.md index 200fdcb..edb65ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- Banner @@ -9,7 +9,7 @@ [![Release](https://img.shields.io/github/v/release/MasterAcnolo/Freedom-Loader?style=for-the-badge&color=blue)](https://github.com/MasterAcnolo/Freedom-Loader/releases) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0) -[![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Site/) +[![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://freedomloader.acnolo.fr/) [![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/) @@ -354,7 +354,7 @@ See [BINARIES.md](./BINARIES.md) for how to setup the binaries. ## Roadmap -See the most recent Roadmap [Here](https://masteracnolo.github.io/Freedom-Loader-Site/Roadmap). +See the most recent Roadmap [Here](https://freedomloader.acnolo.fr/Roadmap). ## Contributing @@ -434,6 +434,6 @@ You are free to use, modify, and redistribute this software under the terms of t **Freedom Loader** - Put freedom in your downloads -[Website](https://masteracnolo.github.io/Freedom-Loader-Site/) • [Download](https://github.com/MasterAcnolo/Freedom-Loader/releases) • [Documentation](https://masteracnolo.github.io/Freedom-Loader-Site/wiki) • [Report Bug](https://github.com/MasterAcnolo/Freedom-Loader/issues) +[Website](https://freedomloader.acnolo.fr/) • [Download](https://github.com/MasterAcnolo/Freedom-Loader/releases) • [Documentation](https://freedomloader.acnolo.fr/wiki) • [Report Bug](https://github.com/MasterAcnolo/Freedom-Loader/issues)
diff --git a/app/discordRPC.js b/app/discordRPC.js index 15d3da8..4a3c8bc 100644 --- a/app/discordRPC.js +++ b/app/discordRPC.js @@ -95,7 +95,7 @@ function startRPC() { smallImageText: "By MasterAcnolo", startTimestamp: new Date(), details: `Open Source Download Tools - ${config.version}`, - state: "masteracnolo.github.io/FreedomLoader", + state: "freedomloader.acnolo.fr", }; rpc.clearActivity(); rpc.setActivity(presence); diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index a82ed90..9f64b63 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -157,14 +157,14 @@ function registerIpcHandlers(getMainWindow) { * Opens external website in default browser. */ ipcMain.on("open-website", () => - shell.openExternal("https://masteracnolo.github.io/Freedom-Loader-Site/") + shell.openExternal("https://freedomloader.acnolo.fr/") ); /** * Opens official wiki page in external browser. */ ipcMain.on("open-wiki", () => - shell.openExternal("https://masteracnolo.github.io/Freedom-Loader-Site/wiki") + shell.openExternal("https://freedomloader.acnolo.fr/wiki") ); /** diff --git a/package.json b/package.json index c5e2188..66090a4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "1.6.4", "author": "MasterAcnolo ", "description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", - "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/", + "homepage": "https://freedomloader.acnolo.fr/", "repository": { "type": "git", "url": "https://github.com/MasterAcnolo/Freedom-Loader.git" diff --git a/scripts/create-copr-package.sh b/scripts/create-copr-package.sh index fcf0ffb..aca36ba 100755 --- a/scripts/create-copr-package.sh +++ b/scripts/create-copr-package.sh @@ -78,7 +78,7 @@ echo "Building RPM..." --version "$VERSION" \ --architecture x86_64 \ --description "Free and open-source GUI for yt-dlp" \ - --url "https://masteracnolo.github.io/Freedom-Loader-Site/" \ + --url "https://freedomloader.acnolo.fr/" \ --maintainer "MasterAcnolo " \ --license "GPL-3.0-only" \ --depends gtk3 \ From cf65069496508299edcec3a3620de061d39148b6 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Fri, 2 Oct 2026 14:52:30 +0200 Subject: [PATCH 25/37] fix: extract .desktop inside a dedicated file and add npm run test:metainfo and test:desktop to check file. also clean:dist --- package.json | 3 +++ .../com.masteracnolo.freedomloader.desktop | 8 +++++++ scripts/create-copr-package.sh | 21 +++---------------- 3 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 package/com.masteracnolo.freedomloader.desktop diff --git a/package.json b/package.json index 66090a4..b89f3ce 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "main": "main.js", "scripts": { + "clean:dist": "rm -rf dist/", "start": "electron --no-warnings .", "start:warn": "electron --trace-warnings .", "start:debug": "WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .", @@ -32,6 +33,8 @@ "release:dry-run": "bash scripts/release.sh --dry-run", "test": "jest", "test:unit": "jest test/unit", + "test:metainfo": "appstreamcli validate package/com.masteracnolo.freedomloader.metainfo.xml", + "test:desktop": "desktop-file-validate package/com.masteracnolo.freedomloader.desktop", "lint": "eslint . --format pretty", "lint:fix": "eslint . --fix --format pretty", "update": "npm update" diff --git a/package/com.masteracnolo.freedomloader.desktop b/package/com.masteracnolo.freedomloader.desktop new file mode 100644 index 0000000..5b4ea55 --- /dev/null +++ b/package/com.masteracnolo.freedomloader.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=Freedom Loader +Exec=/opt/freedom-loader/freedom-loader %U +Icon=freedom-loader +Type=Application +Categories=AudioVideo; +Comment=Free and open-source GUI for yt-dlp +StartupWMClass=com.masteracnolo.freedomloader \ No newline at end of file diff --git a/scripts/create-copr-package.sh b/scripts/create-copr-package.sh index aca36ba..e1303c9 100755 --- a/scripts/create-copr-package.sh +++ b/scripts/create-copr-package.sh @@ -54,20 +54,6 @@ exec /opt/freedom-loader/freedom-loader "$@" EOF chmod +x "$TMP_WRAPPER/freedom-loader" -# Desktop entry file -TMP_DESKTOP="$ROOT_DIR/.rpm-pkg-tmp-desktop" -mkdir -p "$TMP_DESKTOP" -cat > "$TMP_DESKTOP/com.masteracnolo.freedomloader.desktop" << 'EOF' -[Desktop Entry] -Name=Freedom Loader -Exec=/opt/freedom-loader/freedom-loader %U -Icon=freedom-loader -Type=Application -Categories=AudioVideo;Utility;Network; -Comment=Free and open-source GUI for yt-dlp -StartupWMClass=com.masteracnolo.freedomloader -EOF - # ------------------------------------------------------------ # Step 1 - Build .rpm from linux-unpacked via fpm # ------------------------------------------------------------ @@ -89,11 +75,10 @@ echo "Building RPM..." --package "$RPM_OUT" \ "$ROOT_DIR/dist/linux-unpacked/=/opt/freedom-loader" \ "$TMP_WRAPPER/freedom-loader=/usr/bin/freedom-loader" \ - "$TMP_DESKTOP/com.masteracnolo.freedomloader.desktop=/usr/share/applications/freedom-loader.desktop" \ "$ROOT_DIR/build/app-icon.png=/usr/share/icons/hicolor/512x512/apps/freedom-loader.png" \ - "$ROOT_DIR/package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml" - -rm -rf "$TMP_WRAPPER" "$TMP_DESKTOP" + "$ROOT_DIR/package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml" \ + "$ROOT_DIR/package/com.masteracnolo.freedomloader.desktop=/usr/share/applications/com.masteracnolo.freedomloader.desktop" +rm -rf "$TMP_WRAPPER" echo "RPM built: $RPM_OUT" # ------------------------------------------------------------ From dc8d0a62d7c6055eaa984eba3194b2da8e8e02cf Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Sun, 4 Oct 2026 23:09:15 +0200 Subject: [PATCH 26/37] fix: enhance clean:dist and build:rpm npm scripts --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b89f3ce..e452533 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "main": "main.js", "scripts": { - "clean:dist": "rm -rf dist/", + "clean:dist": "rm -rf dist/ && rm -rf srpm-out/", "start": "electron --no-warnings .", "start:warn": "electron --trace-warnings .", "start:debug": "WAYLAND_DEBUG=1 ELECTRON_ENABLE_LOGGING=1 ELECTRON_ENABLE_STACK_DUMPING=1 electron --enable-logging --v=1 .", @@ -26,7 +26,7 @@ "build": "electron-builder", "build:win": "electron-builder --win", "build:linux": "electron-builder --linux", - "build:rpm": "bash scripts/create-copr-package.sh", + "build:rpm": "npm run clean:dist && npm run build:linux && bash scripts/create-copr-package.sh", "build:all": "electron-builder -wl", "release": "bash scripts/release.sh", "release:publish": "bash scripts/release.sh --publish", @@ -67,6 +67,7 @@ }, "build": { "appId": "com.masteracnolo.freedomloader", + "artifactName": "Freedom-Loader-${version}.${ext}", "productName": "Freedom Loader", "publish": [ { From 7af68983016353a325c9ec492c2fc6421f9ec1a7 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Mon, 5 Oct 2026 10:54:59 +0200 Subject: [PATCH 27/37] fix: moved CHANGELOD.md to root --- scripts/CHANGELOG.md => CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename scripts/CHANGELOG.md => CHANGELOG.md (65%) diff --git a/scripts/CHANGELOG.md b/CHANGELOG.md similarity index 65% rename from scripts/CHANGELOG.md rename to CHANGELOG.md index f5c3c0d..892c4ca 100644 --- a/scripts/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -[//]: # (This file is used for the release CHANGELOG. Please use Markdown to describe changes.) \ No newline at end of file +[//]: # (This file is used for the release CHANGELOG. Please use Markdown to describe changes.) + From b2eb3b3b40126796389ac6f50ada58c492c00418 Mon Sep 17 00:00:00 2001 From: MasterAcnolo Date: Tue, 6 Oct 2026 21:42:57 +0200 Subject: [PATCH 28/37] feat: implement changelog feature with UI modal and caching --- app/changelogManager.js | 177 ++++++++++++++++ app/ipcHandlers.js | 11 + preload.js | 10 + public/index.html | 17 ++ public/script/changelog.js | 277 +++++++++++++++++++++++++ public/styles/components/changelog.css | 229 ++++++++++++++++++++ public/styles/styles.css | 1 + 7 files changed, 722 insertions(+) create mode 100644 app/changelogManager.js create mode 100644 public/script/changelog.js create mode 100644 public/styles/components/changelog.css diff --git a/app/changelogManager.js b/app/changelogManager.js new file mode 100644 index 0000000..449223b --- /dev/null +++ b/app/changelogManager.js @@ -0,0 +1,177 @@ +const { app } = require("electron"); +const fs = require("fs"); +const path = require("path"); +const https = require("https"); +const config = require("../config"); +const { logger } = require("../server/logger"); + +/** Maximum age of the cached release list before it must be refreshed. */ +const CACHE_TTL_MS = 24 * 60 * 60 * 1000; + +/** Filename used to persist changelog data in the Electron user data folder. */ +const CACHE_FILE_NAME = "changelog-cache.json"; + +/** GitHub API endpoint containing the application's published releases. */ +const RELEASES_URL = "https://api.github.com/repos/MasterAcnolo/Freedom-Loader/releases"; + +/** Returns the path of the local changelog cache file. */ +function getCachePath() { + return path.join(app.getPath("userData"), CACHE_FILE_NAME); +} + +/** + * Extracts the numeric major, minor, and patch parts from a version string. + * + * @param {string} version - Version string or release tag + * @returns {number[]|null} Parsed version parts, or null when invalid + */ +function normalizeVersion(version) { + const match = String(version || "").match(/(\d+)\.(\d+)\.(\d+)/); + return match ? match.slice(1).map(Number) : null; +} + +/** + * Compares two semantic version strings. + * + * @param {string} left - First version to compare + * @param {string} right - Second version to compare + * @returns {number} Negative, zero, or positive comparison result + */ +function compareVersions(left, right) { + const leftParts = normalizeVersion(left) || [0, 0, 0]; + const rightParts = normalizeVersion(right) || [0, 0, 0]; + + for (let index = 0; index < 3; index += 1) { + if (leftParts[index] !== rightParts[index]) { + return leftParts[index] - rightParts[index]; + } + } + + return 0; +} + +/** Reads and parses the cached changelog, if one is available. */ +function readCache() { + try { + return JSON.parse(fs.readFileSync(getCachePath(), "utf8")); + } catch { + return null; + } +} + +/** Persists the fetched releases together with their cache timestamp. */ +function writeCache(releases) { + try { + fs.writeFileSync( + getCachePath(), + JSON.stringify({ cachedAt: Date.now(), releases }, null, 2), + "utf8", + ); + } catch (error) { + logger.warn(`Unable to cache changelog: ${error.message}`); + } +} + +/** + * Fetches published releases from GitHub and maps them to the UI data shape. + * + * @returns {Promise} Published releases + */ +function fetchReleases() { + return new Promise((resolve, reject) => { + const request = https.get( + RELEASES_URL, + { + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "Freedom-Loader", + }, + }, + (response) => { + let body = ""; + response.setEncoding("utf8"); + response.on("data", (chunk) => { + body += chunk; + }); + response.on("end", () => { + // Do not attempt to parse an error response as a release list. + if (response.statusCode !== 200) { + reject(new Error(`GitHub returned HTTP ${response.statusCode}`)); + return; + } + + try { + // Draft releases are not visible to users and should not appear in the changelog. + const releases = JSON.parse(body) + .filter((release) => !release.draft) + .map((release) => ({ + version: release.tag_name, + name: release.name || release.tag_name, + date: release.published_at || release.created_at, + body: release.body || "No changelog available.", + url: release.html_url, + })); + resolve(releases); + } catch (error) { + reject(error); + } + }); + }, + ); + + request.setTimeout(10000, () => { + request.destroy(new Error("Changelog request timed out")); + }); + request.on("error", reject); + }); +} + +/** + * Returns cached or freshly fetched releases and identifies unread versions. + * + * @returns {Promise} Changelog data for the renderer + */ +async function getChangelog() { + const cache = readCache(); + let releases = cache?.releases || []; + const cacheIsFresh = cache && cache.releases?.length > 0 && Date.now() - cache.cachedAt < CACHE_TTL_MS; + + // Refresh stale or missing data, while keeping the last cache as a fallback. + if (!cacheIsFresh) { + try { + releases = await fetchReleases(); + writeCache(releases); + } catch (error) { + logger.warn(`Unable to retrieve changelog: ${error.message}`); + } + } + + const lastSeenVersion = cache?.lastSeenVersion || null; + + // Without a previous version, all available releases are considered unread. + const unreadReleases = lastSeenVersion + ? releases.filter((release) => compareVersions(release.version, lastSeenVersion) > 0) + : releases; + + return { + // An empty release list means that the fetch failed or returned no releases. + error: releases.length === 0, + currentVersion: config.version, + lastSeenVersion, + hasUnread: unreadReleases.length > 0, + releases, + unreadReleases, + }; +} + +/** Marks the current application version as seen in the local cache. */ +function markChangelogSeen() { + const cache = readCache() || { releases: [] }; + cache.lastSeenVersion = config.version; + cache.cachedAt = cache.cachedAt || 0 ; + fs.mkdirSync(path.dirname(getCachePath()), { recursive: true }); + fs.writeFileSync(getCachePath(), JSON.stringify(cache, null, 2), "utf8"); + return true; +} + +module.exports = { getChangelog, markChangelogSeen }; diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 9f64b63..5e9f3e8 100644 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -21,6 +21,7 @@ const { userThemesPath } = require("../server/helpers/path.helpers"); const { createSystemTray, destroyTray } = require("./tray"); const {sendReport, checkReportService, getReportServiceStatus} = require("./sendReport"); const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater"); +const { getChangelog, markChangelogSeen } = require("./changelogManager"); /** * Security whitelist for feature flags that can be modified at runtime. @@ -71,6 +72,16 @@ function registerIpcHandlers(getMainWindow) { */ ipcMain.handle("version", () => config.version); + /** + * Retrieves the changelog from cache or GitHub API. + */ + ipcMain.handle("get-changelog", () => getChangelog()); + + /** + * Marks all changelog entries as seen and updates cache. + */ + ipcMain.handle("mark-changelog-seen", () => markChangelogSeen()); + /** * Returns runtime feature configuration object. */ diff --git a/preload.js b/preload.js index c00d797..a8233a1 100644 --- a/preload.js +++ b/preload.js @@ -69,6 +69,16 @@ contextBridge.exposeInMainWorld("electronAPI", { */ getVersion: () => ipcRenderer.invoke("version"), + /** + * Returns the changelog data + */ + getChangelog: () => ipcRenderer.invoke("get-changelog"), + + /** + * Marks the current changelog version as seen by the user. + */ + markChangelogSeen: () => ipcRenderer.invoke("mark-changelog-seen"), + /** * Validates a download path before using it for file operations. * diff --git a/public/index.html b/public/index.html index 777d620..fbdeb82 100644 --- a/public/index.html +++ b/public/index.html @@ -27,6 +27,7 @@ +
@@ -303,6 +304,21 @@
+ +

Because Why Not ?

-
- -
- System Tray - Minimize app on close, in the System Tray. [EXPERIMENTAL] -
+
+ +
+ Show Changelogs After Update + Display changelogs after each update.
+
- - -
- -
- Auto Fetch Info - Automatically fetch information for new downloads. -
+
+ +
+ System Tray + Minimize app on close, in the System Tray. [EXPERIMENTAL]
+
-
+ + +
+ +
+ Auto Fetch Info + Automatically fetch information for new downloads. +
+
+ +
Add Metadata diff --git a/public/script/changelog.js b/public/script/changelog.js old mode 100644 new mode 100755 index a9ec6bb..fd88401 --- a/public/script/changelog.js +++ b/public/script/changelog.js @@ -226,10 +226,15 @@ async function closeChangelog() { /** Loads changelog data and opens the modal when unread releases exist. */ async function loadChangelog() { + + const features = await window.electronAPI.getFeatures(); + try { changelogData = await window.electronAPI.getChangelog(); - if (!changelogData.error && changelogData.hasUnread) { + + if (!changelogData.error && changelogData.hasUnread && features.showChanglogsAfterUpdate === true) { openChangelog(changelogData.unreadReleases); + window.electronAPI.logInfo("Showing Changelog"); } } catch (error) { console.error("Unable to load changelog:", error); From 7ee6d6fbe717d07ee6d530ae73677ab803348d75 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 17:21:10 +0200 Subject: [PATCH 32/37] refactor: remove custom top bar feature from configuration and UI --- README.md | 2 -- app/ipcHandlers.js | 1 - app/windowManager.js | 2 +- config/config.default.json | 1 - public/index.html | 4 ++-- public/script/topbar.js | 33 +++++++++++++++-------------- server/helpers/buildArgs.helpers.js | 1 - 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index edb65ea..af3c8e9 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,6 @@ Freedom Loader can be configured either through the settings panel in the UI or "autoUpdate": true, "systemTray": true, "discordRPC": true, - "customTopBar": true, "autoCheckInfo": true, "addThumbnail": true, "addMetadata": true, @@ -252,7 +251,6 @@ Freedom Loader can be configured either through the settings panel in the UI or | `autoUpdate` | boolean | `true` | Enable automatic application updates | | `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | -| `customTopBar` | boolean | `true` | Use custom application top bar | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `addThumbnail` | boolean | `true` | Embed thumbnail in downloaded audio files | | `addMetadata` | boolean | `true` | Add metadata tags to downloaded files | diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 06c94de..3c5512b 100755 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -33,7 +33,6 @@ const FEATURE_WHITELIST = new Set([ "autoUpdate", "systemTray", "discordRPC", - "customTopBar", "autoCheckInfo", "addThumbnail", "addMetadata", diff --git a/app/windowManager.js b/app/windowManager.js index a21ed42..ee30b8f 100644 --- a/app/windowManager.js +++ b/app/windowManager.js @@ -57,7 +57,7 @@ async function createMainWindow() { height: 800, minWidth: 750, minHeight: 800, - frame: !configFeatures.customTopBar, + frame: false, // Force custom topbar for consistent UI across platforms, and less maintenance headaches with native titlebar actions devTools: !app.isPackaged, show: false, webPreferences: { diff --git a/config/config.default.json b/config/config.default.json index 7fc2998..da7c960 100644 --- a/config/config.default.json +++ b/config/config.default.json @@ -2,7 +2,6 @@ "autoUpdate": true, "systemTray": false, "discordRPC": true, - "customTopBar": true, "autoCheckInfo": true, "addThumbnail": true, "addMetadata": true, diff --git a/public/index.html b/public/index.html index 1d50e1d..e9180b0 100644 --- a/public/index.html +++ b/public/index.html @@ -179,13 +179,13 @@
-
+
diff --git a/public/script/topbar.js b/public/script/topbar.js index c8ad591..e2a601d 100644 --- a/public/script/topbar.js +++ b/public/script/topbar.js @@ -35,25 +35,26 @@ export function initTopBar() { } -async function attachListeners(){ - try{ - const featuresList = await window.electronAPI.getFeatures(); +//* Disabled because i disabled native topbar for now, but keeping the code in case i want to re-enable it later +// async function attachListeners(){ +// try{ +// const featuresList = await window.electronAPI.getFeatures(); - if (!featuresList.customTopBar) { - const topbar = document.getElementById("topbar"); - const container = document.getElementById("container"); - const themeSwitcher = document.getElementById("theme-switcher"); +// if (!featuresList.customTopBar) { +// const topbar = document.getElementById("topbar"); +// const container = document.getElementById("container"); +// const themeSwitcher = document.getElementById("theme-switcher"); - if (topbar) topbar.style.display = "none"; - if (container) container.style.marginTop = "0"; - if (themeSwitcher) themeSwitcher.style.top = "30px"; - } +// if (topbar) topbar.style.display = "none"; +// if (container) container.style.marginTop = "0"; +// if (themeSwitcher) themeSwitcher.style.top = "30px"; +// } - } catch (error) { - console.error("Failed to load layout features for topbar:", error); - } +// } catch (error) { +// console.error("Failed to load layout features for topbar:", error); +// } -} +// } initTopBar() -document.addEventListener("DOMContentLoaded", attachListeners) \ No newline at end of file +//document.addEventListener("DOMContentLoaded", attachListeners) \ No newline at end of file diff --git a/server/helpers/buildArgs.helpers.js b/server/helpers/buildArgs.helpers.js index 75ad30f..806cc95 100644 --- a/server/helpers/buildArgs.helpers.js +++ b/server/helpers/buildArgs.helpers.js @@ -69,7 +69,6 @@ function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) { logger.info(`CONFIG autoUpdate: ${configFeatures.autoUpdate}`); logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`); - logger.info(`CONFIG customTopBar: ${configFeatures.customTopBar}`); logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`); logger.info(`CONFIG keepPlaylistOrder: ${configFeatures.keepPlaylistOrder}`); logger.info(`CONFIG addThumbnail: ${configFeatures.addThumbnail}`); From aff7637358861af88ec24d280fed39405c6a591a Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 17:22:44 +0200 Subject: [PATCH 33/37] fix: remove experimental label from system tray option description --- README.md | 2 +- public/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af3c8e9..af69d14 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,7 @@ Freedom Loader can be configured either through the settings panel in the UI or | Option | Type | Default | Description | |------------------------------|---------|----------|------------------------------------------------------------| | `autoUpdate` | boolean | `true` | Enable automatic application updates | -| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] | +| `systemTray` | boolean | `false` | Allow app to minimize on close | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `addThumbnail` | boolean | `true` | Embed thumbnail in downloaded audio files | diff --git a/public/index.html b/public/index.html index e9180b0..1cc7368 100644 --- a/public/index.html +++ b/public/index.html @@ -143,7 +143,7 @@
System Tray - Minimize app on close, in the System Tray. [EXPERIMENTAL] + Minimize app on close, in the System Tray.
From 738d5ec62fe5517a67f7803b2d7b056ffcc80008 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:04:58 +0200 Subject: [PATCH 34/37] fix: update README to clarify custom top bar feature and add cache directory information --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af69d14..18b63a4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ The primary goal is to make media downloading accessible to users who want offli ### User Interface -- **Custom window controls** - Frameless window with custom top bar (optional) +- **Custom window controls** - Frameless window with custom top bar - **Custom themes** - ZIP-based theme system with live preview - **Theme Workshop** - web-based theme creator and browser - **Toast notifications** - in-app feedback system @@ -151,6 +151,7 @@ freedom-loader |---------------|---------------------------------------------------------|--------------------------------------| | **Downloads** | `C:\Users\[USERNAME]\Downloads\Freedom Loader` | `~/Downloads/Freedom Loader` | | **Logs** | `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\` | `~/.local/share/FreedomLoader/logs/` | +| **Cache** | `` > [!NOTE] > The download folder can be changed anytime via the "Edit" button in the UI, on both platforms. From ea3a475525c57d0834cbf3575a4663f82ef9c738 Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:19:58 +0200 Subject: [PATCH 35/37] docs: add option to auto-display changelog after updates in configuration --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 18b63a4..fb0311f 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,7 @@ Freedom Loader can be configured either through the settings panel in the UI or "downloadSystem": true, "notifySystem": true, "enableHardwareAcceleration": true, + "showChanglogsAfterUpdate": true, "theme": "dark" } ``` @@ -265,6 +266,7 @@ Freedom Loader can be configured either through the settings panel in the UI or | `downloadSystem` | boolean | `true` | Enable download system | | `notifySystem` | boolean | `true` | Enable system notifications | | `enableHardwareAcceleration` | boolean | `true` | Enable Hardware Acceleration | +| `showChanglogsAfterUpdate` | boolean | `true` | Auto-display changelog popup after and update | | `theme` | string | `dark` | Current Theme Name | ### Supported Codecs From 773ed9550d00d362d31b3f3eae0940d0a6f0eeaf Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 19:24:47 +0200 Subject: [PATCH 36/37] refactor: remove autoUpdate feature from configuration and UI --- README.md | 2 -- app/ipcHandlers.js | 1 - config/config.default.json | 1 - main.js | 2 +- public/index.html | 8 -------- server/helpers/buildArgs.helpers.js | 1 - test/unit/buildArgs.test.js | 1 - 7 files changed, 1 insertion(+), 15 deletions(-) diff --git a/README.md b/README.md index fb0311f..2b88d75 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,6 @@ Freedom Loader can be configured either through the settings panel in the UI or ### Available Options ```json { - "autoUpdate": true, "systemTray": true, "discordRPC": true, "autoCheckInfo": true, @@ -250,7 +249,6 @@ Freedom Loader can be configured either through the settings panel in the UI or | Option | Type | Default | Description | |------------------------------|---------|----------|------------------------------------------------------------| -| `autoUpdate` | boolean | `true` | Enable automatic application updates | | `systemTray` | boolean | `false` | Allow app to minimize on close | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | diff --git a/app/ipcHandlers.js b/app/ipcHandlers.js index 3c5512b..9a104ad 100755 --- a/app/ipcHandlers.js +++ b/app/ipcHandlers.js @@ -30,7 +30,6 @@ const { getChangelog, markChangelogSeen } = require("./changelogManager"); * Acts as a basic validation layer for IPC "set-feature". */ const FEATURE_WHITELIST = new Set([ - "autoUpdate", "systemTray", "discordRPC", "autoCheckInfo", diff --git a/config/config.default.json b/config/config.default.json index da7c960..b8306fa 100644 --- a/config/config.default.json +++ b/config/config.default.json @@ -1,5 +1,4 @@ { - "autoUpdate": true, "systemTray": false, "discordRPC": true, "autoCheckInfo": true, diff --git a/main.js b/main.js index ef0582c..474a03e 100644 --- a/main.js +++ b/main.js @@ -173,7 +173,7 @@ app.whenReady().then(async () => { if (configFeatures.discordRPC) startRPC(); - if (configFeatures.autoUpdate) initAutoUpdater(getMainWindow()); + initAutoUpdater(getMainWindow()); } catch (err) { logger.error("Boot error:", err); diff --git a/public/index.html b/public/index.html index 1cc7368..7f7688e 100644 --- a/public/index.html +++ b/public/index.html @@ -123,14 +123,6 @@
Advanced Settings -
- -
- Auto Update - Enable automatic app updates. -
-
-
diff --git a/server/helpers/buildArgs.helpers.js b/server/helpers/buildArgs.helpers.js index 806cc95..0f1991d 100644 --- a/server/helpers/buildArgs.helpers.js +++ b/server/helpers/buildArgs.helpers.js @@ -67,7 +67,6 @@ function buildYtDlpArgs({url, audioOnly, quality, outputFolder, isPlaylist}) { logger.info("--- CONFIGURATION ---"); - logger.info(`CONFIG autoUpdate: ${configFeatures.autoUpdate}`); logger.info(`CONFIG discordRPC: ${configFeatures.discordRPC}`); logger.info(`CONFIG autoCheckInfo: ${configFeatures.autoCheckInfo}`); logger.info(`CONFIG keepPlaylistOrder: ${configFeatures.keepPlaylistOrder}`); diff --git a/test/unit/buildArgs.test.js b/test/unit/buildArgs.test.js index 4742a57..920de22 100644 --- a/test/unit/buildArgs.test.js +++ b/test/unit/buildArgs.test.js @@ -11,7 +11,6 @@ jest.mock("../../server/helpers/path.helpers.js", () => ({ jest.mock("../../config.js", () => ({ configFeatures: { - autoUpdate: false, discordRPC: false, customTopBar: false, autoCheckInfo: false, From cfa85649b3c1d86e3bd634c8adcb8fb4702d3f3d Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Wed, 7 Oct 2026 19:25:51 +0200 Subject: [PATCH 37/37] docs: update changelog to reflect recent changes and improvements --- scripts/RELEASE-CHANGELOG.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/RELEASE-CHANGELOG.md b/scripts/RELEASE-CHANGELOG.md index 05fca36..085c067 100644 --- a/scripts/RELEASE-CHANGELOG.md +++ b/scripts/RELEASE-CHANGELOG.md @@ -1,21 +1,24 @@ ### Features - **In-App Feedback**: Added a built-in feedback form allowing users to send feedback, bug reports, and daily logs directly to the development team. - **Community Contributions**: GitHub issues are now better maintained on the repository. If you want to contribute to the project, feel free to drop by and check out the `good first issue` tags! +- **Changelog**: The changelog is now automatically displayed after an update (can be disabled if preferred). ### Improvements -- **Bundle Size**: Optimized assets and dependencies, reducing the overall application bundle size by approximately 50% on specific version. -- **Updater UI**: Reworked the update notification for better clarity. Windows and AppImage users can download updates seamlessly in-app, while unsupported Linux package users are gracefully redirected to the manual download page. Flatpak (.rpm) and Snap (.snap) users are not informed, because that updates are handled by their respective package managers. +- **Bundle Size**: Optimized assets and dependencies, reducing the overall application bundle size by approximately 30% for specific versions. +- **Updater UI**: Reworked the update notification for better clarity. Windows and AppImage users can download updates seamlessly in-app, while unsupported Linux package users are gracefully redirected to the manual download page. Flatpak and Snap users are not prompted, as updates are handled natively by their respective package managers. - **Toast Notifications**: Refactored the style and overall UX of toast alerts. - **Code Quality**: Implemented a linter to enforce coding standards. -### Bug Fixes -- **Auto-Updater**: Fixed a major issue causing 404 Not Found errors when attempting to download updates for `.AppImage` releases. -- **Linux Updates**: Fixed update crashes on `.rpm` and `.snap` packages. In-app update flows are now disabled for these formats to let the OS natively handle the process. +### Fixes +- **Auto-Updater**: Fixed a major issue causing `404 Not Found` errors when attempting to download updates for `.AppImage` releases. +- **Linux Updates**: Fixed update crashes on `.rpm` and `.snap` packages. In-app update flows are now disabled for these formats to let the OS handle the process natively. - **Windows Logs**: Fixed a file naming issue with the log transport system on Windows. - **Desktop Entry Name**: Fixed the desktop entry name for Linux users, ensuring it displays correctly in application menus. +- **Custom Top Bar**: Removed the `customTopBar` settings. +- **Auto Update**: Removed the `autoAppUpdate` settings. +- **UI (System Tray)**: Removed the ***[EXPERIMENTAL]*** label from the config panel. ### Chores - **Dependencies**: Removed `dotenv` from the project. -- **URL**: A new URL for the website is now available ! [freedomloader.acnolo.fr](https://freedomloader.acnolo.fr) -- **Dev**: Add new npm script. To check linux file quality - \ No newline at end of file +- **Website**: A brand new website URL is now available: [freedomloader.acnolo.fr](https://freedomloader.acnolo.fr) +- **Dev**: Added a new npm script to check Linux file quality. \ No newline at end of file