From 25e9cc1125a88cb28608788ae5169370b61cf8df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:44:23 +0000 Subject: [PATCH] fix: remove legacy apps/websocket and apps/tasks, exclude build artifacts from tsconfig - Remove apps/websocket/ (merged into custom server in server.ts) - Remove apps/tasks/ (merged into custom server in server.ts) - Exclude customServer.cjs and customServer.css from tsconfig to prevent tsc errors when build artifacts are present - Update pnpm-lock.yaml (removed 138 unnecessary packages) Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com> Agent-Logs-Url: https://github.com/homarr-labs/homarr/sessions/bd83b347-563b-494e-b8c6-91fcc199dd79 --- apps/nextjs/tsconfig.json | 2 +- apps/tasks/eslint.config.js | 9 - apps/tasks/package.json | 58 ------- apps/tasks/src/job-manager.ts | 128 -------------- apps/tasks/src/main.ts | 52 ------ apps/tasks/src/on-start/index.ts | 7 - .../invalidate-update-checker-cache.ts | 18 -- apps/tasks/src/on-start/session-cleanup.ts | 41 ----- apps/tasks/src/overrides.ts | 5 - apps/tasks/tsconfig.json | 13 -- apps/websocket/eslint.config.js | 9 - apps/websocket/package.json | 42 ----- apps/websocket/src/main.ts | 66 ------- apps/websocket/tsconfig.json | 13 -- pnpm-lock.yaml | 161 ------------------ 15 files changed, 1 insertion(+), 623 deletions(-) delete mode 100644 apps/tasks/eslint.config.js delete mode 100644 apps/tasks/package.json delete mode 100644 apps/tasks/src/job-manager.ts delete mode 100644 apps/tasks/src/main.ts delete mode 100644 apps/tasks/src/on-start/index.ts delete mode 100644 apps/tasks/src/on-start/invalidate-update-checker-cache.ts delete mode 100644 apps/tasks/src/on-start/session-cleanup.ts delete mode 100644 apps/tasks/src/overrides.ts delete mode 100644 apps/tasks/tsconfig.json delete mode 100644 apps/websocket/eslint.config.js delete mode 100644 apps/websocket/package.json delete mode 100644 apps/websocket/src/main.ts delete mode 100644 apps/websocket/tsconfig.json diff --git a/apps/nextjs/tsconfig.json b/apps/nextjs/tsconfig.json index 916d4280c..e6ae095f7 100644 --- a/apps/nextjs/tsconfig.json +++ b/apps/nextjs/tsconfig.json @@ -14,5 +14,5 @@ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, "include": [".", ".next/types/**/*.ts"], - "exclude": ["node_modules", ".next"] + "exclude": ["node_modules", ".next", "customServer.cjs", "customServer.css"] } diff --git a/apps/tasks/eslint.config.js b/apps/tasks/eslint.config.js deleted file mode 100644 index f44e21cd5..000000000 --- a/apps/tasks/eslint.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import baseConfig from "@homarr/eslint-config/base"; - -/** @type {import('typescript-eslint').Config} */ -export default [ - { - ignores: ["tasks.cjs"], - }, - ...baseConfig, -]; diff --git a/apps/tasks/package.json b/apps/tasks/package.json deleted file mode 100644 index 96fbf67ea..000000000 --- a/apps/tasks/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@homarr/tasks", - "version": "0.1.0", - "private": true, - "license": "Apache-2.0", - "type": "module", - "exports": { - ".": "./src/index.ts" - }, - "main": "./src/main.ts", - "types": "./src/main.ts", - "scripts": { - "build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:*.node --external:@opentelemetry/api --external:deasync --external:bcrypt --outfile=tasks.cjs", - "clean": "rm -rf .turbo node_modules", - "dev": "echo 'Tasks server is now integrated into the Next.js custom server'", - "format": "prettier --check . --ignore-path ../../.gitignore", - "lint": "eslint", - "typecheck": "tsc --noEmit", - "with-env": "dotenv -e ../../.env --" - }, - "prettier": "@homarr/prettier-config", - "dependencies": { - "@homarr/analytics": "workspace:^0.1.0", - "@homarr/auth": "workspace:^0.1.0", - "@homarr/common": "workspace:^0.1.0", - "@homarr/core": "workspace:^", - "@homarr/cron-job-api": "workspace:^0.1.0", - "@homarr/cron-jobs": "workspace:^0.1.0", - "@homarr/cron-jobs-core": "workspace:^0.1.0", - "@homarr/db": "workspace:^0.1.0", - "@homarr/definitions": "workspace:^0.1.0", - "@homarr/icons": "workspace:^0.1.0", - "@homarr/integrations": "workspace:^0.1.0", - "@homarr/ping": "workspace:^0.1.0", - "@homarr/redis": "workspace:^0.1.0", - "@homarr/request-handler": "workspace:^0.1.0", - "@homarr/server-settings": "workspace:^0.1.0", - "@homarr/validation": "workspace:^0.1.0", - "@homarr/widgets": "workspace:^0.1.0", - "dayjs": "^1.11.19", - "dotenv": "^17.3.1", - "fastify": "^5.8.2", - "superjson": "2.2.6", - "undici": "7.18.2" - }, - "devDependencies": { - "@homarr/eslint-config": "workspace:^0.2.0", - "@homarr/prettier-config": "workspace:^0.1.0", - "@homarr/tsconfig": "workspace:^0.1.0", - "@types/node": "^24.12.0", - "dotenv-cli": "^11.0.0", - "esbuild": "^0.27.3", - "eslint": "^9.39.4", - "prettier": "^3.8.1", - "tsx": "4.20.4", - "typescript": "^5.9.3" - } -} diff --git a/apps/tasks/src/job-manager.ts b/apps/tasks/src/job-manager.ts deleted file mode 100644 index f4ec8bf97..000000000 --- a/apps/tasks/src/job-manager.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { schedule, validate as validateCron } from "node-cron"; - -import { createLogger } from "@homarr/core/infrastructure/logs"; -import type { IJobManager } from "@homarr/cron-job-api"; -import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs"; -import type { Database, InferInsertModel } from "@homarr/db"; -import { eq } from "@homarr/db"; -import { cronJobConfigurations } from "@homarr/db/schema"; - -const logger = createLogger({ module: "jobManager" }); - -export class JobManager implements IJobManager { - constructor( - private db: Database, - private jobGroup: typeof cronJobGroup, - ) {} - - public async startAsync(name: JobGroupKeys): Promise { - await this.jobGroup.startAsync(name); - } - public async triggerAsync(name: JobGroupKeys): Promise { - await this.jobGroup.runManuallyAsync(name); - } - public async stopAsync(name: JobGroupKeys): Promise { - await this.jobGroup.stopAsync(name); - } - public async updateIntervalAsync(name: JobGroupKeys, cron: string): Promise { - logger.info("Updating cron job interval", { name, expression: cron }); - const job = this.jobGroup.getJobRegistry().get(name); - if (!job) throw new Error(`Job ${name} not found`); - if (!validateCron(cron)) { - throw new Error(`Invalid cron expression: ${cron}`); - } - if (job.preventCustomInterval && cron !== job.cronExpression) { - throw new Error(`Custom cron expressions are not allowed for job ${name}`); - } - - await this.updateConfigurationAsync(name, { cronExpression: cron }); - await this.jobGroup.getTask(name)?.destroy(); - - this.jobGroup.setTask( - name, - schedule(cron, () => void job.executeAsync(), { - name, - }), - ); - logger.info("Cron job interval updated", { name, expression: cron }); - } - public async disableAsync(name: JobGroupKeys): Promise { - logger.info("Disabling cron job", { name }); - const job = this.jobGroup.getJobRegistry().get(name); - if (!job) throw new Error(`Job ${name} not found`); - - await this.updateConfigurationAsync(name, { isEnabled: false }); - await this.jobGroup.stopAsync(name); - logger.info("Cron job disabled", { name }); - } - public async enableAsync(name: JobGroupKeys): Promise { - logger.info("Enabling cron job", { name }); - await this.updateConfigurationAsync(name, { isEnabled: true }); - await this.jobGroup.startAsync(name); - logger.info("Cron job enabled", { name }); - } - - private async updateConfigurationAsync( - name: JobGroupKeys, - configuration: Omit>, "name">, - ) { - const existingConfig = await this.db.query.cronJobConfigurations.findFirst({ - where: (table, { eq }) => eq(table.name, name), - }); - - logger.debug("Updating cron job configuration", { - name, - configuration: JSON.stringify(configuration), - exists: Boolean(existingConfig), - }); - - if (existingConfig) { - await this.db - .update(cronJobConfigurations) - // prevent updating the name, as it is the primary key - .set({ ...configuration, name: undefined }) - .where(eq(cronJobConfigurations.name, name)); - logger.debug("Cron job configuration updated", { - name, - configuration: JSON.stringify(configuration), - }); - return; - } - - const job = this.jobGroup.getJobRegistry().get(name); - if (!job) throw new Error(`Job ${name} not found`); - - await this.db.insert(cronJobConfigurations).values({ - name, - cronExpression: configuration.cronExpression ?? job.cronExpression, - isEnabled: configuration.isEnabled ?? true, - }); - logger.debug("Cron job configuration updated", { - name, - configuration: JSON.stringify(configuration), - }); - } - - public async getAllAsync(): Promise< - { - name: JobGroupKeys; - cron: string; - preventManualExecution: boolean; - preventCustomInterval: boolean; - isEnabled: boolean; - }[] - > { - const configurations = await this.db.query.cronJobConfigurations.findMany(); - - return [...this.jobGroup.getJobRegistry().entries()].map(([name, job]) => { - const config = configurations.find((config) => config.name === name); - return { - name, - cron: config?.cronExpression ?? job.cronExpression, - preventManualExecution: job.preventManualExecution, - preventCustomInterval: job.preventCustomInterval, - isEnabled: config?.isEnabled ?? true, - }; - }); - } -} diff --git a/apps/tasks/src/main.ts b/apps/tasks/src/main.ts deleted file mode 100644 index 4c60f1646..000000000 --- a/apps/tasks/src/main.ts +++ /dev/null @@ -1,52 +0,0 @@ -// This import has to be the first import in the file so that the agent is overridden before any other modules are imported. -import "./overrides"; - -import type { FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify"; -import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify"; -import fastify from "fastify"; - -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import type { JobRouter } from "@homarr/cron-job-api"; -import { jobRouter } from "@homarr/cron-job-api"; -import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "@homarr/cron-job-api/constants"; -import { jobGroup } from "@homarr/cron-jobs"; -import { db } from "@homarr/db"; - -import { JobManager } from "./job-manager"; -import { onStartAsync } from "./on-start"; - -const logger = createLogger({ module: "tasksMain" }); - -const server = fastify({ - maxParamLength: 5000, -}); -server.register(fastifyTRPCPlugin, { - prefix: CRON_JOB_API_PATH, - trpcOptions: { - router: jobRouter, - createContext: ({ req }) => ({ - manager: new JobManager(db, jobGroup), - apiKey: req.headers[CRON_JOB_API_KEY_HEADER] as string | undefined, - }), - onError({ path, error }) { - logger.error(new ErrorWithMetadata("Error in tasks tRPC handler", { path }, { cause: error })); - }, - } satisfies FastifyTRPCPluginOptions["trpcOptions"], -}); - -void (async () => { - await onStartAsync(); - await jobGroup.initializeAsync(); - await jobGroup.startAllAsync(); - - try { - await server.listen({ port: CRON_JOB_API_PORT }); - logger.info("Tasks web server started successfully", { port: CRON_JOB_API_PORT }); - } catch (err) { - logger.error( - new ErrorWithMetadata("Failed to start tasks web server", { port: CRON_JOB_API_PORT }, { cause: err }), - ); - process.exit(1); - } -})(); diff --git a/apps/tasks/src/on-start/index.ts b/apps/tasks/src/on-start/index.ts deleted file mode 100644 index cb9564d1b..000000000 --- a/apps/tasks/src/on-start/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { invalidateUpdateCheckerCacheAsync } from "./invalidate-update-checker-cache"; -import { cleanupSessionsAsync } from "./session-cleanup"; - -export async function onStartAsync() { - await cleanupSessionsAsync(); - await invalidateUpdateCheckerCacheAsync(); -} diff --git a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts b/apps/tasks/src/on-start/invalidate-update-checker-cache.ts deleted file mode 100644 index 470989dcf..000000000 --- a/apps/tasks/src/on-start/invalidate-update-checker-cache.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; - -const logger = createLogger({ module: "invalidateUpdateCheckerCache" }); - -/** - * Invalidates the update checker cache on startup to ensure fresh data. - * It is important as we want to avoid showing pending updates after the update to latest version. - */ -export async function invalidateUpdateCheckerCacheAsync() { - try { - const handler = updateCheckerRequestHandler.handler({}); - await handler.invalidateAsync(); - logger.debug("Update checker cache invalidated"); - } catch (error) { - logger.error(new Error("Failed to invalidate update checker cache", { cause: error })); - } -} diff --git a/apps/tasks/src/on-start/session-cleanup.ts b/apps/tasks/src/on-start/session-cleanup.ts deleted file mode 100644 index 3ef6d9cdc..000000000 --- a/apps/tasks/src/on-start/session-cleanup.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { env } from "@homarr/auth/env"; -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { db, eq, inArray } from "@homarr/db"; -import { sessions, users } from "@homarr/db/schema"; -import { supportedAuthProviders } from "@homarr/definitions"; - -const logger = createLogger({ module: "sessionCleanup" }); - -/** - * Deletes sessions for users that have inactive auth providers. - * Sessions from other providers are deleted so they can no longer be used. - */ -export async function cleanupSessionsAsync() { - try { - const currentAuthProviders = env.AUTH_PROVIDERS; - - const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider)); - const subQuery = db - .select({ id: users.id }) - .from(users) - .where(inArray(users.provider, inactiveAuthProviders)) - .as("sq"); - const sessionsWithInactiveProviders = await db - .select({ userId: sessions.userId }) - .from(sessions) - .rightJoin(subQuery, eq(sessions.userId, subQuery.id)); - - const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null); - await db.delete(sessions).where(inArray(sessions.userId, userIds)); - - if (sessionsWithInactiveProviders.length > 0) { - logger.info("Deleted sessions for inactive providers", { - count: userIds.length, - }); - } else { - logger.debug("No sessions to delete"); - } - } catch (error) { - logger.error(new Error("Failed to clean up sessions", { cause: error })); - } -} diff --git a/apps/tasks/src/overrides.ts b/apps/tasks/src/overrides.ts deleted file mode 100644 index 684531f81..000000000 --- a/apps/tasks/src/overrides.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { setGlobalDispatcher } from "undici"; - -import { UndiciHttpAgent } from "@homarr/core/infrastructure/http"; - -setGlobalDispatcher(new UndiciHttpAgent()); diff --git a/apps/tasks/tsconfig.json b/apps/tasks/tsconfig.json deleted file mode 100644 index 51e14eb17..000000000 --- a/apps/tasks/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@homarr/tsconfig/base.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "~/*": ["src/*"], - "@homarr/node-unifi": ["../../node_modules/@types/node-unifi"] - }, - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - }, - "include": ["."], - "exclude": ["node_modules"] -} diff --git a/apps/websocket/eslint.config.js b/apps/websocket/eslint.config.js deleted file mode 100644 index 984f78162..000000000 --- a/apps/websocket/eslint.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import baseConfig from "@homarr/eslint-config/base"; - -/** @type {import('typescript-eslint').Config} */ -export default [ - { - ignores: ["wssServer.cjs"], - }, - ...baseConfig, -]; diff --git a/apps/websocket/package.json b/apps/websocket/package.json deleted file mode 100644 index 39ee27ae9..000000000 --- a/apps/websocket/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@homarr/websocket", - "version": "0.1.0", - "private": true, - "license": "Apache-2.0", - "type": "module", - "main": "./src/main.ts", - "types": "./src/main.ts", - "scripts": { - "build": "esbuild src/main.ts --bundle --platform=node --outfile=wssServer.cjs --external:bcrypt --external:@opentelemetry/api --external:deasync --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text", - "clean": "rm -rf .turbo node_modules", - "dev": "echo 'WebSocket server is now integrated into the Next.js custom server'", - "format": "prettier --check . --ignore-path ../../.gitignore", - "lint": "eslint", - "typecheck": "tsc --noEmit", - "with-env": "dotenv -e ../../.env --" - }, - "prettier": "@homarr/prettier-config", - "dependencies": { - "@homarr/api": "workspace:^0.1.0", - "@homarr/auth": "workspace:^0.1.0", - "@homarr/common": "workspace:^0.1.0", - "@homarr/core": "workspace:^", - "@homarr/db": "workspace:^0.1.0", - "@homarr/definitions": "workspace:^0.1.0", - "@homarr/redis": "workspace:^0.1.0", - "@homarr/validation": "workspace:^0.1.0", - "dotenv": "^17.3.1", - "tsx": "4.20.4", - "ws": "^8.19.0" - }, - "devDependencies": { - "@homarr/eslint-config": "workspace:^0.2.0", - "@homarr/prettier-config": "workspace:^0.1.0", - "@homarr/tsconfig": "workspace:^0.1.0", - "@types/ws": "^8.18.1", - "esbuild": "^0.27.3", - "eslint": "^9.39.4", - "prettier": "^3.8.1", - "typescript": "^5.9.3" - } -} diff --git a/apps/websocket/src/main.ts b/apps/websocket/src/main.ts deleted file mode 100644 index 45dfe50a2..000000000 --- a/apps/websocket/src/main.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { applyWSSHandler } from "@trpc/server/adapters/ws"; -import { WebSocketServer } from "ws"; - -import { appRouter, createTRPCContext } from "@homarr/api/websocket"; -import { getSessionFromToken, sessionTokenCookieName } from "@homarr/auth"; -import { parseCookies } from "@homarr/common"; -import { createLogger } from "@homarr/core/infrastructure/logs"; -import { db } from "@homarr/db"; - -const logger = createLogger({ module: "websocketMain" }); - -const wss = new WebSocketServer({ - port: 3001, -}); -const handler = applyWSSHandler({ - wss, - router: appRouter, - // ignore error on next line because the createContext must be set with this name - // eslint-disable-next-line no-restricted-syntax - createContext: async ({ req }) => { - try { - const headers = Object.entries(req.headers).map( - ([key, value]) => [key, typeof value === "string" ? value : value?.[0]] as [string, string], - ); - const nextHeaders = new Headers(headers); - - const store = parseCookies(nextHeaders.get("cookie") ?? ""); - const sessionToken = store[sessionTokenCookieName]; - - const session = await getSessionFromToken(db, sessionToken); - - return createTRPCContext({ - headers: nextHeaders, - session, - }); - } catch (error) { - logger.error(error); - return createTRPCContext({ - headers: new Headers(), - session: null, - }); - } - }, - // Enable heartbeat messages to keep connection open (disabled by default) - keepAlive: { - enabled: true, - // server ping message interval in milliseconds - pingMs: 30000, - // connection is terminated if pong message is not received in this many milliseconds - pongWaitMs: 5000, - }, -}); - -wss.on("connection", (websocket, incomingMessage) => { - logger.info(`➕ Connection (${wss.clients.size}) ${incomingMessage.method} ${incomingMessage.url}`); - websocket.once("close", (code, reason) => { - logger.info(`➖ Connection (${wss.clients.size}) ${code} ${reason.toString()}`); - }); -}); -logger.info("✅ WebSocket Server listening on ws://localhost:3001"); - -process.on("SIGTERM", () => { - logger.info("SIGTERM"); - handler.broadcastReconnectNotification(); - wss.close(); -}); diff --git a/apps/websocket/tsconfig.json b/apps/websocket/tsconfig.json deleted file mode 100644 index 51e14eb17..000000000 --- a/apps/websocket/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@homarr/tsconfig/base.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "~/*": ["src/*"], - "@homarr/node-unifi": ["../../node_modules/@types/node-unifi"] - }, - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - }, - "include": ["."], - "exclude": ["node_modules"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7d644c12..aeed84569 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -402,167 +402,6 @@ importers: specifier: ^5.9.3 version: 5.9.3 - apps/tasks: - dependencies: - '@homarr/analytics': - specifier: workspace:^0.1.0 - version: link:../../packages/analytics - '@homarr/auth': - specifier: workspace:^0.1.0 - version: link:../../packages/auth - '@homarr/common': - specifier: workspace:^0.1.0 - version: link:../../packages/common - '@homarr/core': - specifier: workspace:^ - version: link:../../packages/core - '@homarr/cron-job-api': - specifier: workspace:^0.1.0 - version: link:../../packages/cron-job-api - '@homarr/cron-jobs': - specifier: workspace:^0.1.0 - version: link:../../packages/cron-jobs - '@homarr/cron-jobs-core': - specifier: workspace:^0.1.0 - version: link:../../packages/cron-jobs-core - '@homarr/db': - specifier: workspace:^0.1.0 - version: link:../../packages/db - '@homarr/definitions': - specifier: workspace:^0.1.0 - version: link:../../packages/definitions - '@homarr/icons': - specifier: workspace:^0.1.0 - version: link:../../packages/icons - '@homarr/integrations': - specifier: workspace:^0.1.0 - version: link:../../packages/integrations - '@homarr/ping': - specifier: workspace:^0.1.0 - version: link:../../packages/ping - '@homarr/redis': - specifier: workspace:^0.1.0 - version: link:../../packages/redis - '@homarr/request-handler': - specifier: workspace:^0.1.0 - version: link:../../packages/request-handler - '@homarr/server-settings': - specifier: workspace:^0.1.0 - version: link:../../packages/server-settings - '@homarr/validation': - specifier: workspace:^0.1.0 - version: link:../../packages/validation - '@homarr/widgets': - specifier: workspace:^0.1.0 - version: link:../../packages/widgets - dayjs: - specifier: ^1.11.19 - version: 1.11.19 - dotenv: - specifier: ^17.3.1 - version: 17.3.1 - fastify: - specifier: ^5.8.2 - version: 5.8.2 - superjson: - specifier: 2.2.6 - version: 2.2.6 - undici: - specifier: 7.18.2 - version: 7.18.2 - devDependencies: - '@homarr/eslint-config': - specifier: workspace:^0.2.0 - version: link:../../tooling/eslint - '@homarr/prettier-config': - specifier: workspace:^0.1.0 - version: link:../../tooling/prettier - '@homarr/tsconfig': - specifier: workspace:^0.1.0 - version: link:../../tooling/typescript - '@types/node': - specifier: ^24.12.0 - version: 24.12.0 - dotenv-cli: - specifier: ^11.0.0 - version: 11.0.0 - esbuild: - specifier: ^0.27.3 - version: 0.27.3 - eslint: - specifier: ^9.39.4 - version: 9.39.4 - prettier: - specifier: ^3.8.1 - version: 3.8.1 - tsx: - specifier: 4.20.4 - version: 4.20.4 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - apps/websocket: - dependencies: - '@homarr/api': - specifier: workspace:^0.1.0 - version: link:../../packages/api - '@homarr/auth': - specifier: workspace:^0.1.0 - version: link:../../packages/auth - '@homarr/common': - specifier: workspace:^0.1.0 - version: link:../../packages/common - '@homarr/core': - specifier: workspace:^ - version: link:../../packages/core - '@homarr/db': - specifier: workspace:^0.1.0 - version: link:../../packages/db - '@homarr/definitions': - specifier: workspace:^0.1.0 - version: link:../../packages/definitions - '@homarr/redis': - specifier: workspace:^0.1.0 - version: link:../../packages/redis - '@homarr/validation': - specifier: workspace:^0.1.0 - version: link:../../packages/validation - dotenv: - specifier: ^17.3.1 - version: 17.3.1 - tsx: - specifier: 4.20.4 - version: 4.20.4 - ws: - specifier: ^8.19.0 - version: 8.19.0 - devDependencies: - '@homarr/eslint-config': - specifier: workspace:^0.2.0 - version: link:../../tooling/eslint - '@homarr/prettier-config': - specifier: workspace:^0.1.0 - version: link:../../tooling/prettier - '@homarr/tsconfig': - specifier: workspace:^0.1.0 - version: link:../../tooling/typescript - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - esbuild: - specifier: ^0.27.3 - version: 0.27.3 - eslint: - specifier: ^9.39.4 - version: 9.39.4 - prettier: - specifier: ^3.8.1 - version: 3.8.1 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - packages/analytics: dependencies: '@homarr/auth':