feat(logging): add log level env variable (#2299)

This commit is contained in:
Meier Lukas
2025-02-18 22:54:15 +01:00
committed by GitHub
parent 6420feee72
commit 67d48e11d7
31 changed files with 202 additions and 183 deletions

View File

@@ -12,6 +12,8 @@ AUTH_SECRET="supersecret"
# or starting the project without any (which will show a randomly generated one).
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
LOG_LEVEL='info'
# This is how you can use the sqlite driver:
DB_DRIVER='better-sqlite3'
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'

View File

@@ -2,14 +2,13 @@
import "@homarr/auth/env";
import "@homarr/db/env";
import "@homarr/common/env";
import "@homarr/log/env";
import "@homarr/docker/env";
import type { NextConfig } from "next";
import MillionLint from "@million/lint";
import createNextIntlPlugin from "next-intl/plugin";
import "./src/env.ts";
// Package path does not work... so we need to use relative path
const withNextIntl = createNextIntlPlugin("../../packages/translation/src/request.ts");

View File

@@ -55,7 +55,6 @@
"@mantine/modals": "^7.17.0",
"@mantine/tiptap": "^7.17.0",
"@million/lint": "1.0.14",
"@t3-oss/env-nextjs": "^0.12.0",
"@tabler/icons-react": "^3.30.0",
"@tanstack/react-query": "^5.66.7",
"@tanstack/react-query-devtools": "^5.66.7",

View File

@@ -20,8 +20,7 @@ import type { SuperJSONResult } from "superjson";
import type { AppRouter } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared";
import { env } from "~/env";
import { env } from "@homarr/common/env";
const getWebSocketProtocol = () => {
// window is not defined on server side
@@ -66,7 +65,7 @@ export function TRPCReactProvider(props: PropsWithChildren) {
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error),
env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error),
}),
splitLink({
condition: ({ type }) => type === "subscription",

View File

@@ -23,6 +23,7 @@ import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { env } from "@homarr/common/env";
import { useConfirmModal, useModalAction } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
@@ -33,7 +34,6 @@ import { useCategoryActions } from "~/components/board/sections/category/categor
import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal";
import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions";
import { HeaderButton } from "~/components/layout/header/button";
import { env } from "~/env";
export const BoardContentHeaderActions = () => {
const [isEditMode] = useEditMode();

View File

@@ -1,11 +1,11 @@
import { notFound } from "next/navigation";
import { Center } from "@mantine/core";
import { env } from "@homarr/common/env";
import { db } from "@homarr/db";
import type { WidgetKind } from "@homarr/definitions";
import { widgetImports } from "@homarr/widgets";
import { env } from "~/env";
import { WidgetPreviewPageContent } from "./_content";
interface Props {

View File

@@ -1,33 +0,0 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
export const env = createEnv({
shared: {
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
},
/**
* Specify your server-side environment variables schema here. This way you can ensure the app isn't
* built with invalid env vars.
*/
server: {},
/**
* Specify your client-side environment variables schema here.
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
runtimeEnv: {
PORT: process.env.PORT,
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},
skipValidation: shouldSkipEnvValidation(),
emptyStringAsUndefined: true,
});

View File

@@ -1,8 +1,8 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import { createBooleanSchema, createDurationSchema, shouldSkipEnvValidation } from "@homarr/common/env-validation";
import { supportedAuthProviders } from "@homarr/definitions";
import { createEnv } from "@homarr/env";
import { createBooleanSchema, createDurationSchema } from "@homarr/env/schemas";
const authProvidersSchema = z
.string()
@@ -22,8 +22,7 @@ const authProvidersSchema = z
)
.default("credentials");
const skipValidation = shouldSkipEnvValidation();
const authProviders = skipValidation ? [] : authProvidersSchema.parse(process.env.AUTH_PROVIDERS);
const authProviders = authProvidersSchema.safeParse(process.env.AUTH_PROVIDERS).data ?? [];
export const env = createEnv({
server: {
@@ -59,32 +58,5 @@ export const env = createEnv({
}
: {}),
},
client: {},
runtimeEnv: {
AUTH_LOGOUT_REDIRECT_URL: process.env.AUTH_LOGOUT_REDIRECT_URL,
AUTH_SESSION_EXPIRY_TIME: process.env.AUTH_SESSION_EXPIRY_TIME,
AUTH_PROVIDERS: process.env.AUTH_PROVIDERS,
AUTH_LDAP_BASE: process.env.AUTH_LDAP_BASE,
AUTH_LDAP_BIND_DN: process.env.AUTH_LDAP_BIND_DN,
AUTH_LDAP_BIND_PASSWORD: process.env.AUTH_LDAP_BIND_PASSWORD,
AUTH_LDAP_GROUP_CLASS: process.env.AUTH_LDAP_GROUP_CLASS,
AUTH_LDAP_GROUP_FILTER_EXTRA_ARG: process.env.AUTH_LDAP_GROUP_FILTER_EXTRA_ARG,
AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE: process.env.AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE,
AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE: process.env.AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE,
AUTH_LDAP_SEARCH_SCOPE: process.env.AUTH_LDAP_SEARCH_SCOPE,
AUTH_LDAP_URI: process.env.AUTH_LDAP_URI,
AUTH_OIDC_CLIENT_ID: process.env.AUTH_OIDC_CLIENT_ID,
AUTH_OIDC_CLIENT_NAME: process.env.AUTH_OIDC_CLIENT_NAME,
AUTH_OIDC_CLIENT_SECRET: process.env.AUTH_OIDC_CLIENT_SECRET,
AUTH_OIDC_ISSUER: process.env.AUTH_OIDC_ISSUER,
AUTH_OIDC_SCOPE_OVERWRITE: process.env.AUTH_OIDC_SCOPE_OVERWRITE,
AUTH_OIDC_GROUPS_ATTRIBUTE: process.env.AUTH_OIDC_GROUPS_ATTRIBUTE,
AUTH_LDAP_USERNAME_ATTRIBUTE: process.env.AUTH_LDAP_USERNAME_ATTRIBUTE,
AUTH_LDAP_USER_MAIL_ATTRIBUTE: process.env.AUTH_LDAP_USER_MAIL_ATTRIBUTE,
AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG: process.env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG,
AUTH_OIDC_AUTO_LOGIN: process.env.AUTH_OIDC_AUTO_LOGIN,
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: process.env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE,
},
skipValidation,
emptyStringAsUndefined: true,
experimental__runtimeEnv: process.env,
});

View File

@@ -28,9 +28,9 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@t3-oss/env-nextjs": "^0.12.0",
"bcrypt": "^5.1.1",
"cookies": "^0.9.1",
"ldapts": "7.3.1",

View File

@@ -6,10 +6,11 @@ import { rootCertificates } from "node:tls";
import axios from "axios";
import { fetch } from "undici";
import { env } from "@homarr/common/env";
import { LoggingAgent } from "@homarr/common/server";
const getCertificateFolder = () => {
return process.env.NODE_ENV === "production"
return env.NODE_ENV === "production"
? path.join("/appdata", "trusted-certificates")
: process.env.LOCAL_CERTIFICATE_PATH;
};

View File

@@ -1,12 +1,14 @@
import { randomBytes } from "crypto";
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import { shouldSkipEnvValidation } from "./src/env-validation";
import { createEnv } from "@homarr/env";
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
export const env = createEnv({
shared: {
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
},
server: {
SECRET_ENCRYPTION_KEY: z
.string({
@@ -24,7 +26,6 @@ export const env = createEnv({
},
runtimeEnv: {
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
NODE_ENV: process.env.NODE_ENV,
},
skipValidation: shouldSkipEnvValidation(),
emptyStringAsUndefined: true,
});

View File

@@ -27,6 +27,7 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"dayjs": "^1.11.13",
"next": "15.1.7",

View File

@@ -1,7 +1,7 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
import { env as commonEnv } from "@homarr/common/env";
import { createEnv } from "@homarr/env";
const drivers = {
betterSqlite3: "better-sqlite3",
@@ -29,7 +29,7 @@ export const env = createEnv({
? {
DB_URL:
// Fallback to the default sqlite file path in production
process.env.NODE_ENV === "production" && isDriver("better-sqlite3")
commonEnv.NODE_ENV === "production" && isDriver("better-sqlite3")
? z.string().default("/appdata/db/db.sqlite")
: z.string().nonempty(),
}
@@ -49,18 +49,5 @@ export const env = createEnv({
}
: {}),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
runtimeEnv: {
DB_DRIVER: process.env.DB_DRIVER,
DB_URL: process.env.DB_URL,
DB_HOST: process.env.DB_HOST,
DB_USER: process.env.DB_USER,
DB_PASSWORD: process.env.DB_PASSWORD,
DB_NAME: process.env.DB_NAME,
DB_PORT: process.env.DB_PORT,
},
skipValidation: shouldSkipEnvValidation(),
emptyStringAsUndefined: true,
experimental__runtimeEnv: process.env,
});

View File

@@ -40,11 +40,11 @@
"@auth/core": "^0.37.4",
"@homarr/common": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^7.17.0",
"@paralleldrive/cuid2": "^2.2.2",
"@t3-oss/env-nextjs": "^0.12.0",
"@testcontainers/mysql": "^10.18.0",
"better-sqlite3": "^11.8.1",
"dotenv": "^16.4.7",

View File

@@ -24,7 +24,7 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"@t3-oss/env-nextjs": "^0.12.0",
"@homarr/env": "workspace:^0.1.0",
"dockerode": "^4.0.4"
},
"devDependencies": {

View File

@@ -1,7 +1,6 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
import { createEnv } from "@homarr/env";
export const env = createEnv({
server: {
@@ -9,10 +8,5 @@ export const env = createEnv({
DOCKER_HOSTNAMES: z.string().optional(),
DOCKER_PORTS: z.string().optional(),
},
runtimeEnv: {
DOCKER_HOSTNAMES: process.env.DOCKER_HOSTNAMES,
DOCKER_PORTS: process.env.DOCKER_PORTS,
},
skipValidation: shouldSkipEnvValidation(),
emptyStringAsUndefined: true,
experimental__runtimeEnv: process.env,
});

9
packages/env/eslint.config.js vendored Normal file
View File

@@ -0,0 +1,9 @@
import baseConfig from "@homarr/eslint-config/base";
/** @type {import('typescript-eslint').Config} */
export default [
{
ignores: [],
},
...baseConfig,
];

1
packages/env/index.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./src";

36
packages/env/package.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "@homarr/env",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"exports": {
".": "./index.ts",
"./schemas": "./src/schemas.ts"
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
},
"scripts": {
"clean": "rm -rf .turbo node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@t3-oss/env-nextjs": "^0.12.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.20.1",
"typescript": "^5.7.3"
}
}

9
packages/env/src/index.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import { createEnv as createEnvT3 } from "@t3-oss/env-nextjs";
export const defaultEnvOptions = {
emptyStringAsUndefined: true,
skipValidation:
Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint",
} satisfies Partial<Parameters<typeof createEnvT3>[0]>;
export const createEnv: typeof createEnvT3 = (options) => createEnvT3({ ...defaultEnvOptions, ...options });

39
packages/env/src/schemas.ts vendored Normal file
View File

@@ -0,0 +1,39 @@
import { z } from "zod";
const trueStrings = ["1", "yes", "t", "true"];
const falseStrings = ["0", "no", "f", "false"];
export const createBooleanSchema = (defaultValue: boolean) =>
z
.string()
.default(defaultValue.toString())
.transform((value, ctx) => {
const normalized = value.trim().toLowerCase();
if (trueStrings.includes(normalized)) return true;
if (falseStrings.includes(normalized)) return false;
throw new Error(`Invalid boolean value for ${ctx.path.join(".")}`);
});
export const createDurationSchema = (defaultValue: `${number}${"s" | "m" | "h" | "d"}`) =>
z
.string()
.regex(/^\d+[smhd]?$/)
.default(defaultValue)
.transform((duration) => {
const lastChar = duration[duration.length - 1] as "s" | "m" | "h" | "d";
if (!isNaN(Number(lastChar))) {
return Number(defaultValue);
}
const multipliers = {
s: 1,
m: 60,
h: 60 * 60,
d: 60 * 60 * 24,
};
const numberDuration = Number(duration.slice(0, -1));
const multiplier = multipliers[lastChar];
return numberDuration * multiplier;
});

8
packages/env/tsconfig.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "@homarr/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}

View File

@@ -5,11 +5,8 @@
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./src/index.d.ts",
"default": "./src/index.mjs"
},
"./override": "./src/override.cjs"
".": "./src/index.ts",
"./env": "./src/env.ts"
},
"typesVersions": {
"*": {
@@ -26,9 +23,11 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/env": "workspace:^0.1.0",
"ioredis": "5.5.0",
"superjson": "2.2.2",
"winston": "3.17.0"
"winston": "3.17.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",

10
packages/log/src/env.ts Normal file
View File

@@ -0,0 +1,10 @@
import { z } from "zod";
import { createEnv } from "@homarr/env";
export const env = createEnv({
server: {
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
},
experimental__runtimeEnv: process.env,
});

View File

@@ -1,4 +0,0 @@
import type { Logger } from "winston";
// The following is just to make prettier happy
export const logger: Logger = undefined as unknown as Logger;

View File

@@ -1,12 +1,14 @@
import type { transport as Transport } from "winston";
import winston, { format, transports } from "winston";
import { RedisTransport } from "./redis-transport.mjs";
import { env } from "./env";
import { RedisTransport } from "./redis-transport";
const logMessageFormat = format.printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
return `${timestamp as string} ${level}: ${message as string}`;
});
const logTransports = [new transports.Console()];
const logTransports: Transport[] = [new transports.Console()];
// Only add the Redis transport if we are not in CI
if (!(Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS))) {
@@ -16,6 +18,7 @@ if (!(Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS))) {
const logger = winston.createLogger({
format: format.combine(format.colorize(), format.timestamp(), logMessageFormat),
transports: logTransports,
level: env.LOG_LEVEL,
});
export { logger };

View File

@@ -1,42 +0,0 @@
void (async () => {
const { logger } = await import("./index.mjs");
const nextLogger = require("next/dist/build/output/log");
const getWinstonMethodForConsole = (consoleMethod) => {
switch (consoleMethod) {
case "error":
return (...messages) => logger.error(messages.join(" "));
case "warn":
return (...messages) => logger.warn(messages.join(" "));
case "debug":
return (...messages) => logger.debug(messages.join(" "));
case "log":
case "info":
default:
return (...messages) => logger.info(messages.join(" "));
}
};
const consoleMethods = ["log", "debug", "info", "warn", "error"];
consoleMethods.forEach((method) => {
console[method] = getWinstonMethodForConsole(method);
});
const getWinstonMethodForNext = (nextMethod) => {
switch (nextMethod) {
case "error":
return (...messages) => logger.error(messages.join(" "));
case "warn":
return (...messages) => logger.warn(messages.join(" "));
case "trace":
return (...messages) => logger.info(messages.join(" "));
default:
return (...messages) => logger.info(messages.join(" "));
}
};
Object.keys(nextLogger.prefixes).forEach((method) => {
nextLogger[method] = getWinstonMethodForNext(method);
});
})();

View File

@@ -7,15 +7,12 @@ import Transport from "winston-transport";
// of the base functionality and `.exceptions.handle()`.
//
export class RedisTransport extends Transport {
/** @type {Redis} */
redis;
private redis: Redis | null = null;
/**
* Log the info to the Redis channel
* @param {{ message: string; timestamp: string; level: string; }} info
* @param {() => void} callback
*/
log(info, callback) {
log(info: { message: string; timestamp: string; level: string }, callback: () => void) {
setImmediate(() => {
this.emit("logged", info);
});

View File

@@ -3,6 +3,6 @@
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src", "index.mjs"],
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}

63
pnpm-lock.yaml generated
View File

@@ -202,9 +202,6 @@ importers:
'@million/lint':
specifier: 1.0.14
version: 1.0.14(rollup@4.21.3)(webpack-sources@3.2.3)
'@t3-oss/env-nextjs':
specifier: ^0.12.0
version: 0.12.0(typescript@5.7.3)(zod@3.24.2)
'@tabler/icons-react':
specifier: ^3.30.0
version: 3.30.0(react@19.0.0)
@@ -651,15 +648,15 @@ importers:
'@homarr/definitions':
specifier: workspace:^0.1.0
version: link:../definitions
'@homarr/env':
specifier: workspace:^0.1.0
version: link:../env
'@homarr/log':
specifier: workspace:^0.1.0
version: link:../log
'@homarr/validation':
specifier: workspace:^0.1.0
version: link:../validation
'@t3-oss/env-nextjs':
specifier: ^0.12.0
version: 0.12.0(typescript@5.7.3)(zod@3.24.2)
bcrypt:
specifier: ^5.1.1
version: 5.1.1
@@ -802,6 +799,9 @@ importers:
packages/common:
dependencies:
'@homarr/env':
specifier: workspace:^0.1.0
version: link:../env
'@homarr/log':
specifier: workspace:^0.1.0
version: link:../log
@@ -999,6 +999,9 @@ importers:
'@homarr/definitions':
specifier: workspace:^0.1.0
version: link:../definitions
'@homarr/env':
specifier: workspace:^0.1.0
version: link:../env
'@homarr/log':
specifier: workspace:^0.1.0
version: link:../log
@@ -1011,9 +1014,6 @@ importers:
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@t3-oss/env-nextjs':
specifier: ^0.12.0
version: 0.12.0(typescript@5.7.3)(zod@3.24.2)
'@testcontainers/mysql':
specifier: ^10.18.0
version: 10.18.0
@@ -1091,9 +1091,9 @@ importers:
'@homarr/common':
specifier: workspace:^0.1.0
version: link:../common
'@t3-oss/env-nextjs':
specifier: ^0.12.0
version: 0.12.0(typescript@5.7.3)(zod@3.24.2)
'@homarr/env':
specifier: workspace:^0.1.0
version: link:../env
dockerode:
specifier: ^4.0.4
version: 4.0.4
@@ -1117,6 +1117,31 @@ importers:
specifier: ^5.7.3
version: 5.7.3
packages/env:
dependencies:
'@t3-oss/env-nextjs':
specifier: ^0.12.0
version: 0.12.0(typescript@5.7.3)(zod@3.24.2)
zod:
specifier: ^3.24.2
version: 3.24.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
eslint:
specifier: ^9.20.1
version: 9.20.1
typescript:
specifier: ^5.7.3
version: 5.7.3
packages/form:
dependencies:
'@homarr/common':
@@ -1300,6 +1325,9 @@ importers:
packages/log:
dependencies:
'@homarr/env':
specifier: workspace:^0.1.0
version: link:../env
ioredis:
specifier: 5.5.0
version: 5.5.0
@@ -1309,6 +1337,9 @@ importers:
winston:
specifier: 3.17.0
version: 3.17.0
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
@@ -16475,7 +16506,7 @@ snapshots:
dependencies:
'@babel/code-frame': 7.26.2
index-to-position: 0.1.2
type-fest: 4.34.1
type-fest: 4.30.2
parse-ms@3.0.0: {}
@@ -17039,14 +17070,14 @@ snapshots:
dependencies:
find-up-simple: 1.0.0
read-pkg: 9.0.1
type-fest: 4.34.1
type-fest: 4.30.2
read-pkg@9.0.1:
dependencies:
'@types/normalize-package-data': 2.4.4
normalize-package-data: 6.0.2
parse-json: 8.1.0
type-fest: 4.34.1
type-fest: 4.30.2
unicorn-magic: 0.1.0
readable-stream@2.3.8:
@@ -18354,7 +18385,7 @@ snapshots:
consola: 3.2.3
defu: 6.1.4
mime: 3.0.0
node-fetch-native: 1.6.6
node-fetch-native: 1.6.4
pathe: 1.1.2
unicode-emoji-modifier-base@1.0.0: {}

View File

@@ -39,6 +39,7 @@
"NODE_ENV",
"PORT",
"LOCAL_CERTIFICATE_PATH",
"LOG_LEVEL",
"SECRET_ENCRYPTION_KEY",
"SKIP_ENV_VALIDATION"
],