Compare commits
	
		
			4 Commits
		
	
	
		
			dependabot
			...
			workspace-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 783b1b0a79 | |||
| 7c69add264 | |||
| 248fcd5d7f | |||
| 2c64e3f9ba | 
@ -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()],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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"))
 | 
			
		||||
 * }
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file Constants for JavaScript and TypeScript files.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/// <reference types="../../types/global.js" />
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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";
 | 
			
		||||
@ -1,6 +1,20 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file Utility functions for building and copying files.
 | 
			
		||||
 * @file Utility functions for working with environment variables.
 | 
			
		||||
 */
 | 
			
		||||
/// <reference types="./types/global.js" />
 | 
			
		||||
 | 
			
		||||
//#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<string, EnvironmentVariable>} 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<EnvRecord[K]>}}
 | 
			
		||||
 */
 | 
			
		||||
export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ ("process.env.")) {
 | 
			
		||||
export function serializeEnvironmentVars(
 | 
			
		||||
    input,
 | 
			
		||||
    prefix = /** @type {Prefix} */ ("import.meta.env."),
 | 
			
		||||
) {
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Record<string, string>}
 | 
			
		||||
     */
 | 
			
		||||
@ -40,3 +61,5 @@ export function serializeEnvironmentVars(input, prefix = /** @type {Prefix} */ (
 | 
			
		||||
 | 
			
		||||
    return /** @type {any} */ (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
/// <reference types="./types/global.js" />
 | 
			
		||||
 | 
			
		||||
export * from "./paths.js";
 | 
			
		||||
export * from "./constants.js";
 | 
			
		||||
export * from "./build.js";
 | 
			
		||||
export * from "./environment.js";
 | 
			
		||||
export * from "./version.js";
 | 
			
		||||
export * from "./scripting.js";
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,32 @@
 | 
			
		||||
import { spawnSync } from "child_process";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import process from "process";
 | 
			
		||||
/**
 | 
			
		||||
 * @file Lit Localize build script.
 | 
			
		||||
 *
 | 
			
		||||
 * @remarks
 | 
			
		||||
 * Determines if all the Xliff translation source files are present and if the Typescript source files generated from those sources are up-to-date.
 | 
			
		||||
 *
 | 
			
		||||
 * If they are not, it runs the locale building script,
 | 
			
		||||
 * intercepting the long spew of "this string is not translated" and replacing it with a
 | 
			
		||||
 * summary of how many strings are missing with respect to the source locale.
 | 
			
		||||
 *
 | 
			
		||||
 * @import { ConfigFile } from "@lit/localize-tools/lib/types/config"
 | 
			
		||||
 */
 | 
			
		||||
import { PackageRoot } from "@goauthentik/web/paths";
 | 
			
		||||
import { spawnSync } from "node:child_process";
 | 
			
		||||
import { readFileSync, statSync } from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Determines if all the Xliff translation source files are present and if the Typescript source
 | 
			
		||||
 * files generated from those sources are up-to-date. If they are not, it runs the locale building
 | 
			
		||||
 * script, intercepting the long spew of "this string is not translated" and replacing it with a
 | 
			
		||||
 * summary of how many strings are missing with respect to the source locale.
 | 
			
		||||
 * @type {ConfigFile}
 | 
			
		||||
 */
 | 
			
		||||
const localizeRules = JSON.parse(
 | 
			
		||||
    readFileSync(path.join(PackageRoot, "lit-localize.json"), "utf-8"),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const localizeRules = JSON.parse(fs.readFileSync("./lit-localize.json", "utf-8"));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} loc
 | 
			
		||||
 * @returns {boolean}
 | 
			
		||||
 */
 | 
			
		||||
function generatedFileIsUpToDateWithXliffSource(loc) {
 | 
			
		||||
    const xliff = path.join("./xliff", `${loc}.xlf`);
 | 
			
		||||
    const gened = path.join("./src/locales", `${loc}.ts`);
 | 
			
		||||
@ -22,7 +37,7 @@ function generatedFileIsUpToDateWithXliffSource(loc) {
 | 
			
		||||
    // generates a unique error message and halts the build.
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        var xlfStat = fs.statSync(xliff);
 | 
			
		||||
        var xlfStat = statSync(xliff);
 | 
			
		||||
    } catch (_error) {
 | 
			
		||||
        console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
 | 
			
		||||
        process.exit(1);
 | 
			
		||||
@ -30,7 +45,7 @@ function generatedFileIsUpToDateWithXliffSource(loc) {
 | 
			
		||||
 | 
			
		||||
    // If the generated file doesn't exist, of course it's not up to date.
 | 
			
		||||
    try {
 | 
			
		||||
        var genedStat = fs.statSync(gened);
 | 
			
		||||
        var genedStat = statSync(gened);
 | 
			
		||||
    } catch (_error) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
/// <reference types="../types/esbuild.js" />
 | 
			
		||||
/**
 | 
			
		||||
 * @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: {
 | 
			
		||||
        /**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								web/scripts/esbuild/environment.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								web/scripts/esbuild/environment.mjs
									
									
									
									
									
										Normal file
									
								
							@ -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<ESBuildImportEnvKey, string>}
 | 
			
		||||
     */
 | 
			
		||||
    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,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@ -35,6 +35,11 @@ const __dirname = fileURLToPath(new URL(".", import.meta.url));
 | 
			
		||||
const projectRoot = path.join(__dirname, "..");
 | 
			
		||||
process.chdir(projectRoot);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string[]} flags
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
 | 
			
		||||
 | 
			
		||||
const [configFile, files] = hasFlag(["-n", "--nightmare"])
 | 
			
		||||
 | 
			
		||||
@ -1,22 +1,36 @@
 | 
			
		||||
import { readFileSync } from "fs";
 | 
			
		||||
import path from "path";
 | 
			
		||||
/**
 | 
			
		||||
 * @file Pseudo-localization script.
 | 
			
		||||
 *
 | 
			
		||||
 * @import { ConfigFile } from "@lit/localize-tools/lib/types/config.js"
 | 
			
		||||
 * @import { Config } from '@lit/localize-tools/lib/types/config.js';
 | 
			
		||||
 * @import { ProgramMessage } from "@lit/localize-tools/src/messages.js"
 | 
			
		||||
 * @import { Locale } from "@lit/localize-tools/src/types/locale.js"
 | 
			
		||||
 */
 | 
			
		||||
import { PackageRoot } from "@goauthentik/web/paths";
 | 
			
		||||
import { readFileSync } from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import pseudolocale from "pseudolocale";
 | 
			
		||||
import { fileURLToPath } from "url";
 | 
			
		||||
 | 
			
		||||
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";
 | 
			
		||||
import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js";
 | 
			
		||||
import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js";
 | 
			
		||||
 | 
			
		||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
 | 
			
		||||
const pseudoLocale = "pseudo-LOCALE";
 | 
			
		||||
const pseudoLocale = /** @type {Locale} */ ("pseudo-LOCALE");
 | 
			
		||||
const targetLocales = [pseudoLocale];
 | 
			
		||||
const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize.json"), "utf-8"));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {ConfigFile}
 | 
			
		||||
 */
 | 
			
		||||
const baseConfig = JSON.parse(readFileSync(path.join(PackageRoot, "lit-localize.json"), "utf-8"));
 | 
			
		||||
 | 
			
		||||
// Need to make some internal specifications to satisfy the transformer. It doesn't actually matter
 | 
			
		||||
// which Localizer we use (transformer or runtime), because all of the functionality we care about
 | 
			
		||||
// is in their common parent class, but I had to pick one.  Everything else here is just pure
 | 
			
		||||
// exploitation of the lit/localize-tools internals.
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @satisfies {Config}
 | 
			
		||||
 */
 | 
			
		||||
const config = {
 | 
			
		||||
    ...baseConfig,
 | 
			
		||||
    baseDir: path.join(__dirname, ".."),
 | 
			
		||||
@ -28,6 +42,11 @@ const config = {
 | 
			
		||||
    resolve: (path) => path,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param {ProgramMessage} message
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
const pseudoMessagify = (message) => ({
 | 
			
		||||
    name: message.name,
 | 
			
		||||
    contents: message.contents.map((content) =>
 | 
			
		||||
@ -36,7 +55,7 @@ const pseudoMessagify = (message) => ({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const localizer = new TransformLitLocalizer(config);
 | 
			
		||||
const messages = localizer.extractSourceMessages().messages;
 | 
			
		||||
const { messages } = localizer.extractSourceMessages();
 | 
			
		||||
const translations = messages.map(pseudoMessagify);
 | 
			
		||||
const sorted = sortProgramMessages([...messages]);
 | 
			
		||||
const formatter = makeFormatter(config);
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { globalAK } from "@goauthentik/common/global";
 | 
			
		||||
import { DefaultBrand } from "@goauthentik/common/ui/config";
 | 
			
		||||
import "@goauthentik/elements/EmptyState";
 | 
			
		||||
@ -45,7 +44,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
 | 
			
		||||
        }
 | 
			
		||||
        return [
 | 
			
		||||
            [msg("Version"), version.versionCurrent],
 | 
			
		||||
            [msg("UI Version"), VERSION],
 | 
			
		||||
            [msg("UI Version"), import.meta.env.AK_VERSION],
 | 
			
		||||
            [msg("Build"), build],
 | 
			
		||||
            [msg("Python version"), status.runtime.pythonVersion],
 | 
			
		||||
            [msg("Platform"), status.runtime.platform],
 | 
			
		||||
 | 
			
		||||
@ -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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ import "@goauthentik/admin/admin-overview/cards/WorkerStatusCard";
 | 
			
		||||
import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
 | 
			
		||||
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
 | 
			
		||||
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { me } from "@goauthentik/common/users";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
 | 
			
		||||
@ -22,8 +21,6 @@ import { msg, str } from "@lit/localize";
 | 
			
		||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, state } from "lit/decorators.js";
 | 
			
		||||
import { classMap } from "lit/directives/class-map.js";
 | 
			
		||||
import { map } from "lit/directives/map.js";
 | 
			
		||||
import { when } from "lit/directives/when.js";
 | 
			
		||||
 | 
			
		||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
 | 
			
		||||
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
 | 
			
		||||
@ -33,21 +30,17 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
			
		||||
 | 
			
		||||
import { SessionUser } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
export function versionFamily(): string {
 | 
			
		||||
    const parts = VERSION.split(".");
 | 
			
		||||
    parts.pop();
 | 
			
		||||
    return parts.join(".");
 | 
			
		||||
function createReleaseNotesURL(semver: string): URL {
 | 
			
		||||
    const segments = semver.split(".");
 | 
			
		||||
    const versionFamily = segments.slice(0, -1).join(".");
 | 
			
		||||
 | 
			
		||||
    const release = `${versionFamily}#fixed-in-${segments.join("")}`;
 | 
			
		||||
 | 
			
		||||
    return new URL(`/docs/releases/${release}`, "https://goauthentik.io");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const RELEASE = `${VERSION.split(".").slice(0, -1).join(".")}#fixed-in-${VERSION.replaceAll(
 | 
			
		||||
    ".",
 | 
			
		||||
    "",
 | 
			
		||||
)}`;
 | 
			
		||||
 | 
			
		||||
const AdminOverviewBase = WithLicenseSummary(AKElement);
 | 
			
		||||
 | 
			
		||||
type Renderer = () => TemplateResult | typeof nothing;
 | 
			
		||||
 | 
			
		||||
@customElement("ak-admin-overview")
 | 
			
		||||
export class AdminOverviewPage extends AdminOverviewBase {
 | 
			
		||||
    static get styles(): CSSResult[] {
 | 
			
		||||
@ -83,7 +76,11 @@ export class AdminOverviewPage extends AdminOverviewBase {
 | 
			
		||||
        [msg("Check the logs"), paramURL("/events/log")],
 | 
			
		||||
        [msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
 | 
			
		||||
        [msg("Manage users"), paramURL("/identity/users")],
 | 
			
		||||
        [msg("Check the release notes"), `https://goauthentik.io/docs/releases/${RELEASE}`, true],
 | 
			
		||||
        [
 | 
			
		||||
            msg("Check the release notes"),
 | 
			
		||||
            createReleaseNotesURL(import.meta.env.AK_VERSION).href,
 | 
			
		||||
            true,
 | 
			
		||||
        ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    @state()
 | 
			
		||||
@ -193,45 +190,6 @@ export class AdminOverviewPage extends AdminOverviewBase {
 | 
			
		||||
                  </div>`
 | 
			
		||||
                : nothing} `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderActions() {
 | 
			
		||||
        const release = `${versionFamily()}#fixed-in-${VERSION.replaceAll(".", "")}`;
 | 
			
		||||
 | 
			
		||||
        const quickActions: [string, string][] = [
 | 
			
		||||
            [msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
 | 
			
		||||
            [msg("Check the logs"), paramURL("/events/log")],
 | 
			
		||||
            [msg("Explore integrations"), "https://goauthentik.io/integrations/"],
 | 
			
		||||
            [msg("Manage users"), paramURL("/identity/users")],
 | 
			
		||||
            [msg("Check the release notes"), `https://goauthentik.io/docs/releases/${release}`],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const action = ([label, url]: [string, string]) => {
 | 
			
		||||
            const isExternal = url.startsWith("https://");
 | 
			
		||||
            const ex = (truecase: Renderer, falsecase: Renderer) =>
 | 
			
		||||
                when(isExternal, truecase, falsecase);
 | 
			
		||||
 | 
			
		||||
            const content = html`${label}${ex(
 | 
			
		||||
                () => html`<i class="fas fa-external-link-alt ak-external-link"></i>`,
 | 
			
		||||
                () => nothing,
 | 
			
		||||
            )}`;
 | 
			
		||||
 | 
			
		||||
            return html`<li>
 | 
			
		||||
                ${ex(
 | 
			
		||||
                    () =>
 | 
			
		||||
                        html`<a
 | 
			
		||||
                            href="${url}"
 | 
			
		||||
                            class="pf-u-mb-xl"
 | 
			
		||||
                            rel="noopener noreferrer"
 | 
			
		||||
                            target="_blank"
 | 
			
		||||
                            >${content}</a
 | 
			
		||||
                        >`,
 | 
			
		||||
                    () => html`<a href="${url}" class="pf-u-mb-xl" )>${content}</a>`,
 | 
			
		||||
                )}
 | 
			
		||||
            </li>`;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return html`${map(quickActions, action)}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import {
 | 
			
		||||
    EventMiddleware,
 | 
			
		||||
    LoggingMiddleware,
 | 
			
		||||
} from "@goauthentik/common/api/middleware.js";
 | 
			
		||||
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants.js";
 | 
			
		||||
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants.js";
 | 
			
		||||
import { globalAK } from "@goauthentik/common/global.js";
 | 
			
		||||
import { SentryMiddleware } from "@goauthentik/common/sentry";
 | 
			
		||||
 | 
			
		||||
@ -79,4 +79,6 @@ export function AndNext(url: string): string {
 | 
			
		||||
    return `?next=${encodeURIComponent(url)}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);
 | 
			
		||||
console.debug(
 | 
			
		||||
    `authentik(early): version ${import.meta.env.AK_VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,35 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file Global constants used throughout the application.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo Much of this content can be moved to a specific file, element, or component.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/// <reference types="../../types/esbuild.js" />
 | 
			
		||||
 | 
			
		||||
//#region Patternfly
 | 
			
		||||
 | 
			
		||||
export const SECONDARY_CLASS = "pf-m-secondary";
 | 
			
		||||
export const SUCCESS_CLASS = "pf-m-success";
 | 
			
		||||
export const ERROR_CLASS = "pf-m-danger";
 | 
			
		||||
export const PROGRESS_CLASS = "pf-m-in-progress";
 | 
			
		||||
export const CURRENT_CLASS = "pf-m-current";
 | 
			
		||||
export const VERSION = "2025.4.1";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region Application
 | 
			
		||||
 | 
			
		||||
export const TITLE_DEFAULT = "authentik";
 | 
			
		||||
/**
 | 
			
		||||
 * The delimiter used to parse the URL for the current route.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo Move this to the ak-router.
 | 
			
		||||
 */
 | 
			
		||||
export const ROUTE_SEPARATOR = ";";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region Events
 | 
			
		||||
 | 
			
		||||
export const EVENT_REFRESH = "ak-refresh";
 | 
			
		||||
export const EVENT_NOTIFICATION_DRAWER_TOGGLE = "ak-notification-toggle";
 | 
			
		||||
export const EVENT_API_DRAWER_TOGGLE = "ak-api-drawer-toggle";
 | 
			
		||||
@ -20,7 +43,17 @@ export const EVENT_MESSAGE = "ak-message";
 | 
			
		||||
export const EVENT_THEME_CHANGE = "ak-theme-change";
 | 
			
		||||
export const EVENT_REFRESH_ENTERPRISE = "ak-refresh-enterprise";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region WebSocket
 | 
			
		||||
 | 
			
		||||
export const WS_MSG_TYPE_MESSAGE = "message";
 | 
			
		||||
export const WS_MSG_TYPE_REFRESH = "refresh";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
//#region LocalStorage
 | 
			
		||||
 | 
			
		||||
export const LOCALSTORAGE_AUTHENTIK_KEY = "authentik-local-settings";
 | 
			
		||||
 | 
			
		||||
//#endregion
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { SentryIgnoredError } from "@goauthentik/common/sentry";
 | 
			
		||||
 | 
			
		||||
export interface PlexPinResponse {
 | 
			
		||||
@ -19,7 +18,7 @@ export const DEFAULT_HEADERS = {
 | 
			
		||||
    "Accept": "application/json",
 | 
			
		||||
    "Content-Type": "application/json",
 | 
			
		||||
    "X-Plex-Product": "authentik",
 | 
			
		||||
    "X-Plex-Version": VERSION,
 | 
			
		||||
    "X-Plex-Version": import.meta.env.AK_VERSION,
 | 
			
		||||
    "X-Plex-Device-Vendor": "goauthentik.io",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { globalAK } from "@goauthentik/common/global";
 | 
			
		||||
import { me } from "@goauthentik/common/users";
 | 
			
		||||
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
 | 
			
		||||
@ -50,7 +49,7 @@ export function configureSentry(canDoPpi = false) {
 | 
			
		||||
            /MutationObserver.observe/gi,
 | 
			
		||||
            /NS_ERROR_FAILURE/gi,
 | 
			
		||||
        ],
 | 
			
		||||
        release: `authentik@${VERSION}`,
 | 
			
		||||
        release: `authentik@${import.meta.env.AK_VERSION}`,
 | 
			
		||||
        integrations: [
 | 
			
		||||
            browserTracingIntegration({
 | 
			
		||||
                // https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#custom-routing
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,11 @@ import {
 | 
			
		||||
    type StyleRoot,
 | 
			
		||||
    createStyleSheetUnsafe,
 | 
			
		||||
    setAdoptedStyleSheets,
 | 
			
		||||
} from "@goauthentik/common/stylesheets.js";
 | 
			
		||||
import { UIConfig } from "@goauthentik/common/ui/config.js";
 | 
			
		||||
} from "@goauthentik/web/common/stylesheets.js";
 | 
			
		||||
import { UIConfig } from "@goauthentik/web/common/ui/config.js";
 | 
			
		||||
 | 
			
		||||
import AKBase from "@goauthentik/common/styles/authentik.css";
 | 
			
		||||
import AKBaseDark from "@goauthentik/common/styles/theme-dark.css";
 | 
			
		||||
import AKBase from "@goauthentik/web/common/styles/authentik.css";
 | 
			
		||||
import AKBaseDark from "@goauthentik/web/common/styles/theme-dark.css";
 | 
			
		||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
			
		||||
 | 
			
		||||
import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { PFSize } from "@goauthentik/common/enums.js";
 | 
			
		||||
import {
 | 
			
		||||
    EventContext,
 | 
			
		||||
@ -76,7 +75,7 @@ ${context.message as string}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**Version and Deployment (please complete the following information):**
 | 
			
		||||
- authentik version: ${VERSION}
 | 
			
		||||
- authentik version: ${import.meta.env.AK_VERSION}
 | 
			
		||||
- Deployment: [e.g. docker-compose, helm]
 | 
			
		||||
 | 
			
		||||
**Additional context**
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { VERSION } from "@goauthentik/common/constants";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
 | 
			
		||||
 | 
			
		||||
@ -10,20 +9,19 @@ import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
 | 
			
		||||
 | 
			
		||||
@customElement("ak-version-banner")
 | 
			
		||||
export class VersionBanner extends WithVersion(AKElement) {
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [PFBanner];
 | 
			
		||||
    }
 | 
			
		||||
    static styles = [PFBanner];
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return this.version && this.version.versionCurrent !== VERSION
 | 
			
		||||
            ? html`
 | 
			
		||||
                  <div class="pf-c-banner pf-m-sticky pf-m-gold">
 | 
			
		||||
                      ${msg(
 | 
			
		||||
                          str`A newer version (${this.version.versionCurrent}) of the UI is available.`,
 | 
			
		||||
                      )}
 | 
			
		||||
                  </div>
 | 
			
		||||
              `
 | 
			
		||||
            : nothing;
 | 
			
		||||
        if (!this.version?.versionCurrent) return nothing;
 | 
			
		||||
        if (this.version.versionCurrent === import.meta.env.AK_VERSION) return nothing;
 | 
			
		||||
 | 
			
		||||
        return html`
 | 
			
		||||
            <div class="pf-c-banner pf-m-sticky pf-m-gold">
 | 
			
		||||
                ${msg(
 | 
			
		||||
                    str`A newer version (${this.version.versionCurrent}) of the UI is available.`,
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,9 @@
 | 
			
		||||
{
 | 
			
		||||
    "extends": "@goauthentik/tsconfig",
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
        "checkJs": true,
 | 
			
		||||
        "allowJs": true,
 | 
			
		||||
        "resolveJsonModule": true,
 | 
			
		||||
        "allowSyntheticDefaultImports": true,
 | 
			
		||||
        "emitDeclarationOnly": true,
 | 
			
		||||
        "experimentalDecorators": true,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								web/types/esbuild.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/types/esbuild.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								web/types/global.d.ts → web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								web/types/global.d.ts → web/types/node.d.ts
									
									
									
									
										vendored
									
									
								
							@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -1,17 +1,15 @@
 | 
			
		||||
import replace from "@rollup/plugin-replace";
 | 
			
		||||
/// <reference types="@wdio/browser-runner" />
 | 
			
		||||
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,
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user