diff --git a/web/.storybook/main.js b/web/.storybook/main.js index ac6c5eb840..3e8cfdf5be 100644 --- a/web/.storybook/main.js +++ b/web/.storybook/main.js @@ -3,13 +3,11 @@ * @import { StorybookConfig } from "@storybook/web-components-vite"; * @import { InlineConfig, Plugin } from "vite"; */ -import { cwd } from "process"; +import { createBundleDefinitions } from "@goauthentik/web/scripts/esbuild/environment"; import postcssLit from "rollup-plugin-postcss-lit"; import tsconfigPaths from "vite-tsconfig-paths"; -const NODE_ENV = process.env.NODE_ENV || "development"; - -const CSSImportPattern = /import [\w\$]+ from .+\.(css)/g; +const CSSImportPattern = /import [\w$]+ from .+\.(css)/g; const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/; /** @@ -54,11 +52,7 @@ const config = { */ const mergedConfig = { ...config, - define: { - "process.env.NODE_ENV": JSON.stringify(NODE_ENV), - "process.env.CWD": JSON.stringify(cwd()), - "process.env.AK_API_BASE_PATH": JSON.stringify(process.env.AK_API_BASE_PATH || ""), - }, + define: createBundleDefinitions(), plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()], }; diff --git a/web/packages/esbuild-plugin-live-reload/client/ESBuildObserver.js b/web/packages/esbuild-plugin-live-reload/client/ESBuildObserver.js index 2f8ebe3ce2..8a6b0a3990 100644 --- a/web/packages/esbuild-plugin-live-reload/client/ESBuildObserver.js +++ b/web/packages/esbuild-plugin-live-reload/client/ESBuildObserver.js @@ -21,7 +21,7 @@ const log = console.debug.bind(console, logPrefix); * ESBuild may tree-shake it out of production builds. * * ```ts - * if (process.env.NODE_ENV === "development") { + * if (import.meta.env.NODE_ENV=== "development") { * await import("@goauthentik/esbuild-plugin-live-reload/client") * .catch(() => console.warn("Failed to import watcher")) * } diff --git a/web/packages/esbuild-plugin-live-reload/client/types.d.ts b/web/packages/esbuild-plugin-live-reload/client/types.d.ts index c446181716..370d24a148 100644 --- a/web/packages/esbuild-plugin-live-reload/client/types.d.ts +++ b/web/packages/esbuild-plugin-live-reload/client/types.d.ts @@ -4,15 +4,20 @@ export {}; declare global { + /** + * Environment variables injected by ESBuild. + */ + interface ImportMetaEnv { + /** + * The injected watcher URL for ESBuild. + * This is used for live reloading in development mode. + * + * @format url + */ + readonly ESBUILD_WATCHER_URL?: string; + } + interface ImportMeta { - readonly env: { - /** - * The injected watcher URL for ESBuild. - * This is used for live reloading in development mode. - * - * @format url - */ - ESBUILD_WATCHER_URL: string; - }; + readonly env: ImportMetaEnv; } } diff --git a/web/packages/monorepo/constants.js b/web/packages/monorepo/constants.js deleted file mode 100644 index 6abf55e8a5..0000000000 --- a/web/packages/monorepo/constants.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @file Constants for JavaScript and TypeScript files. - */ - -/// - -/** - * The current Node.js environment, defaulting to "development" when not set. - * - * Note, this should only be used during the build process. - * - * If you need to check the environment at runtime, use `process.env.NODE_ENV` to - * ensure that module tree-shaking works correctly. - * - */ -export const NodeEnvironment = process.env.NODE_ENV || "development"; diff --git a/web/packages/monorepo/build.js b/web/packages/monorepo/environment.js similarity index 56% rename from web/packages/monorepo/build.js rename to web/packages/monorepo/environment.js index 343d522cd6..062c1a4f4c 100644 --- a/web/packages/monorepo/build.js +++ b/web/packages/monorepo/environment.js @@ -1,6 +1,20 @@ /** - * @file Utility functions for building and copying files. + * @file Utility functions for working with environment variables. */ +/// + +//#region Constants + +/** + * The current Node.js environment, defaulting to "development" when not set. + * + * Note, this should only be used during the build process. + * + * If you need to check the environment at runtime, use `process.env.NODE_ENV` to + * ensure that module tree-shaking works correctly. + * + */ +export const NodeEnvironment = process.env.NODE_ENV || "development"; /** * A source environment variable, which can be a string, number, boolean, null, or undefined. @@ -14,19 +28,26 @@ * @typedef {T extends string ? `"${T}"` : T} JSONify */ +//#endregion + +//#region Utilities + /** * Given an object of environment variables, returns a new object with the same keys and values, but * with the values serialized as strings. * * @template {Record} EnvRecord - * @template {string} [Prefix='process.env.'] + * @template {string} [Prefix='import.meta.env.'] * * @param {EnvRecord} input - * @param {Prefix} [prefix='process.env.'] + * @param {Prefix} [prefix='import.meta.env.'] * * @returns {{[K in keyof EnvRecord as `${Prefix}${K}`]: JSONify}} */ -export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) { +export function serializeEnvironmentVars( + input, + prefix = /** @type {Prefix} */ ("import.meta.env."), +) { /** * @type {Record} */ @@ -40,3 +61,5 @@ export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ( return /** @type {any} */ (env); } + +//#endregion diff --git a/web/packages/monorepo/index.js b/web/packages/monorepo/index.js index 284d2b5d2b..5eee5d4cef 100644 --- a/web/packages/monorepo/index.js +++ b/web/packages/monorepo/index.js @@ -1,7 +1,6 @@ /// export * from "./paths.js"; -export * from "./constants.js"; -export * from "./build.js"; +export * from "./environment.js"; export * from "./version.js"; export * from "./scripting.js"; diff --git a/web/scripts/build-web.mjs b/web/scripts/build-web.mjs index 3b1f329e23..67394ec32b 100644 --- a/web/scripts/build-web.mjs +++ b/web/scripts/build-web.mjs @@ -1,3 +1,4 @@ +/// /** * @file ESBuild script for building the authentik web UI. * @@ -9,7 +10,6 @@ import { NodeEnvironment, readBuildIdentifier, resolvePackage, - serializeEnvironmentVars, } from "@goauthentik/monorepo"; import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "@goauthentik/web/paths"; import { deepmerge } from "deepmerge-ts"; @@ -20,15 +20,10 @@ import * as fs from "node:fs/promises"; import * as path from "node:path"; import { mdxPlugin } from "./esbuild/build-mdx-plugin.mjs"; +import { createBundleDefinitions } from "./esbuild/environment.mjs"; const logPrefix = "[Build]"; -const definitions = serializeEnvironmentVars({ - NODE_ENV: NodeEnvironment, - CWD: process.cwd(), - AK_API_BASE_PATH: process.env.AK_API_BASE_PATH, -}); - const patternflyPath = resolvePackage("@patternfly/patternfly"); /** @@ -86,7 +81,7 @@ const BASE_ESBUILD_OPTIONS = { root: MonoRepoRoot, }), ], - define: definitions, + define: createBundleDefinitions(), format: "esm", logOverride: { /** diff --git a/web/scripts/esbuild/environment.mjs b/web/scripts/esbuild/environment.mjs new file mode 100644 index 0000000000..2af7afcaba --- /dev/null +++ b/web/scripts/esbuild/environment.mjs @@ -0,0 +1,29 @@ +/** + * @file ESBuild environment utilities. + */ +import { AuthentikVersion, NodeEnvironment, serializeEnvironmentVars } from "@goauthentik/monorepo"; + +/** + * Creates a mapping of environment variables to their respective runtime constants. + */ +export function createBundleDefinitions() { + const SerializedNodeEnvironment = /** @type {`"development"` | `"production"`} */ ( + JSON.stringify(NodeEnvironment) + ); + + /** + * @satisfies {Record} + */ + const envRecord = { + AK_VERSION: AuthentikVersion, + AK_API_BASE_PATH: process.env.AK_API_BASE_PATH ?? "", + }; + + return { + ...serializeEnvironmentVars(envRecord), + // We need to explicitly set this for NPM packages that use `process` + // to determine their environment. + "process.env.NODE_ENV": SerializedNodeEnvironment, + "import.meta.env.NODE_ENV": SerializedNodeEnvironment, + }; +} diff --git a/web/src/admin/AdminInterface/index.entrypoint.ts b/web/src/admin/AdminInterface/index.entrypoint.ts index 9b6480078e..2797ddb6e1 100644 --- a/web/src/admin/AdminInterface/index.entrypoint.ts +++ b/web/src/admin/AdminInterface/index.entrypoint.ts @@ -43,7 +43,7 @@ import { renderSidebarItems, } from "./AdminSidebar.js"; -if (process.env.NODE_ENV === "development") { +if (import.meta.env.NODE_ENV === "development") { await import("@goauthentik/esbuild-plugin-live-reload/client"); } diff --git a/web/src/common/stylesheets.ts b/web/src/common/stylesheets.ts index 84a2459a56..094f4ec0bc 100644 --- a/web/src/common/stylesheets.ts +++ b/web/src/common/stylesheets.ts @@ -223,7 +223,7 @@ export function inspectStyleSheetTree(element: ReactiveElement): InspectedStyleS }; } -if (process.env.NODE_ENV === "development") { +if (import.meta.env.NODE_ENV === "development") { Object.assign(window, { inspectStyleSheetTree, serializeStyleSheet, diff --git a/web/src/flow/index.entrypoint.ts b/web/src/flow/index.entrypoint.ts index 9f5cfff49c..759efa4b9f 100644 --- a/web/src/flow/index.entrypoint.ts +++ b/web/src/flow/index.entrypoint.ts @@ -14,6 +14,6 @@ import "@goauthentik/flow/stages/password/PasswordStage"; // end of stage import -if (process.env.NODE_ENV === "development") { +if (import.meta.env.NODE_ENV === "development") { await import("@goauthentik/esbuild-plugin-live-reload/client"); } diff --git a/web/src/user/index.entrypoint.ts b/web/src/user/index.entrypoint.ts index 5bc8721fe4..d295f8ad6a 100644 --- a/web/src/user/index.entrypoint.ts +++ b/web/src/user/index.entrypoint.ts @@ -43,7 +43,7 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; -if (process.env.NODE_ENV === "development") { +if (import.meta.env.NODE_ENV === "development") { await import("@goauthentik/esbuild-plugin-live-reload/client"); } diff --git a/web/types/esbuild.d.ts b/web/types/esbuild.d.ts new file mode 100644 index 0000000000..c227f2ab67 --- /dev/null +++ b/web/types/esbuild.d.ts @@ -0,0 +1,39 @@ +/** + * @file Import meta environment variables available via ESBuild. + */ + +export {}; +declare global { + interface ESBuildImportEnv { + /** + * The authentik version injected by ESBuild during build time. + * + * @format semver + */ + readonly AK_VERSION: string; + + /** + * @todo Determine where this is used and if it is needed, + * give it a better name. + * @deprecated + */ + readonly AK_API_BASE_PATH: string; + } + + type ESBuildImportEnvKey = keyof ESBuildImportEnv; + + /** + * Environment variables injected by ESBuild. + */ + interface ImportMetaEnv extends ESBuildImportEnv { + /** + * An environment variable used to determine + * whether Node.js is running in production mode. + */ + readonly NODE_ENV: "development" | "production"; + } + + interface ImportMeta { + readonly env: ImportMetaEnv; + } +} diff --git a/web/types/global.d.ts b/web/types/node.d.ts similarity index 70% rename from web/types/global.d.ts rename to web/types/node.d.ts index 4418fb7e9f..9a9d8c56b8 100644 --- a/web/types/global.d.ts +++ b/web/types/node.d.ts @@ -1,5 +1,5 @@ /** - * @file Environment variables available via ESBuild. + * @file Global variables provided by Node.js */ declare module "module" { @@ -14,8 +14,8 @@ declare module "module" { * const relativeDirname = dirname(fileURLToPath(import.meta.url)); * ``` */ - // eslint-disable-next-line no-var - var __dirname: string; + + const __dirname: string; } } @@ -23,13 +23,16 @@ declare module "process" { global { namespace NodeJS { interface ProcessEnv { - CWD: string; + /** + * Node environment, if any. + */ + readonly NODE_ENV?: "development" | "production"; /** * @todo Determine where this is used and if it is needed, * give it a better name. * @deprecated */ - AK_API_BASE_PATH: string; + readonly AK_API_BASE_PATH?: string; } } } diff --git a/web/wdio.conf.ts b/web/wdio.conf.ts index 8b41fdcd79..22bfbab91c 100644 --- a/web/wdio.conf.ts +++ b/web/wdio.conf.ts @@ -1,17 +1,15 @@ -import replace from "@rollup/plugin-replace"; +/// import { browser } from "@wdio/globals"; import type { Options } from "@wdio/types"; -import path from "path"; -import { cwd } from "process"; -import { fileURLToPath } from "url"; -import type { UserConfig } from "vite"; -import litCss from "vite-plugin-lit-css"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createBundleDefinitions } from "scripts/esbuild/environment.mjs"; +import type { InlineConfig } from "vite"; +import litCSS from "vite-plugin-lit-css"; import tsconfigPaths from "vite-tsconfig-paths"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -const isProdBuild = process.env.NODE_ENV === "production"; -const apiBasePath = process.env.AK_API_BASE_PATH || ""; const runHeadless = process.env.CI !== undefined; const testSafari = process.env.WDIO_TEST_SAFARI !== undefined; @@ -72,21 +70,9 @@ export const config: Options.Testrunner = { runner: [ "browser", { - viteConfig: (userConfig: UserConfig = { plugins: [] }) => ({ - ...userConfig, - plugins: [ - litCss(), - replace({ - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development", - ), - "process.env.CWD": JSON.stringify(cwd()), - "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), - "preventAssignment": true, - }), - ...(userConfig?.plugins ?? []), - tsconfigPaths(), - ], + viteConfig: { + define: createBundleDefinitions(), + plugins: [litCSS(), tsconfigPaths()], resolve: { alias: { "@goauthentik/admin": path.resolve(__dirname, "src/admin"), @@ -101,7 +87,7 @@ export const config: Options.Testrunner = { "@goauthentik/user": path.resolve(__dirname, "src/user"), }, }, - }), + } satisfies InlineConfig, }, ],