diff --git a/apps/nextjs/next.config.ts b/apps/nextjs/next.config.ts index 5e36315b1..5a082309c 100644 --- a/apps/nextjs/next.config.ts +++ b/apps/nextjs/next.config.ts @@ -1,13 +1,13 @@ // Importing env files here to validate on build -import "@homarr/auth/env.mjs"; -import "@homarr/db/env.mjs"; -import "@homarr/common/env.mjs"; +import "@homarr/auth/env"; +import "@homarr/db/env"; +import "@homarr/common/env"; import type { NextConfig } from "next"; import MillionLint from "@million/lint"; import createNextIntlPlugin from "next-intl/plugin"; -import "./src/env.mjs"; +import "./src/env.ts"; // Package path does not work... so we need to use relative path const withNextIntl = createNextIntlPlugin("../../packages/translation/src/request.ts"); diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 52d5c10b7..3d2042030 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -21,7 +21,7 @@ import type { AppRouter } from "@homarr/api"; import { clientApi, getTrpcUrl } from "@homarr/api/client"; import { createHeadersCallbackForSource } from "@homarr/api/shared"; -import { env } from "~/env.mjs"; +import { env } from "~/env"; const getWebSocketProtocol = () => { // window is not defined on server side diff --git a/apps/nextjs/src/app/[locale]/auth/login/page.tsx b/apps/nextjs/src/app/[locale]/auth/login/page.tsx index 9b5dfbd83..c9d913c08 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/page.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/page.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation"; import { Card, Center, Stack, Text, Title } from "@mantine/core"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { getScopedI18n } from "@homarr/translation/server"; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index e951a4318..b0657e1e1 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -28,7 +28,7 @@ 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.mjs"; +import { env } from "~/env"; import { useEditMode, useRequiredBoard } from "./_context"; export const BoardContentHeaderActions = () => { diff --git a/apps/nextjs/src/app/[locale]/layout.tsx b/apps/nextjs/src/app/[locale]/layout.tsx index 6469ddd39..8f145ee3c 100644 --- a/apps/nextjs/src/app/[locale]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/layout.tsx @@ -9,7 +9,7 @@ import "~/styles/scroll-area.scss"; import { notFound } from "next/navigation"; import { NextIntlClientProvider } from "next-intl"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { ModalProvider } from "@homarr/modals"; import { Notifications } from "@homarr/notifications"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx index 9609a2089..965bb61ed 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx @@ -5,7 +5,7 @@ import { IconExclamationCircle } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { everyoneGroup } from "@homarr/definitions"; diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx index bc3af8b27..bcffbb4e2 100644 --- a/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx +++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx @@ -5,7 +5,7 @@ import { db } from "@homarr/db"; import type { WidgetKind } from "@homarr/definitions"; import { widgetImports } from "@homarr/widgets"; -import { env } from "~/env.mjs"; +import { env } from "~/env"; import { WidgetPreviewPageContent } from "./_content"; interface Props { diff --git a/apps/nextjs/src/env.mjs b/apps/nextjs/src/env.ts similarity index 86% rename from apps/nextjs/src/env.mjs rename to apps/nextjs/src/env.ts index fffc18fda..72d1c3837 100644 --- a/apps/nextjs/src/env.mjs +++ b/apps/nextjs/src/env.ts @@ -1,6 +1,8 @@ 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), @@ -13,7 +15,7 @@ export const env = createEnv({ server: { // Comma separated list of docker hostnames that can be used to connect to query the docker endpoints (localhost:2375,host.docker.internal:2375, ...) DOCKER_HOSTNAMES: z.string().optional(), - DOCKER_PORTS: z.number().optional(), + DOCKER_PORTS: z.string().optional(), }, /** * Specify your client-side environment variables schema here. @@ -32,6 +34,5 @@ export const env = createEnv({ DOCKER_PORTS: process.env.DOCKER_PORTS, // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, }, - skipValidation: - Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint", + skipValidation: shouldSkipEnvValidation(), }); diff --git a/packages/api/src/router/invite/checks.ts b/packages/api/src/router/invite/checks.ts index 330faaae3..10585d8b2 100644 --- a/packages/api/src/router/invite/checks.ts +++ b/packages/api/src/router/invite/checks.ts @@ -1,6 +1,6 @@ import { TRPCError } from "@trpc/server"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; export const throwIfCredentialsDisabled = () => { if (!env.AUTH_PROVIDERS.includes("credentials")) { diff --git a/packages/api/src/router/test/group.spec.ts b/packages/api/src/router/test/group.spec.ts index ab5357e8f..e3d989ab7 100644 --- a/packages/api/src/router/test/group.spec.ts +++ b/packages/api/src/router/test/group.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; -import * as env from "@homarr/auth/env.mjs"; +import * as env from "@homarr/auth/env"; import { createId, eq } from "@homarr/db"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema"; import { createDb } from "@homarr/db/test"; diff --git a/packages/api/src/router/test/invite.spec.ts b/packages/api/src/router/test/invite.spec.ts index 886ef63df..c03d9a180 100644 --- a/packages/api/src/router/test/invite.spec.ts +++ b/packages/api/src/router/test/invite.spec.ts @@ -24,7 +24,7 @@ vi.mock("@homarr/auth", async () => { }); // Mock the env module to return the credentials provider -vi.mock("@homarr/auth/env.mjs", () => { +vi.mock("@homarr/auth/env", () => { return { env: { AUTH_PROVIDERS: ["credentials"], diff --git a/packages/api/src/router/test/user.spec.ts b/packages/api/src/router/test/user.spec.ts index 0a3aa8d4f..9aa6e1c1b 100644 --- a/packages/api/src/router/test/user.spec.ts +++ b/packages/api/src/router/test/user.spec.ts @@ -28,7 +28,7 @@ vi.mock("@homarr/auth", async () => { }); // Mock the env module to return the credentials provider -vi.mock("@homarr/auth/env.mjs", () => { +vi.mock("@homarr/auth/env", () => { return { env: { AUTH_PROVIDERS: ["credentials"], diff --git a/packages/auth/configuration.ts b/packages/auth/configuration.ts index 49dd7126a..96bba6723 100644 --- a/packages/auth/configuration.ts +++ b/packages/auth/configuration.ts @@ -8,7 +8,7 @@ import type { SupportedAuthProvider } from "@homarr/definitions"; import { createAdapter } from "./adapter"; import { createSessionCallback } from "./callbacks"; -import { env } from "./env.mjs"; +import { env } from "./env"; import { createSignInEventHandler } from "./events"; import { createCredentialsConfiguration, createLdapConfiguration } from "./providers/credentials/credentials-provider"; import { EmptyNextAuthProvider } from "./providers/empty/empty-provider"; diff --git a/packages/auth/env.mjs b/packages/auth/env.ts similarity index 75% rename from packages/auth/env.mjs rename to packages/auth/env.ts index 9105ee776..6d9aa3354 100644 --- a/packages/auth/env.mjs +++ b/packages/auth/env.ts @@ -1,10 +1,9 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; -const trueStrings = ["1", "yes", "t", "true"]; -const falseStrings = ["0", "no", "f", "false"]; +import { createBooleanSchema, createDurationSchema, shouldSkipEnvValidation } from "@homarr/common/env-validation"; +import { supportedAuthProviders } from "@homarr/definitions"; -const supportedAuthProviders = ["credentials", "oidc", "ldap"]; const authProvidersSchema = z .string() .min(1) @@ -14,7 +13,7 @@ const authProvidersSchema = z .toLowerCase() .split(",") .filter((provider) => { - if (supportedAuthProviders.includes(provider)) return true; + if (supportedAuthProviders.some((supportedProvider) => supportedProvider === provider)) return true; else if (!provider) console.log("One or more of the entries for AUTH_PROVIDER could not be parsed and/or returned null."); else console.log(`The value entered for AUTH_PROVIDER "${provider}" is incorrect.`); @@ -23,41 +22,7 @@ const authProvidersSchema = z ) .default("credentials"); -const createDurationSchema = (defaultValue) => - z - .string() - .regex(/^\d+[smhd]?$/) - .default(defaultValue) - .transform((duration) => { - const lastChar = duration[duration.length - 1]; - 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; - }); - -const booleanSchema = z - .string() - .default("false") - .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(".")}`); - }); - -const skipValidation = Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION); +const skipValidation = shouldSkipEnvValidation(); const authProviders = skipValidation ? [] : authProvidersSchema.parse(process.env.AUTH_PROVIDERS); export const env = createEnv({ @@ -71,7 +36,7 @@ export const env = createEnv({ AUTH_OIDC_CLIENT_ID: z.string().min(1), AUTH_OIDC_CLIENT_SECRET: z.string().min(1), AUTH_OIDC_CLIENT_NAME: z.string().min(1).default("OIDC"), - AUTH_OIDC_AUTO_LOGIN: booleanSchema, + AUTH_OIDC_AUTO_LOGIN: createBooleanSchema(false), AUTH_OIDC_SCOPE_OVERWRITE: z.string().min(1).default("openid email profile groups"), AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: z.string().optional(), diff --git a/packages/auth/events.ts b/packages/auth/events.ts index 31713ac7d..2c8c68dd7 100644 --- a/packages/auth/events.ts +++ b/packages/auth/events.ts @@ -8,7 +8,7 @@ import { groupMembers, groups, users } from "@homarr/db/schema"; import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions"; import { logger } from "@homarr/log"; -import { env } from "./env.mjs"; +import { env } from "./env"; import { extractProfileName } from "./providers/oidc/oidc-provider"; export const createSignInEventHandler = (db: Database): Exclude["signIn"] => { diff --git a/packages/auth/package.json b/packages/auth/package.json index e94c2cacf..a4c57c9eb 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -11,7 +11,7 @@ "./client": "./client.ts", "./server": "./server.ts", "./shared": "./shared.ts", - "./env.mjs": "./env.mjs" + "./env": "./env.ts" }, "main": "./index.ts", "types": "./index.ts", diff --git a/packages/auth/providers/check-provider.ts b/packages/auth/providers/check-provider.ts index fd483465b..b38e3c3ad 100644 --- a/packages/auth/providers/check-provider.ts +++ b/packages/auth/providers/check-provider.ts @@ -1,6 +1,6 @@ import type { SupportedAuthProvider } from "@homarr/definitions"; -import { env } from "../env.mjs"; +import { env } from "../env"; export const isProviderEnabled = (provider: SupportedAuthProvider) => { // The question mark is placed there because isProviderEnabled is called during static build of about page diff --git a/packages/auth/providers/credentials/authorization/ldap-authorization.ts b/packages/auth/providers/credentials/authorization/ldap-authorization.ts index 529d26ff2..bf793ba05 100644 --- a/packages/auth/providers/credentials/authorization/ldap-authorization.ts +++ b/packages/auth/providers/credentials/authorization/ldap-authorization.ts @@ -7,7 +7,7 @@ import { logger } from "@homarr/log"; import type { validation } from "@homarr/validation"; import { z } from "@homarr/validation"; -import { env } from "../../../env.mjs"; +import { env } from "../../../env"; import { LdapClient } from "../ldap-client"; export const authorizeWithLdapCredentialsAsync = async ( diff --git a/packages/auth/providers/credentials/ldap-client.ts b/packages/auth/providers/credentials/ldap-client.ts index 7c266360f..4dc48e2a8 100644 --- a/packages/auth/providers/credentials/ldap-client.ts +++ b/packages/auth/providers/credentials/ldap-client.ts @@ -3,7 +3,7 @@ import { Client } from "ldapts"; import { objectEntries } from "@homarr/common"; -import { env } from "../../env.mjs"; +import { env } from "../../env"; export interface BindOptions { distinguishedName: string; diff --git a/packages/auth/providers/filter-providers.ts b/packages/auth/providers/filter-providers.ts index 1ee2ad38e..236bfe6ac 100644 --- a/packages/auth/providers/filter-providers.ts +++ b/packages/auth/providers/filter-providers.ts @@ -1,6 +1,6 @@ import type { Provider } from "next-auth/providers"; -import { env } from "../env.mjs"; +import { env } from "../env"; export const filterProviders = (providers: Exclude unknown>[]) => { // During build this will be undefined, so we default to an empty array diff --git a/packages/auth/providers/oidc/oidc-provider.ts b/packages/auth/providers/oidc/oidc-provider.ts index 5d4570564..82328b3ff 100644 --- a/packages/auth/providers/oidc/oidc-provider.ts +++ b/packages/auth/providers/oidc/oidc-provider.ts @@ -2,7 +2,7 @@ import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapte import type { OIDCConfig } from "@auth/core/providers"; import type { Profile } from "@auth/core/types"; -import { env } from "../../env.mjs"; +import { env } from "../../env"; import { createRedirectUri } from "../../redirect"; export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig => ({ diff --git a/packages/auth/providers/test/ldap-authorization.spec.ts b/packages/auth/providers/test/ldap-authorization.spec.ts index 8d4a03ff9..50f35399e 100644 --- a/packages/auth/providers/test/ldap-authorization.spec.ts +++ b/packages/auth/providers/test/ldap-authorization.spec.ts @@ -9,7 +9,7 @@ import { createDb } from "@homarr/db/test"; import { authorizeWithLdapCredentialsAsync } from "../credentials/authorization/ldap-authorization"; import * as ldapClient from "../credentials/ldap-client"; -vi.mock("../../env.mjs", () => ({ +vi.mock("../../env", () => ({ env: { AUTH_LDAP_BIND_DN: "bind_dn", AUTH_LDAP_BIND_PASSWORD: "bind_password", diff --git a/packages/auth/test/events.spec.ts b/packages/auth/test/events.spec.ts index 2bd4ad921..a2783bad7 100644 --- a/packages/auth/test/events.spec.ts +++ b/packages/auth/test/events.spec.ts @@ -11,7 +11,7 @@ import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions"; import { createSignInEventHandler } from "../events"; -vi.mock("../env.mjs", () => { +vi.mock("../env", () => { return { env: { AUTH_OIDC_GROUPS_ATTRIBUTE: "someRandomGroupsKey", diff --git a/packages/common/env.mjs b/packages/common/env.ts similarity index 86% rename from packages/common/env.mjs rename to packages/common/env.ts index 4e62ec25d..fda1b7859 100644 --- a/packages/common/env.mjs +++ b/packages/common/env.ts @@ -2,6 +2,8 @@ import { randomBytes } from "crypto"; import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; +import { shouldSkipEnvValidation } from "./src/env-validation"; + const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`; export const env = createEnv({ @@ -23,6 +25,5 @@ export const env = createEnv({ runtimeEnv: { SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY, }, - skipValidation: - Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint", + skipValidation: shouldSkipEnvValidation(), }); diff --git a/packages/common/package.json b/packages/common/package.json index b9758e9ff..f1705a4ed 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -9,7 +9,8 @@ "./types": "./src/types.ts", "./server": "./src/server.ts", "./client": "./src/client.ts", - "./env.mjs": "./env.mjs" + "./env": "./env.ts", + "./env-validation": "./src/env-validation.ts" }, "typesVersions": { "*": { @@ -30,7 +31,8 @@ "dayjs": "^1.11.13", "next": "15.1.4", "react": "19.0.0", - "react-dom": "19.0.0" + "react-dom": "19.0.0", + "zod": "^3.24.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/common/src/encryption.ts b/packages/common/src/encryption.ts index e9a20b69c..a10451f9f 100644 --- a/packages/common/src/encryption.ts +++ b/packages/common/src/encryption.ts @@ -1,6 +1,6 @@ import crypto from "crypto"; -import { env } from "../env.mjs"; +import { env } from "../env"; const algorithm = "aes-256-cbc"; //Using AES encryption diff --git a/packages/common/src/env-validation.ts b/packages/common/src/env-validation.ts new file mode 100644 index 000000000..ff884bc0a --- /dev/null +++ b/packages/common/src/env-validation.ts @@ -0,0 +1,42 @@ +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; + }); + +export const shouldSkipEnvValidation = () => + Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint"; diff --git a/packages/cron-jobs/src/jobs/session-cleanup.ts b/packages/cron-jobs/src/jobs/session-cleanup.ts index cbfe8533d..72f6c88fe 100644 --- a/packages/cron-jobs/src/jobs/session-cleanup.ts +++ b/packages/cron-jobs/src/jobs/session-cleanup.ts @@ -1,4 +1,4 @@ -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { NEVER } from "@homarr/cron-jobs-core/expressions"; import { db, eq, inArray } from "@homarr/db"; import { sessions, users } from "@homarr/db/schema"; diff --git a/packages/db/configs/mysql.config.ts b/packages/db/configs/mysql.config.ts index c04425119..1eb3aa943 100644 --- a/packages/db/configs/mysql.config.ts +++ b/packages/db/configs/mysql.config.ts @@ -1,6 +1,6 @@ import type { Config } from "drizzle-kit"; -import { env } from "../env.mjs"; +import { env } from "../env"; export default { dialect: "mysql", diff --git a/packages/db/configs/sqlite.config.ts b/packages/db/configs/sqlite.config.ts index 052c926bb..6b38860f2 100644 --- a/packages/db/configs/sqlite.config.ts +++ b/packages/db/configs/sqlite.config.ts @@ -1,6 +1,6 @@ import type { Config } from "drizzle-kit"; -import { env } from "../env.mjs"; +import { env } from "../env"; export default { dialect: "sqlite", diff --git a/packages/db/driver.ts b/packages/db/driver.ts index 8e54f07b5..643651d78 100644 --- a/packages/db/driver.ts +++ b/packages/db/driver.ts @@ -7,7 +7,7 @@ import mysql from "mysql2"; import { logger } from "@homarr/log"; -import { env } from "./env.mjs"; +import { env } from "./env"; import * as mysqlSchema from "./schema/mysql"; import * as sqliteSchema from "./schema/sqlite"; diff --git a/packages/db/env.mjs b/packages/db/env.ts similarity index 87% rename from packages/db/env.mjs rename to packages/db/env.ts index fb5ec2923..4c5ae0319 100644 --- a/packages/db/env.mjs +++ b/packages/db/env.ts @@ -1,12 +1,14 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; +import { shouldSkipEnvValidation } from "@homarr/common/env-validation"; + const drivers = { betterSqlite3: "better-sqlite3", mysql2: "mysql2", -}; +} as const; -const isDriver = (driver) => process.env.DB_DRIVER === driver; +const isDriver = (driver: (typeof drivers)[keyof typeof drivers]) => process.env.DB_DRIVER === driver; const isUsingDbHost = Boolean(process.env.DB_HOST); const onlyAllowUrl = isDriver(drivers.betterSqlite3); const urlRequired = onlyAllowUrl || !isUsingDbHost; @@ -55,6 +57,5 @@ export const env = createEnv({ DB_NAME: process.env.DB_NAME, DB_PORT: process.env.DB_PORT, }, - skipValidation: - Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint", + skipValidation: shouldSkipEnvValidation(), }); diff --git a/packages/db/migrations/mysql/migrate.ts b/packages/db/migrations/mysql/migrate.ts index daca71ee7..4b2616c49 100644 --- a/packages/db/migrations/mysql/migrate.ts +++ b/packages/db/migrations/mysql/migrate.ts @@ -3,7 +3,7 @@ import { migrate } from "drizzle-orm/mysql2/migrator"; import mysql from "mysql2"; import type { Database } from "../.."; -import { env } from "../../env.mjs"; +import { env } from "../../env"; import * as mysqlSchema from "../../schema/mysql"; import { seedDataAsync } from "../seed"; diff --git a/packages/db/migrations/sqlite/migrate.ts b/packages/db/migrations/sqlite/migrate.ts index 803f36215..e075a8d14 100644 --- a/packages/db/migrations/sqlite/migrate.ts +++ b/packages/db/migrations/sqlite/migrate.ts @@ -2,7 +2,7 @@ import Database from "better-sqlite3"; import { drizzle } from "drizzle-orm/better-sqlite3"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; -import { env } from "../../env.mjs"; +import { env } from "../../env"; import * as sqliteSchema from "../../schema/sqlite"; import { seedDataAsync } from "../seed"; diff --git a/packages/db/package.json b/packages/db/package.json index a33036b75..e26e15ed1 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -11,7 +11,7 @@ "./test": "./test/index.ts", "./queries": "./queries/index.ts", "./validationSchemas": "./validationSchemas.ts", - "./env.mjs": "./env.mjs" + "./env": "./env.ts" }, "main": "./index.ts", "types": "./index.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2e1786d7..505a53ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -716,6 +716,9 @@ importers: react-dom: specifier: 19.0.0 version: 19.0.0(react@19.0.0) + zod: + specifier: ^3.24.1 + version: 3.24.1 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0