Compare commits

..

20 Commits

Author SHA1 Message Date
144c3cc082 Release candidate 2024-06-09 11:53:41 +02:00
802cef41a6 Rename KcApp to KcPage 2024-06-09 11:53:25 +02:00
e128e8f0a9 Release candidate 2024-06-09 11:25:05 +02:00
8a25b93ab2 Rename Fallback to DefaultPage 2024-06-09 11:24:50 +02:00
7a040935e9 i18n need to be passed as props if we want to be able to ovewrite 2024-06-09 11:20:45 +02:00
2015882688 Avoid loop rebuild in watch mode 2024-06-09 10:28:06 +02:00
379301eb9d Release candidate 2024-06-09 09:50:27 +02:00
5d86b05cdb Fix eject-page script 2024-06-09 09:50:02 +02:00
73c99d3157 Fix scripts 2024-06-09 09:39:16 +02:00
acba197c94 Release candidate 2024-06-09 09:36:31 +02:00
2441d8ed8a Fix tests 2024-06-09 09:36:16 +02:00
9c123f37c8 Make of doMakeUserConfirmPassword a prop of UserProfileFormFields 2024-06-09 09:34:39 +02:00
b48dbd99cf Enable to pass a path to a file for exclusions #525 2024-06-09 09:20:55 +02:00
25c8599d8f Rename BuildOptions -> BuildContext 2024-06-09 09:15:45 +02:00
3453a17c15 Rename reactAppRootDirPath -> projectDirPath and reactAppBuildDirPath -> projectBuildDirPath 2024-06-09 09:03:43 +02:00
6e95dacd3a Remove todo 2024-06-09 08:52:52 +02:00
a286e252e9 Enable to pass environement variables to test docker container 2024-06-09 08:50:59 +02:00
a8997e92c3 Improve cache strategy for getKcClsx 2024-06-09 08:37:23 +02:00
89137153a0 Remove isStorybook util 2024-06-09 08:37:02 +02:00
e3382de8e0 Fix boolean logic error 2024-06-09 08:30:57 +02:00
139 changed files with 1062 additions and 922 deletions

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "10.0.0-rc.38",
"version": "10.0.0-rc.42",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",

View File

@ -26,7 +26,7 @@ async function main() {
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion,
buildOptions: {
buildContext: {
cacheDirPath: pathJoin(
thisCodebaseRootDirPath,
"node_modules",

View File

@ -1,7 +1,8 @@
import { lazy, Suspense } from "react";
import { assert, type Equals } from "tsafe/assert";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "./KcContext";
import type { KcContext } from "keycloakify/account/KcContext";
import { I18n } from "keycloakify/account/i18n";
const Password = lazy(() => import("keycloakify/account/pages/Password"));
const Account = lazy(() => import("keycloakify/account/pages/Account"));
@ -11,7 +12,7 @@ const Applications = lazy(() => import("keycloakify/account/pages/Applications")
const Log = lazy(() => import("keycloakify/account/pages/Log"));
const FederatedIdentity = lazy(() => import("keycloakify/account/pages/FederatedIdentity"));
export default function Fallback(props: PageProps<KcContext>) {
export default function DefaultPage(props: PageProps<KcContext, I18n>) {
const { kcContext, ...rest } = props;
return (

View File

@ -5,15 +5,15 @@ import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { TemplateProps } from "keycloakify/account/TemplateProps";
import type { I18n } from "./i18n";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
export default function Template(props: TemplateProps<KcContext>) {
const { kcContext, doUseDefaultCss, active, classes, children } = props;
export default function Template(props: TemplateProps<KcContext, I18n>) {
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = useI18n({ kcContext });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { locale, url, features, realm, message, referrer } = kcContext;

View File

@ -1,12 +1,13 @@
import type { ReactNode } from "react";
import type { KcContext } from "./KcContext";
export type TemplateProps<KcContext extends KcContext.Common> = {
export type TemplateProps<KcContext, I18n> = {
kcContext: KcContext;
i18n: I18n;
doUseDefaultCss: boolean;
active: string;
classes?: Partial<Record<ClassKey, string>>;
children: ReactNode;
active: string;
};
export type ClassKey =

View File

@ -130,7 +130,7 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
extraMessages: extraMessages[partialI18n.currentLanguageTag]
});
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag !== fallbackLanguageTag;
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag === fallbackLanguageTag;
const result: Result = {
i18n: {
@ -175,7 +175,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
const { getI18n } = createGetI18n(extraMessages);
function useI18n(params: { kcContext: KcContextLike }): I18n {
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
@ -198,7 +198,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
};
}, []);
return i18n_toReturn;
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };

View File

@ -1,10 +1,5 @@
export type { MessageKey } from "./i18n";
import { createUseI18n } from "./i18n";
export { createUseI18n };
import type { GenericI18n, MessageKey, KcContextLike } from "./i18n";
export type { MessageKey, KcContextLike };
export type I18n = GenericI18n<MessageKey>;
export { createUseI18n } from "./i18n";
export { fallbackLanguageTag } from "./i18n";
const { useI18n, ofTypeI18n } = createUseI18n({});
export type I18n = typeof ofTypeI18n;
export { useI18n };

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Account(props: PageProps<Extract<KcContext, { pageId: "account.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template } = props;
export default function Account(props: PageProps<Extract<KcContext, { pageId: "account.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template } = props;
const classes = {
...props.classes,
@ -19,10 +19,10 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
const { url, realm, messagesPerField, stateChecker, account, referrer } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="account">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="account">
<div className="row">
<div className="col-md-10">
<h2>{msg("editAccountHtmlTitle")}</h2>

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -17,10 +17,10 @@ export default function Applications(props: PageProps<Extract<KcContext, { pageI
stateChecker
} = kcContext;
const { msg, advancedMsg } = useI18n({ kcContext });
const { msg, advancedMsg } = i18n;
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="applications">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="applications">
<div className="row">
<div className="col-md-10">
<h2>{msg("applicationsHtmlTitle")}</h2>

View File

@ -1,14 +1,14 @@
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { url, federatedIdentity, stateChecker } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="federatedIdentity">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="federatedIdentity">
<div className="main-layout social">
<div className="row">
<div className="col-md-10">

View File

@ -2,10 +2,10 @@ import type { Key } from "react";
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -14,10 +14,10 @@ export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.f
const { log } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="log">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="log">
<div className={kcClsx("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("accountLogHtmlTitle")}</h2>

View File

@ -1,10 +1,10 @@
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
import { type TemplateProps, type ClassKey } from "keycloakify/account/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { KcContext } from "../KcContext";
export type PageProps<NarrowedKcContext = KcContext> = {
Template: LazyOrNot<(props: TemplateProps<any>) => JSX.Element | null>;
export type PageProps<NarrowedKcContext, I18n> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
kcContext: NarrowedKcContext;
i18n: I18n;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;
};

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Password(props: PageProps<Extract<KcContext, { pageId: "password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template } = props;
export default function Password(props: PageProps<Extract<KcContext, { pageId: "password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template } = props;
const classes = {
...props.classes,
@ -20,7 +20,7 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
const { url, password, account, stateChecker } = kcContext;
const { msgStr, msg } = useI18n({ kcContext });
const { msgStr, msg } = i18n;
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
@ -77,6 +77,7 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
return kcContext.message;
})()
},
i18n,
doUseDefaultCss,
classes
}}

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,9 +13,9 @@ export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "
const { url, stateChecker, sessions } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="sessions">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="sessions">
<div className={kcClsx("kcContentWrapperClass")}>
<div className="col-md-10">
<h2>{msg("sessionsHtmlTitle")}</h2>

View File

@ -2,10 +2,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { getKcClsx } from "keycloakify/account/lib/kcClsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -14,7 +14,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
const { totp, mode, url, messagesPerField, stateChecker } = kcContext;
const { msg, msgStr, advancedMsg } = useI18n({ kcContext });
const { msg, msgStr, advancedMsg } = i18n;
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
HmacSHA1: "SHA1",
@ -23,7 +23,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
};
return (
<Template {...{ kcContext, doUseDefaultCss, classes }} active="totp">
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="totp">
<>
<div className="row">
<div className="col-md-10">

View File

@ -15,13 +15,13 @@ import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./shared/getThemeSrcDirPath";
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import chalk from "chalk";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
const buildContext = getBuildContext({
cliCommandOptions
});
@ -54,7 +54,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(`${pageId}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
projectDirPath: buildContext.projectDirPath
});
const componentBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(

View File

@ -1,13 +1,13 @@
import { copyKeycloakResourcesToPublic } from "./shared/copyKeycloakResourcesToPublic";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import type { CliCommandOptions } from "./main";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({ cliCommandOptions });
const buildContext = getBuildContext({ cliCommandOptions });
await copyKeycloakResourcesToPublic({
buildOptions
buildContext
});
}

View File

@ -1,6 +1,6 @@
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import { downloadKeycloakDefaultTheme } from "./shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "./tools/transformCodebase";
import type { CliCommandOptions } from "./main";
@ -9,7 +9,7 @@ import chalk from "chalk";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
const buildContext = getBuildContext({
cliCommandOptions
});
@ -21,13 +21,13 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { keycloakVersion } = await promptKeycloakVersion({
startingFromMajor: undefined,
cacheDirPath: buildOptions.cacheDirPath
cacheDirPath: buildContext.cacheDirPath
});
console.log(`${keycloakVersion}`);
const destDirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",
@ -51,7 +51,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion,
buildOptions
buildContext
});
transformCodebase({

View File

@ -17,13 +17,13 @@ import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./shared/getThemeSrcDirPath";
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import chalk from "chalk";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
const buildContext = getBuildContext({
cliCommandOptions
});
@ -69,7 +69,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(`${pageIdOrComponent}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
projectDirPath: buildContext.projectDirPath
});
const componentBasename = (() => {
@ -149,7 +149,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
break edit_KcApp;
}
const kcAppTsxPath = pathJoin(themeSrcDirPath, themeType, "KcApp.tsx");
const kcAppTsxPath = pathJoin(themeSrcDirPath, themeType, "KcPage.tsx");
const kcAppTsxCode = fs.readFileSync(kcAppTsxPath).toString("utf8");
@ -172,7 +172,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
if (kcAppTsxCode === modifiedKcAppTsxCode) {
console.log(
chalk.red(
"Unable to automatically update KcApp.tsx, please update it manually"
"Unable to automatically update KcPage.tsx, please update it manually"
)
);
return;
@ -201,7 +201,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
".",
pathRelative(process.cwd(), themeSrcDirPath),
themeType,
"KcApp.tsx"
"KcPage.tsx"
)
)}:`,
chalk.grey("```"),
@ -215,7 +215,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
),
...[
``,
` export default function KcApp(props: { kcContext: KcContext; }) {`,
` export default function KcPage(props: { kcContext: KcContext; }) {`,
``,
` // ...`,
``,
@ -225,15 +225,16 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
` switch (kcContext.pageId) {`,
` // ...`,
`+ case "${pageIdOrComponent}": return (`,
`+ <Login`,
`+ <${componentBasename}`,
`+ {...{ kcContext, i18n, classes }}`,
`+ Template={Template}`,
`+ doUseDefaultCss={true}`,
...(!componentCode.includes(userProfileFormFieldComponentName)
? []
: [
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`,
`+ doMakeUserConfirmPassword={doMakeUserConfirmPassword}`
]),
`+ doUseDefaultCss={true}`,
`+ />`,
`+ );`,
` default: return <Fallback /* .. */ />;`,

View File

@ -2,7 +2,7 @@ import { downloadKeycloakDefaultTheme } from "./shared/downloadKeycloakDefaultTh
import { join as pathJoin, relative as pathRelative } from "path";
import { transformCodebase } from "./tools/transformCodebase";
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import * as fs from "fs";
import { getThemeSrcDirPath } from "./shared/getThemeSrcDirPath";
import type { CliCommandOptions } from "./main";
@ -10,10 +10,10 @@ import type { CliCommandOptions } from "./main";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({ cliCommandOptions });
const buildContext = getBuildContext({ cliCommandOptions });
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
projectDirPath: buildContext.projectDirPath
});
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
@ -34,12 +34,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { keycloakVersion } = await promptKeycloakVersion({
// NOTE: This is arbitrary
startingFromMajor: 17,
cacheDirPath: buildOptions.cacheDirPath
cacheDirPath: buildContext.cacheDirPath
});
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion,
buildOptions
buildContext
});
transformCodebase({

View File

@ -5,12 +5,12 @@ import type {
} from "./extensionVersions";
import { join as pathJoin, dirname as pathDirname } from "path";
import { transformCodebase } from "../../tools/transformCodebase";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import * as fs from "fs/promises";
import { accountV1ThemeName } from "../../shared/constants";
import {
generatePom,
BuildOptionsLike as BuildOptionsLike_generatePom
BuildContextLike as BuildContextLike_generatePom
} from "./generatePom";
import { readFileSync } from "fs";
import { isInside } from "../../tools/isInside";
@ -18,7 +18,7 @@ import child_process from "child_process";
import { rmSync } from "../../tools/fs.rmSync";
import { getMetaInfKeycloakThemesJsonFilePath } from "../../shared/metaInfKeycloakThemes";
export type BuildOptionsLike = BuildOptionsLike_generatePom & {
export type BuildContextLike = BuildContextLike_generatePom & {
keycloakifyBuildDirPath: string;
themeNames: string[];
artifactId: string;
@ -26,23 +26,23 @@ export type BuildOptionsLike = BuildOptionsLike_generatePom & {
cacheDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function buildJar(params: {
jarFileBasename: string;
keycloakAccountV1Version: KeycloakAccountV1Version;
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<void> {
const {
jarFileBasename,
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
buildContext
} = params;
const keycloakifyBuildTmpDirPath = pathJoin(
buildOptions.cacheDirPath,
buildContext.cacheDirPath,
jarFileBasename.replace(".jar", "")
);
@ -62,7 +62,7 @@ export async function buildJar(params: {
return { modifiedSourceCode: sourceCode };
}
for (const themeName of [...buildOptions.themeNames, accountV1ThemeName]) {
for (const themeName of [...buildContext.themeNames, accountV1ThemeName]) {
if (
isInside({
dirPath: pathJoin("src", "main", "resources", "theme", themeName),
@ -125,7 +125,7 @@ export async function buildJar(params: {
};
}
for (const themeName of buildOptions.themeNames) {
for (const themeName of buildContext.themeNames) {
if (
fileRelativePath ===
pathJoin(
@ -160,7 +160,7 @@ export async function buildJar(params: {
};
transformCodebase({
srcDirPath: buildOptions.keycloakifyBuildDirPath,
srcDirPath: buildContext.keycloakifyBuildDirPath,
destDirPath: keycloakifyBuildTmpDirPath,
transformSourceCode: params => {
const resultCommon = transformCodebase_common(params);
@ -203,7 +203,7 @@ export async function buildJar(params: {
}
(["register.ftl", "login-update-profile.ftl"] as const).forEach(pageId =>
buildOptions.themeNames.map(themeName => {
buildContext.themeNames.map(themeName => {
const ftlFilePath = pathJoin(
keycloakifyBuildTmpDirPath,
"src",
@ -244,7 +244,7 @@ export async function buildJar(params: {
{
const { pomFileCode } = generatePom({
buildOptions,
buildContext,
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion
});
@ -285,9 +285,9 @@ export async function buildJar(params: {
pathJoin(
keycloakifyBuildTmpDirPath,
"target",
`${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`
`${buildContext.artifactId}-${buildContext.themeVersion}.jar`
),
pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)
pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename)
);
rmSync(keycloakifyBuildTmpDirPath, { recursive: true });

View File

@ -5,25 +5,25 @@ import {
keycloakThemeAdditionalInfoExtensionVersions
} from "./extensionVersions";
import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar";
import type { BuildOptions } from "../../shared/buildOptions";
import { buildJar, BuildContextLike as BuildContextLike_buildJar } from "./buildJar";
import type { BuildContext } from "../../shared/buildContext";
import { getJarFileBasename } from "../../shared/getJarFileBasename";
import { readMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
import { accountV1ThemeName } from "../../shared/constants";
export type BuildOptionsLike = BuildOptionsLike_buildJar & {
export type BuildContextLike = BuildContextLike_buildJar & {
keycloakifyBuildDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function buildJars(params: {
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<void> {
const { buildOptions } = params;
const { buildContext } = params;
const doesImplementAccountTheme = readMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
}).themes.some(({ name }) => name === accountV1ThemeName);
await Promise.all(
@ -71,7 +71,7 @@ export async function buildJars(params: {
jarFileBasename,
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
buildContext
})
)
)

View File

@ -1,27 +1,27 @@
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import type {
KeycloakAccountV1Version,
KeycloakThemeAdditionalInfoExtensionVersion
} from "./extensionVersions";
export type BuildOptionsLike = {
export type BuildContextLike = {
groupId: string;
artifactId: string;
themeVersion: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function generatePom(params: {
keycloakAccountV1Version: KeycloakAccountV1Version;
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const {
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
buildContext
} = params;
const { pomFileCode } = (function generatePomFileCode(): {
@ -33,10 +33,10 @@ export function generatePom(params: {
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
` <modelVersion>4.0.0</modelVersion>`,
` <groupId>${buildOptions.groupId}</groupId>`,
` <artifactId>${buildOptions.artifactId}</artifactId>`,
` <version>${buildOptions.themeVersion}</version>`,
` <name>${buildOptions.artifactId}</name>`,
` <groupId>${buildContext.groupId}</groupId>`,
` <artifactId>${buildContext.artifactId}</artifactId>`,
` <version>${buildContext.themeVersion}</version>`,
` <name>${buildContext.artifactId}</name>`,
` <description />`,
` <packaging>jar</packaging>`,
` <properties>`,

View File

@ -4,7 +4,7 @@ import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCss
import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode";
import * as fs from "fs";
import { join as pathJoin } from "path";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import {
type ThemeType,
@ -15,22 +15,22 @@ import {
} from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildOptionsLike = {
export type BuildContextLike = {
bundler: "vite" | "webpack";
themeVersion: string;
urlPathname: string | undefined;
reactAppBuildDirPath: string;
projectBuildDirPath: string;
assetsDirPath: string;
kcContextExclusionsFtlCode: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function generateFtlFilesCodeFactory(params: {
themeName: string;
indexHtmlCode: string;
cssGlobalsToDefine: Record<string, string>;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
keycloakifyVersion: string;
themeType: ThemeType;
fieldNames: string[];
@ -39,7 +39,7 @@ export function generateFtlFilesCodeFactory(params: {
themeName,
cssGlobalsToDefine,
indexHtmlCode,
buildOptions,
buildContext,
keycloakifyVersion,
themeType,
fieldNames
@ -55,7 +55,7 @@ export function generateFtlFilesCodeFactory(params: {
const { fixedJsCode } = replaceImportsInJsCode({
jsCode,
buildOptions
buildContext
});
$(element).text(fixedJsCode);
@ -68,7 +68,7 @@ export function generateFtlFilesCodeFactory(params: {
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
buildOptions
buildContext
});
$(element).text(fixedCssCode);
@ -91,7 +91,7 @@ export function generateFtlFilesCodeFactory(params: {
attrName,
href.replace(
new RegExp(
`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`
`^${(buildContext.urlPathname ?? "/").replace(/\//g, "\\/")}`
),
`\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
)
@ -106,7 +106,7 @@ export function generateFtlFilesCodeFactory(params: {
"<style>",
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
buildOptions
buildContext
}).cssCodeToPrependInHead,
"</style>",
""
@ -134,7 +134,7 @@ export function generateFtlFilesCodeFactory(params: {
fieldNames.map(name => `"${name}"`).join(", ")
)
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildContext.themeVersion)
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName)
.replace("RESOURCES_COMMON_cLsLsMrtDkpVv", resources_common)
@ -144,7 +144,7 @@ export function generateFtlFilesCodeFactory(params: {
)
.replace(
"USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2",
buildOptions.kcContextExclusionsFtlCode ?? ""
buildContext.kcContextExclusionsFtlCode ?? ""
);
const ftlObjectToJsCodeDeclaringAnObjectPlaceholder =
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }';

View File

@ -1,7 +1,7 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import {
resources_common,
lastKeycloakVersionWithAccountV1,
@ -10,24 +10,24 @@ import {
import { downloadKeycloakDefaultTheme } from "../../shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "../../tools/transformCodebase";
export type BuildOptionsLike = {
export type BuildContextLike = {
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
keycloakifyBuildDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike }) {
const { buildOptions } = params;
export async function bringInAccountV1(params: { buildContext: BuildContextLike }) {
const { buildContext } = params;
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion: lastKeycloakVersionWithAccountV1,
buildOptions
buildContext
});
const accountV1DirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",

View File

@ -1,34 +1,34 @@
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import {
generateSrcMainResourcesForMainTheme,
type BuildOptionsLike as BuildOptionsLike_generateSrcMainResourcesForMainTheme
type BuildContextLike as BuildContextLike_generateSrcMainResourcesForMainTheme
} from "./generateSrcMainResourcesForMainTheme";
import { generateSrcMainResourcesForThemeVariant } from "./generateSrcMainResourcesForThemeVariant";
export type BuildOptionsLike = BuildOptionsLike_generateSrcMainResourcesForMainTheme & {
export type BuildContextLike = BuildContextLike_generateSrcMainResourcesForMainTheme & {
themeNames: string[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function generateSrcMainResources(params: {
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<void> {
const { buildOptions } = params;
const { buildContext } = params;
const [themeName, ...themeVariantNames] = buildOptions.themeNames;
const [themeName, ...themeVariantNames] = buildContext.themeNames;
await generateSrcMainResourcesForMainTheme({
themeName,
buildOptions
buildContext
});
for (const themeVariantName of themeVariantNames) {
generateSrcMainResourcesForThemeVariant({
themeName,
themeVariantName,
buildOptions
buildContext
});
}
}

View File

@ -5,7 +5,7 @@ import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
import {
generateFtlFilesCodeFactory,
type BuildOptionsLike as BuildOptionsLike_kcContextExclusionsFtlCode
type BuildContextLike as BuildContextLike_kcContextExclusionsFtlCode
} from "../generateFtl";
import {
type ThemeType,
@ -17,18 +17,18 @@ import {
accountThemePageIds
} from "../../shared/constants";
import { isInside } from "../../tools/isInside";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import { assert, type Equals } from "tsafe/assert";
import {
downloadKeycloakStaticResources,
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources
type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources
} from "../../shared/downloadKeycloakStaticResources";
import { readFieldNameUsage } from "./readFieldNameUsage";
import { readExtraPagesNames } from "./readExtraPageNames";
import { generateMessageProperties } from "./generateMessageProperties";
import {
bringInAccountV1,
type BuildOptionsLike as BuildOptionsLike_bringInAccountV1
type BuildContextLike as BuildContextLike_bringInAccountV1
} from "./bringInAccountV1";
import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath";
import { rmSync } from "../../tools/fs.rmSync";
@ -40,36 +40,36 @@ import {
import { objectEntries } from "tsafe/objectEntries";
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
export type BuildOptionsLike = BuildOptionsLike_kcContextExclusionsFtlCode &
BuildOptionsLike_downloadKeycloakStaticResources &
BuildOptionsLike_bringInAccountV1 & {
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
BuildContextLike_downloadKeycloakStaticResources &
BuildContextLike_bringInAccountV1 & {
bundler: "vite" | "webpack";
extraThemeProperties: string[] | undefined;
loginThemeResourcesFromKeycloakVersion: string;
reactAppBuildDirPath: string;
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
reactAppRootDirPath: string;
projectDirPath: string;
keycloakifyBuildDirPath: string;
environmentVariables: { name: string; default: string }[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function generateSrcMainResourcesForMainTheme(params: {
themeName: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<void> {
const { themeName, buildOptions } = params;
const { themeName, buildContext } = params;
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
projectDirPath: buildContext.projectDirPath
});
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
const { themeType } = params;
return pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",
@ -124,7 +124,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
}
transformCodebase({
srcDirPath: buildOptions.reactAppBuildDirPath,
srcDirPath: buildContext.projectBuildDirPath,
destDirPath,
transformSourceCode: ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
@ -132,7 +132,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
if (
isInside({
dirPath: pathJoin(
buildOptions.reactAppBuildDirPath,
buildContext.projectBuildDirPath,
keycloak_resources
),
filePath
@ -163,7 +163,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportsInJsCode({
jsCode: sourceCode.toString("utf8"),
buildOptions
buildContext
});
return {
@ -179,10 +179,10 @@ export async function generateSrcMainResourcesForMainTheme(params: {
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
themeName,
indexHtmlCode: fs
.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html"))
.readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
.toString("utf8"),
cssGlobalsToDefine,
buildOptions,
buildContext,
keycloakifyVersion: readThisNpmPackageVersion(),
themeType,
fieldNames: readFieldNameUsage({
@ -242,12 +242,12 @@ export async function generateSrcMainResourcesForMainTheme(params: {
case "account":
return lastKeycloakVersionWithAccountV1;
case "login":
return buildOptions.loginThemeResourcesFromKeycloakVersion;
return buildContext.loginThemeResourcesFromKeycloakVersion;
}
})(),
themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")),
themeType,
buildOptions
buildContext
});
fs.writeFileSync(
@ -263,8 +263,8 @@ export async function generateSrcMainResourcesForMainTheme(params: {
}
assert<Equals<typeof themeType, never>>(false);
})()}`,
...(buildOptions.extraThemeProperties ?? []),
buildOptions.environmentVariables.map(
...(buildContext.extraThemeProperties ?? []),
buildContext.environmentVariables.map(
({ name, default: defaultValue }) =>
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
)
@ -291,7 +291,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
if (implementedThemeTypes.account) {
await bringInAccountV1({
buildOptions
buildContext
});
}
@ -313,7 +313,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
}
writeMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath,
metaInfKeycloakThemes
});
}

View File

@ -1,27 +1,27 @@
import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path";
import { transformCodebase } from "../../tools/transformCodebase";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import {
readMetaInfKeycloakThemes,
writeMetaInfKeycloakThemes
} from "../../shared/metaInfKeycloakThemes";
import { assert } from "tsafe/assert";
export type BuildOptionsLike = {
export type BuildContextLike = {
keycloakifyBuildDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function generateSrcMainResourcesForThemeVariant(params: {
themeName: string;
themeVariantName: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const { themeName, themeVariantName, buildOptions } = params;
const { themeName, themeVariantName, buildContext } = params;
const mainThemeDirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",
@ -58,7 +58,7 @@ export function generateSrcMainResourcesForThemeVariant(params: {
{
const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
});
updatedMetaInfKeycloakThemes.themes.push({
@ -73,7 +73,7 @@ export function generateSrcMainResourcesForThemeVariant(params: {
});
writeMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath,
metaInfKeycloakThemes: updatedMetaInfKeycloakThemes
});
}

View File

@ -5,15 +5,15 @@ import {
basename as pathBasename
} from "path";
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../shared/buildOptions";
import type { BuildContext } from "../shared/buildContext";
import { accountV1ThemeName } from "../shared/constants";
export type BuildOptionsLike = {
export type BuildContextLike = {
keycloakifyBuildDirPath: string;
themeNames: string[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh";
@ -24,15 +24,15 @@ const keycloakVersion = "24.0.4";
export function generateStartKeycloakTestingContainer(params: {
jarFilePath: string;
doesImplementAccountTheme: boolean;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const { jarFilePath, doesImplementAccountTheme, buildOptions } = params;
const { jarFilePath, doesImplementAccountTheme, buildContext } = params;
const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme");
fs.writeFileSync(
pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
generateStartKeycloakTestingContainer.basename
),
Buffer.from(
@ -41,7 +41,7 @@ export function generateStartKeycloakTestingContainer(params: {
"",
`docker rm ${containerName} || true`,
"",
`cd "${buildOptions.keycloakifyBuildDirPath}"`,
`cd "${buildContext.keycloakifyBuildDirPath}"`,
"",
"docker run \\",
" -p 8080:8080 \\",
@ -50,11 +50,11 @@ export function generateStartKeycloakTestingContainer(params: {
" -e KEYCLOAK_ADMIN_PASSWORD=admin \\",
` -v "${pathJoin(
"$(pwd)",
pathRelative(buildOptions.keycloakifyBuildDirPath, jarFilePath)
pathRelative(buildContext.keycloakifyBuildDirPath, jarFilePath)
)}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`,
[
...(doesImplementAccountTheme ? [accountV1ThemeName] : []),
...buildOptions.themeNames
...buildContext.themeNames
].map(
themeName =>
` -v "${pathJoin(

View File

@ -2,7 +2,7 @@ import { generateSrcMainResources } from "./generateSrcMainResources";
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import * as child_process from "child_process";
import * as fs from "fs";
import { readBuildOptions } from "../shared/buildOptions";
import { getBuildContext } from "../shared/buildContext";
import { vitePluginSubScriptEnvNames, skipBuildJarsEnvName } from "../shared/constants";
import { buildJars } from "./buildJars";
import type { CliCommandOptions } from "../main";
@ -47,7 +47,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({ cliCommandOptions });
const buildContext = getBuildContext({ cliCommandOptions });
console.log(
[
@ -55,7 +55,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
chalk.green(
`Building the keycloak theme in .${pathSep}${pathRelative(
process.cwd(),
buildOptions.keycloakifyBuildDirPath
buildContext.keycloakifyBuildDirPath
)} ...`
)
].join(" ")
@ -64,31 +64,31 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const startTime = Date.now();
{
if (!fs.existsSync(buildOptions.keycloakifyBuildDirPath)) {
fs.mkdirSync(buildOptions.keycloakifyBuildDirPath, {
if (!fs.existsSync(buildContext.keycloakifyBuildDirPath)) {
fs.mkdirSync(buildContext.keycloakifyBuildDirPath, {
recursive: true
});
}
fs.writeFileSync(
pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"),
pathJoin(buildContext.keycloakifyBuildDirPath, ".gitignore"),
Buffer.from("*", "utf8")
);
}
await generateSrcMainResources({ buildOptions });
await generateSrcMainResources({ buildContext });
run_post_build_script: {
if (buildOptions.bundler !== "vite") {
if (buildContext.bundler !== "vite") {
break run_post_build_script;
}
child_process.execSync("npx vite", {
cwd: buildOptions.reactAppRootDirPath,
cwd: buildContext.projectDirPath,
env: {
...process.env,
[vitePluginSubScriptEnvNames.runPostBuildScript]:
JSON.stringify(buildOptions)
JSON.stringify(buildContext)
}
});
}
@ -98,7 +98,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
break build_jars;
}
await buildJars({ buildOptions });
await buildJars({ buildContext });
}
console.log(

View File

@ -1,13 +1,13 @@
import * as crypto from "crypto";
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { basenameOfTheKeycloakifyResourcesDir } from "../../shared/constants";
export type BuildOptionsLike = {
export type BuildContextLike = {
urlPathname: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInCssCode(params: { cssCode: string }): {
fixedCssCode: string;
@ -44,11 +44,11 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
export function generateCssCodeToDefineGlobals(params: {
cssGlobalsToDefine: Record<string, string>;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): {
cssCodeToPrependInHead: string;
} {
const { cssGlobalsToDefine, buildOptions } = params;
const { cssGlobalsToDefine, buildContext } = params;
return {
cssCodeToPrependInHead: [
@ -59,7 +59,7 @@ export function generateCssCodeToDefineGlobals(params: {
`--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace(
new RegExp(
`url\\(${(buildOptions.urlPathname ?? "/").replace(
`url\\(${(buildContext.urlPathname ?? "/").replace(
/\//g,
"\\/"
)}`,

View File

@ -1,25 +1,25 @@
import type { BuildOptions } from "../../shared/buildOptions";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { basenameOfTheKeycloakifyResourcesDir } from "../../shared/constants";
export type BuildOptionsLike = {
export type BuildContextLike = {
urlPathname: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInInlineCssCode(params: {
cssCode: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): {
fixedCssCode: string;
} {
const { cssCode, buildOptions } = params;
const { cssCode, buildContext } = params;
const fixedCssCode = cssCode.replace(
buildOptions.urlPathname === undefined
buildContext.urlPathname === undefined
? /url\(["']?\/([^/][^)"']+)["']?\)/g
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
: new RegExp(`url\\(["']?${buildContext.urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) =>
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
);

View File

@ -1,38 +1,38 @@
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../../shared/buildOptions";
import type { BuildContext } from "../../../shared/buildContext";
import { replaceImportsInJsCode_vite } from "./vite";
import { replaceImportsInJsCode_webpack } from "./webpack";
import * as fs from "fs";
export type BuildOptionsLike = {
reactAppBuildDirPath: string;
export type BuildContextLike = {
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
bundler: "vite" | "webpack";
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInJsCode(params: {
jsCode: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const { jsCode, buildOptions } = params;
const { jsCode, buildContext } = params;
const { fixedJsCode } = (() => {
switch (buildOptions.bundler) {
switch (buildContext.bundler) {
case "vite":
return replaceImportsInJsCode_vite({
jsCode,
buildOptions,
buildContext,
basenameOfAssetsFiles: readAssetsDirSync({
assetsDirPath: params.buildOptions.assetsDirPath
assetsDirPath: params.buildContext.assetsDirPath
})
});
case "webpack":
return replaceImportsInJsCode_webpack({
jsCode,
buildOptions
buildContext
});
}
})();

View File

@ -3,21 +3,21 @@ import {
basenameOfTheKeycloakifyResourcesDir
} from "../../../shared/constants";
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../../shared/buildOptions";
import type { BuildContext } from "../../../shared/buildContext";
import * as nodePath from "path";
import { replaceAll } from "../../../tools/String.prototype.replaceAll";
export type BuildOptionsLike = {
reactAppBuildDirPath: string;
export type BuildContextLike = {
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInJsCode_vite(params: {
jsCode: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
basenameOfAssetsFiles: string[];
systemType?: "posix" | "win32";
}): {
@ -25,7 +25,7 @@ export function replaceImportsInJsCode_vite(params: {
} {
const {
jsCode,
buildOptions,
buildContext,
basenameOfAssetsFiles,
systemType = nodePath.sep === "/" ? "posix" : "win32"
} = params;
@ -35,11 +35,11 @@ export function replaceImportsInJsCode_vite(params: {
let fixedJsCode = jsCode;
replace_base_javacript_import: {
if (buildOptions.urlPathname === undefined) {
if (buildContext.urlPathname === undefined) {
break replace_base_javacript_import;
}
// Optimization
if (!jsCode.includes(buildOptions.urlPathname)) {
if (!jsCode.includes(buildContext.urlPathname)) {
break replace_base_javacript_import;
}
@ -47,7 +47,7 @@ export function replaceImportsInJsCode_vite(params: {
fixedJsCode = fixedJsCode.replace(
new RegExp(
`([\\w\\$][\\w\\d\\$]*)=function\\(([\\w\\$][\\w\\d\\$]*)\\)\\{return"${replaceAll(
buildOptions.urlPathname,
buildContext.urlPathname,
"/",
"\\/"
)}"\\+\\2\\}`,
@ -62,15 +62,15 @@ export function replaceImportsInJsCode_vite(params: {
// Example: "assets/ or "foo/bar/"
const staticDir = (() => {
let out = pathRelative(
buildOptions.reactAppBuildDirPath,
buildOptions.assetsDirPath
buildContext.projectBuildDirPath,
buildContext.assetsDirPath
);
out = replaceAll(out, pathSep, "/") + "/";
if (out === "/") {
throw new Error(
`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`
`The assetsDirPath must be a subdirectory of projectBuildDirPath`
);
}
@ -93,7 +93,7 @@ export function replaceImportsInJsCode_vite(params: {
fixedJsCode = replaceAll(
fixedJsCode,
`"${buildOptions.urlPathname ?? "/"}${relativePathOfAssetFile}"`,
`"${buildContext.urlPathname ?? "/"}${relativePathOfAssetFile}"`,
`(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${relativePathOfAssetFile}")`
);
});

View File

@ -3,28 +3,28 @@ import {
basenameOfTheKeycloakifyResourcesDir
} from "../../../shared/constants";
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../../shared/buildOptions";
import type { BuildContext } from "../../../shared/buildContext";
import * as nodePath from "path";
import { replaceAll } from "../../../tools/String.prototype.replaceAll";
export type BuildOptionsLike = {
reactAppBuildDirPath: string;
export type BuildContextLike = {
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInJsCode_webpack(params: {
jsCode: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
systemType?: "posix" | "win32";
}): {
fixedJsCode: string;
} {
const {
jsCode,
buildOptions,
buildContext,
systemType = nodePath.sep === "/" ? "posix" : "win32"
} = params;
@ -32,12 +32,12 @@ export function replaceImportsInJsCode_webpack(params: {
let fixedJsCode = jsCode;
if (buildOptions.urlPathname !== undefined) {
if (buildContext.urlPathname !== undefined) {
// "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ...
fixedJsCode = fixedJsCode.replace(
new RegExp(
`,([a-zA-Z]\\.[a-zA-Z])="${replaceAll(
buildOptions.urlPathname,
buildContext.urlPathname,
"/",
"\\/"
)}",`,
@ -50,15 +50,15 @@ export function replaceImportsInJsCode_webpack(params: {
// Example: "static/ or "foo/bar/"
const staticDir = (() => {
let out = pathRelative(
buildOptions.reactAppBuildDirPath,
buildOptions.assetsDirPath
buildContext.projectBuildDirPath,
buildContext.assetsDirPath
);
out = replaceAll(out, pathSep, "/") + "/";
if (out === "/") {
throw new Error(
`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`
`The assetsDirPath must be a subdirectory of projectBuildDirPath`
);
}

View File

@ -5,7 +5,7 @@ import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as child_process from "child_process";
export type CliCommandOptions = {
reactAppRootDirPath: string | undefined;
projectDirPath: string | undefined;
};
const program = termost<CliCommandOptions>(
@ -25,7 +25,7 @@ const program = termost<CliCommandOptions>(
const optionsKeys: string[] = [];
program.option({
key: "reactAppRootDirPath",
key: "projectDirPath",
name: (() => {
const long = "project";
const short = "p";

View File

@ -9,8 +9,7 @@ import { assert } from "tsafe";
import * as child_process from "child_process";
import { vitePluginSubScriptEnvNames } from "./constants";
/** Consolidated build option gathered form CLI arguments and config in package.json */
export type BuildOptions = {
export type BuildContext = {
bundler: "vite" | "webpack";
themeVersion: string;
themeNames: string[];
@ -18,9 +17,8 @@ export type BuildOptions = {
groupId: string;
artifactId: string;
loginThemeResourcesFromKeycloakVersion: string;
reactAppRootDirPath: string;
// TODO: Remove from vite type
reactAppBuildDirPath: string;
projectDirPath: string;
projectBuildDirPath: string;
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
keycloakifyBuildDirPath: string;
publicDirPath: string;
@ -34,7 +32,7 @@ export type BuildOptions = {
environmentVariables: { name: string; default: string }[];
};
export type UserProvidedBuildOptions = {
export type BuildOptions = {
themeName?: string | string[];
environmentVariables?: { name: string; default: string }[];
extraThemeProperties?: string[];
@ -42,7 +40,7 @@ export type UserProvidedBuildOptions = {
groupId?: string;
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
kcContextExclusionsFtlCode?: string;
kcContextExclusionsFtl?: string;
};
export type ResolvedViteConfig = {
@ -50,21 +48,21 @@ export type ResolvedViteConfig = {
publicDir: string;
assetsDir: string;
urlPathname: string | undefined;
userProvidedBuildOptions: UserProvidedBuildOptions;
buildOptions: BuildOptions;
};
export function readBuildOptions(params: {
export function getBuildContext(params: {
cliCommandOptions: CliCommandOptions;
}): BuildOptions {
}): BuildContext {
const { cliCommandOptions } = params;
const reactAppRootDirPath = (() => {
if (cliCommandOptions.reactAppRootDirPath === undefined) {
const projectDirPath = (() => {
if (cliCommandOptions.projectDirPath === undefined) {
return process.cwd();
}
return getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.reactAppRootDirPath,
pathIsh: cliCommandOptions.projectDirPath,
cwd: process.cwd()
});
})();
@ -72,7 +70,7 @@ export function readBuildOptions(params: {
const { resolvedViteConfig } = (() => {
if (
fs
.readdirSync(reactAppRootDirPath)
.readdirSync(projectDirPath)
.find(fileBasename => fileBasename.startsWith("vite.config")) ===
undefined
) {
@ -81,7 +79,7 @@ export function readBuildOptions(params: {
const output = child_process
.execSync("npx vite", {
cwd: reactAppRootDirPath,
cwd: projectDirPath,
env: {
...process.env,
[vitePluginSubScriptEnvNames.resolveViteConfig]: "true"
@ -108,8 +106,8 @@ export function readBuildOptions(params: {
name: string;
version?: string;
homepage?: string;
keycloakify?: UserProvidedBuildOptions & {
reactAppBuildDirPath?: string;
keycloakify?: BuildOptions & {
projectBuildDirPath?: string;
};
};
@ -123,7 +121,7 @@ export function readBuildOptions(params: {
artifactId: z.string().optional(),
groupId: z.string().optional(),
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
reactAppBuildDirPath: z.string().optional(),
projectBuildDirPath: z.string().optional(),
keycloakifyBuildDirPath: z.string().optional(),
themeName: z.union([z.string(), z.array(z.string())]).optional()
})
@ -139,20 +137,18 @@ export function readBuildOptions(params: {
return zParsedPackageJson.parse(
JSON.parse(
fs
.readFileSync(pathJoin(reactAppRootDirPath, "package.json"))
.toString("utf8")
fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8")
)
);
})();
const userProvidedBuildOptions: UserProvidedBuildOptions = {
const buildOptions: BuildOptions = {
...parsedPackageJson.keycloakify,
...resolvedViteConfig?.userProvidedBuildOptions
...resolvedViteConfig?.buildOptions
};
const themeNames = (() => {
if (userProvidedBuildOptions.themeName === undefined) {
if (buildOptions.themeName === undefined) {
return [
parsedPackageJson.name
.replace(/^@(.*)/, "$1")
@ -161,34 +157,34 @@ export function readBuildOptions(params: {
];
}
if (typeof userProvidedBuildOptions.themeName === "string") {
return [userProvidedBuildOptions.themeName];
if (typeof buildOptions.themeName === "string") {
return [buildOptions.themeName];
}
return userProvidedBuildOptions.themeName;
return buildOptions.themeName;
})();
const reactAppBuildDirPath = (() => {
const projectBuildDirPath = (() => {
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
if (parsedPackageJson.keycloakify?.reactAppBuildDirPath !== undefined) {
if (parsedPackageJson.keycloakify?.projectBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: parsedPackageJson.keycloakify.reactAppBuildDirPath,
cwd: reactAppRootDirPath
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
cwd: projectDirPath
});
}
return pathJoin(reactAppRootDirPath, "build");
return pathJoin(projectDirPath, "build");
}
return pathJoin(reactAppRootDirPath, resolvedViteConfig.buildDir);
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
})();
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
reactAppRootDirPath,
projectDirPath,
dependencyExpected: "keycloakify"
});
@ -197,13 +193,13 @@ export function readBuildOptions(params: {
themeVersion:
process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
themeNames,
extraThemeProperties: userProvidedBuildOptions.extraThemeProperties,
extraThemeProperties: buildOptions.extraThemeProperties,
groupId: (() => {
const fallbackGroupId = `${themeNames[0]}.keycloak`;
return (
process.env.KEYCLOAKIFY_GROUP_ID ??
userProvidedBuildOptions.groupId ??
buildOptions.groupId ??
(parsedPackageJson.homepage === undefined
? fallbackGroupId
: urlParse(parsedPackageJson.homepage)
@ -215,22 +211,22 @@ export function readBuildOptions(params: {
})(),
artifactId:
process.env.KEYCLOAKIFY_ARTIFACT_ID ??
userProvidedBuildOptions.artifactId ??
buildOptions.artifactId ??
`${themeNames[0]}-keycloak-theme`,
loginThemeResourcesFromKeycloakVersion:
userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
reactAppRootDirPath,
reactAppBuildDirPath,
buildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
projectDirPath,
projectBuildDirPath,
keycloakifyBuildDirPath: (() => {
if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) {
if (buildOptions.keycloakifyBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: userProvidedBuildOptions.keycloakifyBuildDirPath,
cwd: reactAppRootDirPath
pathIsh: buildOptions.keycloakifyBuildDirPath,
cwd: projectDirPath
});
}
return pathJoin(
reactAppRootDirPath,
projectDirPath,
resolvedViteConfig?.buildDir === undefined
? "build_keycloak"
: `${resolvedViteConfig.buildDir}_keycloak`
@ -245,14 +241,14 @@ export function readBuildOptions(params: {
if (process.env.PUBLIC_DIR_PATH !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: process.env.PUBLIC_DIR_PATH,
cwd: reactAppRootDirPath
cwd: projectDirPath
});
}
return pathJoin(reactAppRootDirPath, "public");
return pathJoin(projectDirPath, "public");
}
return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir);
return pathJoin(projectDirPath, resolvedViteConfig.publicDir);
})(),
cacheDirPath: (() => {
const cacheDirPath = pathJoin(
@ -301,13 +297,28 @@ export function readBuildOptions(params: {
break webpack;
}
return pathJoin(reactAppBuildDirPath, "static");
return pathJoin(projectBuildDirPath, "static");
}
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
})(),
npmWorkspaceRootDirPath,
kcContextExclusionsFtlCode: userProvidedBuildOptions.kcContextExclusionsFtlCode,
environmentVariables: userProvidedBuildOptions.environmentVariables ?? []
kcContextExclusionsFtlCode: (() => {
if (buildOptions.kcContextExclusionsFtl === undefined) {
return undefined;
}
if (buildOptions.kcContextExclusionsFtl.endsWith(".ftl")) {
const kcContextExclusionsFtlPath = getAbsoluteAndInOsFormatPath({
pathIsh: buildOptions.kcContextExclusionsFtl,
cwd: projectDirPath
});
return fs.readFileSync(kcContextExclusionsFtlPath).toString("utf8");
}
return buildOptions.kcContextExclusionsFtl;
})(),
environmentVariables: buildOptions.environmentVariables ?? []
};
}

View File

@ -1,6 +1,6 @@
import {
downloadKeycloakStaticResources,
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources
type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources
} from "./downloadKeycloakStaticResources";
import { join as pathJoin, relative as pathRelative } from "path";
import {
@ -12,21 +12,21 @@ import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import { assert } from "tsafe/assert";
import * as fs from "fs";
import { rmSync } from "../tools/fs.rmSync";
import type { BuildOptions } from "./buildOptions";
import type { BuildContext } from "./buildContext";
export type BuildOptionsLike = BuildOptionsLike_downloadKeycloakStaticResources & {
export type BuildContextLike = BuildContextLike_downloadKeycloakStaticResources & {
loginThemeResourcesFromKeycloakVersion: string;
publicDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function copyKeycloakResourcesToPublic(params: {
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const { buildOptions } = params;
const { buildContext } = params;
const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
const destDirPath = pathJoin(buildContext.publicDirPath, keycloak_resources);
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
@ -34,12 +34,12 @@ export async function copyKeycloakResourcesToPublic(params: {
{
destDirPath,
keycloakifyVersion: readThisNpmPackageVersion(),
buildOptions: {
buildContext: {
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
cacheDirPath: pathRelative(destDirPath, buildOptions.cacheDirPath),
cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath),
npmWorkspaceRootDirPath: pathRelative(
destDirPath,
buildOptions.npmWorkspaceRootDirPath
buildContext.npmWorkspaceRootDirPath
)
}
},
@ -74,14 +74,14 @@ export async function copyKeycloakResourcesToPublic(params: {
keycloakVersion: (() => {
switch (themeType) {
case "login":
return buildOptions.loginThemeResourcesFromKeycloakVersion;
return buildContext.loginThemeResourcesFromKeycloakVersion;
case "account":
return lastKeycloakVersionWithAccountV1;
}
})(),
themeType,
themeDirPath: destDirPath,
buildOptions
buildContext
});
}

View File

@ -1,27 +1,27 @@
import { join as pathJoin, relative as pathRelative } from "path";
import { type BuildOptions } from "./buildOptions";
import { type BuildContext } from "./buildContext";
import { assert } from "tsafe/assert";
import { lastKeycloakVersionWithAccountV1 } from "./constants";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
import { isInside } from "../tools/isInside";
export type BuildOptionsLike = {
export type BuildContextLike = {
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function downloadKeycloakDefaultTheme(params: {
keycloakVersion: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<{ defaultThemeDirPath: string }> {
const { keycloakVersion, buildOptions } = params;
const { keycloakVersion, buildContext } = params;
const { extractedDirPath } = await downloadAndExtractArchive({
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
cacheDirPath: buildOptions.cacheDirPath,
npmWorkspaceRootDirPath: buildOptions.npmWorkspaceRootDirPath,
cacheDirPath: buildContext.cacheDirPath,
npmWorkspaceRootDirPath: buildContext.npmWorkspaceRootDirPath,
uniqueIdOfOnOnArchiveFile: "downloadKeycloakDefaultTheme",
onArchiveFile: async params => {
if (!isInside({ dirPath: "theme", filePath: params.fileRelativePath })) {

View File

@ -2,28 +2,28 @@ import { transformCodebase } from "../tools/transformCodebase";
import { join as pathJoin } from "path";
import {
downloadKeycloakDefaultTheme,
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakDefaultTheme
type BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme
} from "./downloadKeycloakDefaultTheme";
import { resources_common, type ThemeType } from "./constants";
import type { BuildOptions } from "./buildOptions";
import type { BuildContext } from "./buildContext";
import { assert } from "tsafe/assert";
import { existsAsync } from "../tools/fs.existsAsync";
export type BuildOptionsLike = BuildOptionsLike_downloadKeycloakDefaultTheme & {};
export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme & {};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function downloadKeycloakStaticResources(params: {
themeType: ThemeType;
themeDirPath: string;
keycloakVersion: string;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}) {
const { themeType, themeDirPath, keycloakVersion, buildOptions } = params;
const { themeType, themeDirPath, keycloakVersion, buildContext } = params;
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion,
buildOptions
buildContext
});
const resourcesDirPath = pathJoin(themeDirPath, themeType, "resources");

View File

@ -1,61 +1,71 @@
import { assert } from "tsafe/assert";
import type { BuildOptions } from "./buildOptions";
import type { BuildContext } from "./buildContext";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
import * as fs from "fs/promises";
import { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync";
export type BuildOptionsLike = {
reactAppRootDirPath: string;
export type BuildContextLike = {
projectDirPath: string;
themeNames: string[];
environmentVariables: { name: string; default: string }[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function generateKcGenTs(params: {
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<void> {
const { buildOptions } = params;
const { buildContext } = params;
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
projectDirPath: buildContext.projectDirPath
});
await fs.writeFile(
pathJoin(themeSrcDirPath, "kc.gen.ts"),
Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`export type ThemeName = ${buildOptions.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildOptions.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildOptions.environmentVariables.length === 0 ? "never" : buildOptions.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const KcEnvNames: KcEnvName[] = [${buildOptions.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildOptions.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`/* prettier-ignore-end */`
].join("\n"),
"utf8"
)
const filePath = pathJoin(themeSrcDirPath, "kc.gen.ts");
const currentContent = (await existsAsync(filePath))
? await fs.readFile(filePath)
: undefined;
const newContent = Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const KcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildContext.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`/* prettier-ignore-end */`
].join("\n"),
"utf8"
);
if (currentContent !== undefined && currentContent.equals(newContent)) {
return;
}
await fs.writeFile(filePath, newContent);
}

View File

@ -7,10 +7,10 @@ import { themeTypes } from "./constants";
const themeSrcDirBasenames = ["keycloak-theme", "keycloak_theme"];
/** Can't catch error, if the directory isn't found, this function will just exit the process with an error message. */
export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
const { reactAppRootDirPath } = params;
export function getThemeSrcDirPath(params: { projectDirPath: string }) {
const { projectDirPath } = params;
const srcDirPath = pathJoin(reactAppRootDirPath, "src");
const srcDirPath = pathJoin(projectDirPath, "src");
const themeSrcDirPath: string | undefined = crawl({
dirPath: srcDirPath,

View File

@ -2,26 +2,26 @@ import * as child_process from "child_process";
import { Deferred } from "evt/tools/Deferred";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import type { BuildOptions } from "../shared/buildOptions";
import type { BuildContext } from "../shared/buildContext";
import * as fs from "fs";
import { join as pathJoin } from "path";
export type BuildOptionsLike = {
reactAppRootDirPath: string;
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: "vite" | "webpack";
npmWorkspaceRootDirPath: string;
reactAppBuildDirPath: string;
projectBuildDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function appBuild(params: {
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildOptions } = params;
const { buildContext } = params;
const { bundler } = buildOptions;
const { bundler } = buildContext;
const { command, args, cwd } = (() => {
switch (bundler) {
@ -29,12 +29,12 @@ export async function appBuild(params: {
return {
command: "npx",
args: ["vite", "build"],
cwd: buildOptions.reactAppRootDirPath
cwd: buildContext.projectDirPath
};
case "webpack": {
for (const dirPath of [
buildOptions.reactAppRootDirPath,
buildOptions.npmWorkspaceRootDirPath
buildContext.projectDirPath,
buildContext.npmWorkspaceRootDirPath
]) {
try {
const parsedPackageJson = JSON.parse(

View File

@ -2,27 +2,27 @@ import { skipBuildJarsEnvName } from "../shared/constants";
import * as child_process from "child_process";
import { Deferred } from "evt/tools/Deferred";
import { assert } from "tsafe/assert";
import type { BuildOptions } from "../shared/buildOptions";
import type { BuildContext } from "../shared/buildContext";
export type BuildOptionsLike = {
reactAppRootDirPath: string;
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: "vite" | "webpack";
npmWorkspaceRootDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
assert<BuildContext extends BuildContextLike ? true : false>();
export async function keycloakifyBuild(params: {
doSkipBuildJars: boolean;
buildOptions: BuildOptionsLike;
buildContext: BuildContextLike;
}): Promise<{ isKeycloakifyBuildSuccess: boolean }> {
const { buildOptions, doSkipBuildJars } = params;
const { buildContext, doSkipBuildJars } = params;
const dResult = new Deferred<{ isSuccess: boolean }>();
const child = child_process.spawn("npx", ["keycloakify", "build"], {
cwd: buildOptions.reactAppRootDirPath,
cwd: buildContext.projectDirPath,
env: {
...process.env,
...(doSkipBuildJars ? { [skipBuildJarsEnvName]: "true" } : {})

View File

@ -1,4 +1,5 @@
import { readBuildOptions } from "../shared/buildOptions";
import { getBuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude";
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
@ -80,11 +81,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({ cliCommandOptions });
const buildContext = getBuildContext({ cliCommandOptions });
{
const { isAppBuildSuccess } = await appBuild({
buildOptions
buildContext
});
if (!isAppBuildSuccess) {
@ -98,7 +99,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
doSkipBuildJars: false,
buildOptions
buildContext
});
if (!isKeycloakifyBuildSuccess) {
@ -112,7 +113,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
}
const metaInfKeycloakThemes = readMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
});
const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(
@ -138,7 +139,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { keycloakVersion } = await promptKeycloakVersion({
startingFromMajor: 17,
cacheDirPath: buildOptions.cacheDirPath
cacheDirPath: buildContext.cacheDirPath
});
console.log(`${keycloakVersion}`);
@ -259,7 +260,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return pathJoin(dirPath, value);
})();
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename);
const jarFilePath = pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename);
const { doUseBuiltInAccountV1Theme } = await (async () => {
let doUseBuiltInAccountV1Theme = false;
@ -267,7 +268,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
await extractArchive({
archiveFilePath: jarFilePath,
onArchiveFile: async ({ relativeFilePathInArchive, readFile, earlyExit }) => {
for (const themeName of buildOptions.themeNames) {
for (const themeName of buildContext.themeNames) {
if (
relativeFilePathInArchive ===
pathJoin("theme", themeName, "account", "theme.properties")
@ -292,9 +293,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const accountThemePropertyPatch = !doUseBuiltInAccountV1Theme
? undefined
: () => {
for (const themeName of buildOptions.themeNames) {
for (const themeName of buildContext.themeNames) {
const filePath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",
@ -346,12 +347,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"]
: []),
...[
...buildOptions.themeNames,
...buildContext.themeNames,
...(doUseBuiltInAccountV1Theme ? [] : [accountV1ThemeName])
]
.map(themeName => ({
localDirPath: pathJoin(
buildOptions.keycloakifyBuildDirPath,
buildContext.keycloakifyBuildDirPath,
"src",
"main",
"resources",
@ -365,6 +366,17 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`${localDirPath}:${containerDirPath}:rw`
])
.flat(),
...buildContext.environmentVariables
.map(({ name }) => ({ name, envValue: process.env[name] }))
.map(({ name, envValue }) =>
envValue === undefined ? undefined : { name, envValue }
)
.filter(exclude(undefined))
.map(({ name, envValue }) => [
"--env",
`${name}='${envValue.replace(/'/g, "'\\''")}'`
])
.flat(),
`quay.io/keycloak/keycloak:${keycloakVersion}`,
"start-dev",
...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24
@ -373,7 +385,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
...(realmJsonFilePath === undefined ? [] : ["--import-realm"])
],
{
cwd: buildOptions.keycloakifyBuildDirPath
cwd: buildContext.keycloakifyBuildDirPath
}
] as const;
@ -385,7 +397,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
child.on("exit", process.exit);
const srcDirPath = pathJoin(buildOptions.reactAppRootDirPath, "src");
const srcDirPath = pathJoin(buildContext.projectDirPath, "src");
{
const handler = async (data: Buffer) => {
@ -417,7 +429,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`- password: ${chalk.cyan.bold("password123")}`,
"",
`Watching for changes in ${chalk.bold(
`.${pathSep}${pathRelative(process.cwd(), buildOptions.reactAppRootDirPath)}`
`.${pathSep}${pathRelative(process.cwd(), buildContext.projectDirPath)}`
)}`
].join("\n")
);
@ -431,7 +443,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(chalk.cyan("Detected changes in the theme. Rebuilding ..."));
const { isAppBuildSuccess } = await appBuild({
buildOptions
buildContext
});
if (!isAppBuildSuccess) {
@ -440,7 +452,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
doSkipBuildJars: true,
buildOptions
buildContext
});
if (!isKeycloakifyBuildSuccess) {
@ -458,11 +470,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
.watch(
[
srcDirPath,
buildOptions.publicDirPath,
pathJoin(buildOptions.reactAppRootDirPath, "package.json"),
pathJoin(buildOptions.reactAppRootDirPath, "vite.config.ts"),
pathJoin(buildOptions.reactAppRootDirPath, "vite.config.js"),
pathJoin(buildOptions.reactAppRootDirPath, "index.html"),
buildContext.publicDirPath,
pathJoin(buildContext.projectDirPath, "package.json"),
pathJoin(buildContext.projectDirPath, "vite.config.ts"),
pathJoin(buildContext.projectDirPath, "vite.config.js"),
pathJoin(buildContext.projectDirPath, "index.html"),
pathJoin(getThisCodebaseRootDirPath(), "src")
],
{

View File

@ -4,14 +4,14 @@ import { assert } from "tsafe/assert";
import * as fs from "fs";
export function getNpmWorkspaceRootDirPath(params: {
reactAppRootDirPath: string;
projectDirPath: string;
dependencyExpected: string;
}) {
const { reactAppRootDirPath, dependencyExpected } = params;
const { projectDirPath, dependencyExpected } = params;
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
const cwd = pathResolve(
pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])
pathJoin(...[projectDirPath, ...Array(depth).fill("..")])
);
assert(cwd !== pathSep, "NPM workspace not found");

View File

@ -1,13 +1,13 @@
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import { getBuildContext } from "./shared/buildContext";
import { generateKcGenTs } from "./shared/generateKcGenTs";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
const buildContext = getBuildContext({
cliCommandOptions
});
await generateKcGenTs({ buildOptions });
await generateKcGenTs({ buildContext });
}

View File

@ -21,7 +21,21 @@ export function createGetKcClsx<ClassKey extends string>(params: {
return true;
}
return JSON.stringify(params1.classes) === JSON.stringify(params2.classes);
if (params1.classes === undefined || params2.classes === undefined) {
return false;
}
if (Object.keys(params1.classes).length !== Object.keys(params2.classes).length) {
return false;
}
for (const key in params1.classes) {
if (params1.classes[key] !== params2.classes[key]) {
return false;
}
}
return true;
}
let cache:

View File

@ -1,3 +0,0 @@
export const isStorybook =
typeof window === "object" &&
Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined;

View File

@ -2,7 +2,8 @@ import { lazy, Suspense } from "react";
import { assert, type Equals } from "tsafe/assert";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "./KcContext";
import type { I18n } from "keycloakify/login/i18n";
import type { KcContext } from "keycloakify/login/KcContext";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
const Login = lazy(() => import("keycloakify/login/pages/Login"));
@ -40,11 +41,12 @@ const LoginResetOtp = lazy(() => import("keycloakify/login/pages/LoginResetOtp")
const LoginX509Info = lazy(() => import("keycloakify/login/pages/LoginX509Info"));
const WebauthnError = lazy(() => import("keycloakify/login/pages/WebauthnError"));
type FallbackProps = PageProps<KcContext> & {
type DefaultPageProps = PageProps<KcContext, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
doMakeUserConfirmPassword: boolean;
};
export default function Fallback(props: FallbackProps) {
export default function DefaultPage(props: DefaultPageProps) {
const { kcContext, ...rest } = props;
return (

View File

@ -6,10 +6,10 @@ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { I18n } from "./i18n";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
export default function Template(props: TemplateProps<KcContext>) {
export default function Template(props: TemplateProps<KcContext, I18n>) {
const {
displayInfo = false,
displayMessage = true,
@ -21,6 +21,7 @@ export default function Template(props: TemplateProps<KcContext>) {
documentTitle,
bodyClassName,
kcContext,
i18n,
doUseDefaultCss,
classes,
children
@ -28,7 +29,7 @@ export default function Template(props: TemplateProps<KcContext>) {
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = useI18n({ kcContext });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;

View File

@ -1,10 +1,11 @@
import type { ReactNode } from "react";
import type { KcContext } from "./KcContext";
export type TemplateProps<KcContext extends KcContext.Common> = {
export type TemplateProps<KcContext, I18n> = {
kcContext: KcContext;
i18n: I18n;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;
children: ReactNode;
displayInfo?: boolean;
displayMessage?: boolean;
@ -16,8 +17,6 @@ export type TemplateProps<KcContext extends KcContext.Common> = {
infoNode?: ReactNode;
documentTitle?: string;
bodyClassName?: string;
children: ReactNode;
};
export type ClassKey =

View File

@ -9,12 +9,14 @@ import {
type FormFieldError
} from "keycloakify/login/lib/useUserProfileForm";
import type { Attribute } from "keycloakify/login/KcContext";
import { useI18n, type I18n } from "./i18n";
import type { I18n } from "./i18n";
export type UserProfileFormFieldsProps = {
kcContext: KcContextLike;
i18n: I18n;
kcClsx: KcClsx;
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
doMakeUserConfirmPassword: boolean;
BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
AfterField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
};
@ -28,19 +30,17 @@ type BeforeAfterFieldProps = {
i18n: I18n;
};
// NOTE: Enabled by default but it's a UX best practice to set it to false.
const doMakeUserConfirmPassword = true;
export default function UserProfileFormFields(props: UserProfileFormFieldsProps) {
const { kcContext, kcClsx, onIsFormSubmittableValueChange, BeforeField, AfterField } = props;
const { kcContext, i18n, kcClsx, onIsFormSubmittableValueChange, doMakeUserConfirmPassword, BeforeField, AfterField } = props;
const { advancedMsg } = useI18n({ kcContext });
const { advancedMsg } = i18n;
const {
formState: { formFieldStates, isFormSubmittable },
dispatchFormAction
} = useUserProfileForm({
kcContext,
i18n,
doMakeUserConfirmPassword
});
@ -48,8 +48,6 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps)
onIsFormSubmittableValueChange(isFormSubmittable);
}, [isFormSubmittable]);
const i18n = useI18n({ kcContext });
const groupNameRef = { current: "" };
return (

View File

@ -132,7 +132,7 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
__localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
});
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag !== fallbackLanguageTag;
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag === fallbackLanguageTag;
const result: Result = {
i18n: {
@ -177,7 +177,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
const { getI18n } = createGetI18n(extraMessages);
function useI18n(params: { kcContext: KcContextLike }): I18n {
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
@ -200,7 +200,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
};
}, []);
return i18n_toReturn;
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };

View File

@ -1,10 +1,5 @@
export type { MessageKey, KcContextLike } from "./i18n";
import { createUseI18n } from "./i18n";
export { createUseI18n };
import type { GenericI18n, MessageKey, KcContextLike } from "./i18n";
export type { MessageKey, KcContextLike };
export type I18n = GenericI18n<MessageKey>;
export { createUseI18n } from "./i18n";
export { fallbackLanguageTag } from "./i18n";
const { useI18n, ofTypeI18n } = createUseI18n({});
export type I18n = typeof ofTypeI18n;
export { useI18n };

View File

@ -11,7 +11,7 @@ import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/
import type { KcContext } from "../KcContext";
import type { MessageKey } from "keycloakify/login/i18n";
import { KcContextLike as KcContextLike_i18n } from "keycloakify/login/i18n";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export type FormFieldError = {
errorMessage: JSX.Element;
@ -83,6 +83,7 @@ assert<Extract<KcContext.Register, { pageId: "register.ftl" }> extends KcContext
export type ParamsOfUseUserProfileForm = {
kcContext: KcContextLike;
i18n: I18n;
doMakeUserConfirmPassword: boolean;
};
@ -105,7 +106,7 @@ namespace internal {
}
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
const { kcContext, doMakeUserConfirmPassword } = params;
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
const { insertScriptTags } = useInsertScriptTags({
componentOrHookName: "useUserProfileForm",
@ -122,7 +123,8 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
}, []);
const { getErrors } = useGetErrors({
kcContext
kcContext,
i18n
});
const initialState = useMemo((): internal.State => {
@ -523,12 +525,12 @@ type KcContextLike_useGetErrors = KcContextLike_i18n & {
assert<KcContextLike extends KcContextLike_useGetErrors ? true : false>();
function useGetErrors(params: { kcContext: KcContextLike_useGetErrors }) {
const { kcContext } = params;
function useGetErrors(params: { kcContext: KcContextLike_useGetErrors; i18n: I18n }) {
const { kcContext, i18n } = params;
const { messagesPerField, passwordPolicies } = kcContext;
const { msg, msgStr, advancedMsg, advancedMsgStr } = useI18n({ kcContext });
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const getErrors = useConstCallback(
(params: {

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,11 +13,12 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
const { code } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,10 +13,10 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
const { url, triggered_from_aia } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("deleteAccountConfirm")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("deleteAccountConfirm")}>
<form action={url.loginAction} className="form-vertical" method="post">
<div className="alert alert-warning" style={{ marginTop: "0", marginBottom: "30px" }}>
<span className="pficon pficon-warning-triangle-o"></span>

View File

@ -1,12 +1,12 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = useI18n({ kcContext });
const { msgStr, msg } = i18n;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -18,6 +18,7 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}

View File

@ -1,16 +1,23 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { message, client, skipLink } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} displayMessage={false} headerNode={msg("errorTitle")}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}
headerNode={msg("errorTitle")}
>
<div id="kc-error-message">
<p className="instruction">{message.summary}</p>
{!skipLink && client !== undefined && client.baseUrl !== undefined && (

View File

@ -1,14 +1,14 @@
import { useEffect } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { logout } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
useEffect(() => {
if (logout.logoutRedirectUri) {
@ -19,6 +19,7 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
documentTitle={msgStr("frontchannel-logout.title")}

View File

@ -4,21 +4,22 @@ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>> & {
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
doMakeUserConfirmPassword: boolean;
};
export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
classes
});
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { url, messagesPerField } = kcContext;
@ -27,6 +28,7 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={messagesPerField.exists("global")}
@ -34,7 +36,13 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
headerNode={msg("loginIdpReviewProfileTitle")}
>
<form id="kc-idp-review-profile-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} kcClsx={kcClsx} />
<UserProfileFormFields
kcContext={kcContext}
i18n={i18n}
onIsFormSubmittableValueChange={setIsFomSubmittable}
kcClsx={kcClsx}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
<div className={kcClsx("kcFormGroupClass")}>
<div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>
<div className={kcClsx("kcFormOptionsWrapperClass")} />

View File

@ -1,12 +1,12 @@
import { assert } from "keycloakify/tools/assert";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = useI18n({ kcContext });
const { msgStr, msg } = i18n;
assert(
kcContext.message !== undefined,
@ -18,6 +18,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}

View File

@ -4,10 +4,10 @@ import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -16,7 +16,6 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
@ -24,6 +23,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("username", "password")}

View File

@ -1,10 +1,10 @@
import { getKcClsx, KcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,12 +13,10 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr, advancedMsg } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("loginTotpTitle")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("loginTotpTitle")}>
<>
<ol id="kc-totp-settings">
<li>

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,10 +13,10 @@ export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext,
const { url, idpAlias } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("confirmLinkIdpTitle")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("confirmLinkIdpTitle")}>
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={kcClsx("kcFormGroupClass")}>
<button

View File

@ -1,16 +1,22 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("emailLinkIdpTitle", idpAlias)}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("emailLinkIdpTitle", idpAlias)}
>
<p id="instruction1" className="instruction">
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
</p>

View File

@ -1,15 +1,15 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginOauth2DeviceVerifyUserCode(
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>>
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>
) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { url } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -17,7 +17,13 @@ export default function LoginOauth2DeviceVerifyUserCode(
});
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("oauth2DeviceVerificationTitle")}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("oauth2DeviceVerificationTitle")}
>
<form
id="kc-user-verify-device-user-code-form"
className={kcClsx("kcFormClass")}

View File

@ -1,13 +1,13 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>>) {
const { kcContext, doUseDefaultCss, classes, Template } = props;
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
const { url, oauth, client } = kcContext;
const { msg, msgStr, advancedMsg, advancedMsgStr } = useI18n({ kcContext });
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -17,6 +17,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
bodyClassName="oauth"

View File

@ -2,10 +2,10 @@ import { Fragment } from "react";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -14,11 +14,12 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
const { otpLogin, url, messagesPerField } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("totp")}

View File

@ -1,16 +1,16 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url } = kcContext;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("pageExpiredTitle")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("pageExpiredTitle")}>
<p id="instruction1" className="instruction">
{msg("pageExpiredMsg1")}
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>

View File

@ -4,10 +4,10 @@ import { assert } from "tsafe/assert";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -16,7 +16,6 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
const { realm, url, messagesPerField } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
@ -24,6 +23,7 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("doLogIn")}

View File

@ -4,10 +4,10 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -16,7 +16,6 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
const { recoveryAuthnCodesConfigBean, isAppInitiatedAction } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({
@ -145,7 +144,13 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
}, []);
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("recovery-code-config-header")}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("recovery-code-config-header")}
>
<div className={clsx("pf-c-alert", "pf-m-warning", "pf-m-inline", kcClsx("kcRecoveryCodesWarning"))} aria-label="Warning alert">
<div className="pf-c-alert__icon">
<i className="pficon-warning-triangle-o" aria-hidden="true" />

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,11 +13,12 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
const { url, messagesPerField, recoveryAuthnCodesInputBean } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("auth-recovery-code-header")}

View File

@ -2,10 +2,10 @@ import { Fragment } from "react";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -14,11 +14,12 @@ export default function LoginResetOtp(props: PageProps<Extract<KcContext, { page
const { url, messagesPerField, configuredOtpCredentials } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("totp")}

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,11 +13,12 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
const { url, realm, auth, messagesPerField } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayInfo

View File

@ -3,17 +3,16 @@ import { assert } from "tsafe/assert";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
classes
});
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { url, messagesPerField, isAppInitiatedAction } = kcContext;
@ -21,6 +20,7 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("password", "password-confirm")}

View File

@ -4,14 +4,15 @@ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>> & {
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
doMakeUserConfirmPassword: boolean;
};
export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -20,20 +21,27 @@ export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
const { url, isAppInitiatedAction } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayRequiredFields
headerNode={msg("loginProfileTitle")}
>
<form id="kc-update-profile-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} kcClsx={kcClsx} onIsFormSubmittableValueChange={setIsFormSubmittable} />
<UserProfileFormFields
kcContext={kcContext}
i18n={i18n}
kcClsx={kcClsx}
onIsFormSubmittableValueChange={setIsFormSubmittable}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
<div className={kcClsx("kcFormGroupClass")}>
<div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>
<div className={kcClsx("kcFormOptionsWrapperClass")} />

View File

@ -3,10 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -15,13 +15,14 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
const { social, realm, url, usernameHidden, login, registrationDisabled, messagesPerField } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("username")}

View File

@ -1,17 +1,18 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
const { url, user } = kcContext;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayInfo

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LoginX509Info(props: PageProps<Extract<KcContext, { pageId: "login-x509-info.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LoginX509Info(props: PageProps<Extract<KcContext, { pageId: "login-x509-info.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,10 +13,10 @@ export default function LoginX509Info(props: PageProps<Extract<KcContext, { page
const { url, x509 } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("doLogIn")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("doLogIn")}>
<form id="kc-x509-login-info" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
<div className={kcClsx("kcFormGroupClass")}>
<div className={kcClsx("kcLabelWrapperClass")}>

View File

@ -1,10 +1,10 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -13,10 +13,10 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
const { url, client, logoutConfirm } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("logoutConfirmTitle")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("logoutConfirmTitle")}>
<div id="kc-logout-confirm" className="content-area">
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">

View File

@ -1,10 +1,10 @@
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { KcContext } from "keycloakify/account/KcContext";
export type PageProps<NarowedKcContext = KcContext> = {
Template: LazyOrNot<(props: TemplateProps<any>) => JSX.Element | null>;
kcContext: NarowedKcContext;
export type PageProps<NarrowedKcContext, I18n> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
kcContext: NarrowedKcContext;
i18n: I18n;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;
};

View File

@ -6,14 +6,15 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" }>> & {
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
doMakeUserConfirmPassword: boolean;
};
export default function Register(props: RegisterProps) {
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -22,15 +23,27 @@ export default function Register(props: RegisterProps) {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey, termsAcceptanceRequired } = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("registerTitle")} displayRequiredFields>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={msg("registerTitle")}
displayRequiredFields
>
<form id="kc-register-form" className={kcClsx("kcFormClass")} action={url.registrationAction} method="post">
<UserProfileFormFields kcContext={kcContext} kcClsx={kcClsx} onIsFormSubmittableValueChange={setIsFormSubmittable} />
<UserProfileFormFields
kcContext={kcContext}
i18n={i18n}
kcClsx={kcClsx}
onIsFormSubmittableValueChange={setIsFormSubmittable}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
{termsAcceptanceRequired && <TermsAcceptance i18n={i18n} kcClsx={kcClsx} messagesPerField={messagesPerField} />}
{recaptchaRequired && (
<div className="form-group">

View File

@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageId: "saml-post-form.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageId: "saml-post-form.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { msgStr, msg } = useI18n({ kcContext });
const { msgStr, msg } = i18n;
const { samlPost } = kcContext;
@ -26,7 +26,7 @@ export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageI
htmlFormElement.submit();
}, [htmlFormElement]);
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("saml.post-form.title")}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("saml.post-form.title")}>
<p>{msg("saml.post-form.message")}</p>
<form name="saml-post-binding" method="post" action={samlPost.url} ref={setHtmlFormElement}>
{samlPost.SAMLRequest && <input type="hidden" name="SAMLRequest" value={samlPost.SAMLRequest} />}

View File

@ -1,18 +1,19 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url, auth } = kcContext;
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg } = useI18n({ kcContext });
const { msg } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayInfo={false}

View File

@ -3,17 +3,17 @@ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
classes
});
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { locale, url } = kcContext;
@ -24,7 +24,14 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
}
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} displayMessage={false} headerNode={msg("termsTitle")}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}
headerNode={msg("termsTitle")}
>
<div id="kc-terms-text" lang={termsLanguageTag !== locale?.currentLanguageTag ? termsLanguageTag : undefined}>
<Markdown>{termsMarkdown}</Markdown>
</div>

View File

@ -4,21 +4,21 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
type UpdateEmailProps = PageProps<Extract<KcContext, { pageId: "update-email.ftl" }>> & {
type UpdateEmailProps = PageProps<Extract<KcContext, { pageId: "update-email.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
doMakeUserConfirmPassword: boolean;
};
export default function UpdateEmail(props: UpdateEmailProps) {
const { kcContext, doUseDefaultCss, Template, classes, UserProfileFormFields } = props;
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
classes
});
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
@ -28,6 +28,7 @@ export default function UpdateEmail(props: UpdateEmailProps) {
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={messagesPerField.exists("global")}
@ -35,7 +36,13 @@ export default function UpdateEmail(props: UpdateEmailProps) {
headerNode={msg("updateEmailTitle")}
>
<form id="kc-update-email-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} kcClsx={kcClsx} onIsFormSubmittableValueChange={setIsFormSubmittable} />
<UserProfileFormFields
kcContext={kcContext}
i18n={i18n}
kcClsx={kcClsx}
onIsFormSubmittableValueChange={setIsFormSubmittable}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
<div className={kcClsx("kcFormGroupClass")}>
<div id="kc-form-options" className={kcClsx("kcFormOptionsClass")}>

View File

@ -5,10 +5,10 @@ import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
@ -26,7 +26,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
shouldDisplayAuthenticators
} = kcContext;
const { msg, msgStr, advancedMsg } = useI18n({ kcContext });
const { msg, msgStr, advancedMsg } = i18n;
const { insertScriptTags } = useInsertScriptTags({
componentOrHookName: "WebauthnAuthenticate",
@ -137,6 +137,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("username")}

View File

@ -1,14 +1,14 @@
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n } from "../i18n";
import type { I18n } from "../i18n";
export default function WebauthnError(props: PageProps<Extract<KcContext, { pageId: "webauthn-error.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function WebauthnError(props: PageProps<Extract<KcContext, { pageId: "webauthn-error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url, isAppInitiatedAction } = kcContext;
const { msg, msgStr } = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { kcClsx } = getKcClsx({
doUseDefaultCss,
@ -16,7 +16,14 @@ export default function WebauthnError(props: PageProps<Extract<KcContext, { page
});
return (
<Template kcContext={kcContext} doUseDefaultCss={doUseDefaultCss} classes={classes} displayMessage headerNode={msg("webauthn-error-title")}>
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage
headerNode={msg("webauthn-error-title")}
>
<form id="kc-error-credential-form" className={kcClsx("kcFormClass")} action={url.loginAction} method="post">
<input type="hidden" id="executionValue" name="authenticationExecution" />
<input type="hidden" id="isSetRetry" name="isSetRetry" />

View File

@ -4,10 +4,10 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import { useI18n, type I18n } from "../i18n";
import type { I18n } from "../i18n";
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>>) {
const { kcContext, doUseDefaultCss, Template, classes } = props;
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
@ -29,7 +29,6 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
isAppInitiatedAction
} = kcContext;
const i18n = useI18n({ kcContext });
const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({
@ -207,6 +206,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={

View File

@ -11,22 +11,22 @@ import { rm } from "../bin/tools/fs.rm";
import { copyKeycloakResourcesToPublic } from "../bin/shared/copyKeycloakResourcesToPublic";
import { assert } from "tsafe/assert";
import {
readBuildOptions,
getBuildContext,
type BuildContext,
type BuildOptions,
type UserProvidedBuildOptions,
type ResolvedViteConfig
} from "../bin/shared/buildOptions";
} from "../bin/shared/buildContext";
import MagicString from "magic-string";
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
export type Params = UserProvidedBuildOptions & {
postBuild?: (buildOptions: Omit<BuildOptions, "bundler">) => Promise<void>;
export type Params = BuildOptions & {
postBuild?: (buildContext: Omit<BuildContext, "bundler">) => Promise<void>;
};
export function keycloakify(params?: Params) {
const { postBuild, ...userProvidedBuildOptions } = params ?? {};
const { postBuild, ...buildOptions } = params ?? {};
let reactAppRootDirPath: string | undefined = undefined;
let projectDirPath: string | undefined = undefined;
let urlPathname: string | undefined = undefined;
let buildDirPath: string | undefined = undefined;
let command: "build" | "serve" | undefined = undefined;
@ -45,16 +45,16 @@ export function keycloakify(params?: Params) {
break run_post_build_script_case;
}
const buildOptions = JSON.parse(envValue) as BuildOptions;
const buildContext = JSON.parse(envValue) as BuildContext;
await postBuild?.(buildOptions);
await postBuild?.(buildContext);
process.exit(0);
}
command = resolvedConfig.command;
reactAppRootDirPath = resolvedConfig.root;
projectDirPath = resolvedConfig.root;
urlPathname = (() => {
let out = resolvedConfig.env.BASE_URL;
@ -86,7 +86,7 @@ export function keycloakify(params?: Params) {
return out;
})();
buildDirPath = pathJoin(reactAppRootDirPath, resolvedConfig.build.outDir);
buildDirPath = pathJoin(projectDirPath, resolvedConfig.build.outDir);
resolve_vite_config_case: {
const envValue =
@ -102,13 +102,13 @@ export function keycloakify(params?: Params) {
JSON.stringify(
id<ResolvedViteConfig>({
publicDir: pathRelative(
reactAppRootDirPath,
projectDirPath,
resolvedConfig.publicDir
),
assetsDir: resolvedConfig.build.assetsDir,
buildDir: resolvedConfig.build.outDir,
urlPathname,
userProvidedBuildOptions
buildOptions
})
)
);
@ -116,18 +116,18 @@ export function keycloakify(params?: Params) {
process.exit(0);
}
const buildOptions = readBuildOptions({
const buildContext = getBuildContext({
cliCommandOptions: {
reactAppRootDirPath
projectDirPath
}
});
await Promise.all([
copyKeycloakResourcesToPublic({
buildOptions
buildContext
}),
generateKcGenTs({
buildOptions
buildContext
})
]);
},
@ -139,11 +139,11 @@ export function keycloakify(params?: Params) {
return;
}
assert(reactAppRootDirPath !== undefined);
assert(projectDirPath !== undefined);
{
const isWithinSourceDirectory = id.startsWith(
pathJoin(reactAppRootDirPath, "src") + pathSep
pathJoin(projectDirPath, "src") + pathSep
);
if (!isWithinSourceDirectory) {

View File

@ -1,10 +0,0 @@
import React from "react";
import Fallback from "../../dist/account/Fallback";
import type { KcContext } from "./KcContext";
import Template from "../../dist/account/Template";
export default function KcApp(props: { kcContext: KcContext }) {
const { kcContext } = props;
return <Fallback kcContext={kcContext} Template={Template} doUseDefaultCss={true} />;
}

View File

@ -0,0 +1,13 @@
import React from "react";
import DefaultPage from "../../dist/account/Fallback";
import { useI18n } from "./i18n";
import type { KcContext } from "./KcContext";
import Template from "../../dist/account/Template";
export default function KcPage(props: { kcContext: KcContext }) {
const { kcContext } = props;
const { i18n } = useI18n({ kcContext });
return <DefaultPage kcContext={kcContext} i18n={i18n} Template={Template} doUseDefaultCss={true} />;
}

View File

@ -3,7 +3,7 @@ import type { DeepPartial } from "../../dist/tools/DeepPartial";
import type { KcContext } from "./KcContext";
import { createGetKcContextMock } from "../../dist/account/KcContext";
import type { KcContextExtension, KcContextExtensionPerPage } from "./KcContext";
import KcApp from "./KcApp";
import KcPage from "./KcPage";
import { themeNames, kcEnvDefaults } from "../kc.gen";
const kcContextExtension: KcContextExtension = {
@ -21,10 +21,10 @@ export const { getKcContextMock } = createGetKcContextMock({
overridesPerPage: {}
});
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
export function createKcPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
const { pageId } = params;
function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
function KcPageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
const { kcContext: overrides } = props;
const kcContextMock = getKcContextMock({
@ -34,10 +34,10 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
return (
<React.StrictMode>
<KcApp kcContext={kcContextMock} />
<KcPage kcContext={kcContextMock} />
</React.StrictMode>
);
}
return { PageStory };
return { KcPageStory };
}

View File

@ -1,18 +1,18 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
import { createKcPageStory } from "../KcPageStory";
const { PageStory } = createPageStory({ pageId: "account.ftl" });
const { KcPageStory } = createKcPageStory({ pageId: "account.ftl" });
const meta = {
title: "account/account.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <PageStory />
render: () => <KcPageStory />
};

View File

@ -1,25 +1,25 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
import { createKcPageStory } from "../KcPageStory";
const { PageStory } = createPageStory({ pageId: "federatedIdentity.ftl" });
const { KcPageStory } = createKcPageStory({ pageId: "federatedIdentity.ftl" });
const meta = {
title: "account/federatedIdentity.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <PageStory />
render: () => <KcPageStory />
};
export const NotConnected: Story = {
render: () => (
<PageStory
<KcPageStory
kcContext={{
pageId: "federatedIdentity.ftl",
federatedIdentity: {

View File

@ -1,15 +1,15 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
import { createKcPageStory } from "../KcPageStory";
const { PageStory } = createPageStory({
const { KcPageStory } = createKcPageStory({
pageId: "log.ftl"
});
const meta = {
title: "account/log.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
@ -17,7 +17,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<PageStory
<KcPageStory
kcContext={{
log: {
events: [

View File

@ -1,25 +1,25 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
import { createKcPageStory } from "../KcPageStory";
const { PageStory } = createPageStory({ pageId: "password.ftl" });
const { KcPageStory } = createKcPageStory({ pageId: "password.ftl" });
const meta = {
title: "account/password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <PageStory />
render: () => <KcPageStory />
};
export const WithMessage: Story = {
render: () => (
<PageStory
<KcPageStory
kcContext={{
message: { type: "success", summary: "This is a test message" }
}}

View File

@ -1,13 +1,13 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory } from "../PageStory";
import { createKcPageStory } from "../KcPageStory";
const { PageStory } = createPageStory({ pageId: "sessions.ftl" });
const { KcPageStory } = createKcPageStory({ pageId: "sessions.ftl" });
const meta = {
title: "account/sessions.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
@ -15,7 +15,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<PageStory
<KcPageStory
kcContext={{
sessions: {
sessions: [
@ -45,7 +45,7 @@ export const Default: Story = {
export const WithError: Story = {
render: () => (
<PageStory
<KcPageStory
kcContext={{
url: { passwordUrl: "/auth/realms/keycloakify/account/password" },
stateChecker: "xQ7EOgFrLi4EvnJ8dbXKhwFGWk_bkOp0X89mhilt1os",

Some files were not shown because too many files have changed in this diff Show More